OLD | NEW |
---|---|
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include <dlfcn.h> | |
6 #include <mach/mach.h> | |
7 #include <mach/thread_status.h> | |
8 #include <mach-o/swap.h> | |
9 #include <stdio.h> | |
10 #include <stdlib.h> | |
11 | |
12 #include <map> | |
13 | |
14 #include "base/logging.h" | |
15 #include "base/md5.h" | |
5 #include "base/profiler/native_stack_sampler.h" | 16 #include "base/profiler/native_stack_sampler.h" |
17 #include "base/time/time.h" | |
6 | 18 |
7 namespace base { | 19 namespace base { |
8 | 20 |
21 namespace { | |
22 | |
23 // Copy of x86_64 thread context structure. | |
24 struct ThreadContext { | |
25 uint64_t rax; | |
26 uint64_t rbx; | |
27 uint64_t rcx; | |
28 uint64_t rdx; | |
29 uint64_t rdi; | |
30 uint64_t rsi; | |
31 uint64_t rbp; | |
32 uint64_t rsp; | |
33 uint64_t r8; | |
34 uint64_t r9; | |
35 uint64_t r10; | |
36 uint64_t r11; | |
37 uint64_t r12; | |
38 uint64_t r13; | |
39 uint64_t r14; | |
40 uint64_t r15; | |
41 uint64_t rip; | |
42 uint64_t rflags; | |
43 uint64_t cs; | |
44 uint64_t fs; | |
45 uint64_t gs; | |
46 }; | |
47 | |
48 // Struct for stack walking. | |
49 struct StackFrame { | |
50 struct StackFrame* prev; | |
51 uintptr_t return_addr; | |
52 }; | |
53 | |
54 // Overwrites |dst| with StackFrame referenced in |src|. Returns true if stack | |
55 // jump was successful. Failure implies address in |src| is corrupt. | |
56 bool SafeJump(const StackFrame* src, StackFrame* dst) { | |
57 vm_size_t bytesCopied = 0; | |
58 return vm_read_overwrite(mach_task_self(), (vm_address_t)src, | |
59 (vm_size_t)sizeof(StackFrame), (vm_address_t)dst, | |
60 &bytesCopied) == KERN_SUCCESS; | |
61 } | |
62 | |
63 // Functions related to retrieving Mach-O Identifer ---------------------------- | |
64 // These functions were cannibalized from Mach-O Identifier procedures found in | |
65 // breakpad/src/common/mac. Support for non-X86_64 architectures and MD5 IDs | |
66 // were removed to simplify the code. | |
67 | |
68 // Converts |identifier| to a hexstring and writes to |id_dst|. | |
69 void ConvertIdentifierToString(const unsigned char identifier[16], | |
Robert Sesek
2015/09/14 18:55:54
Can you use HexEncode() from base/strings/string_n
| |
70 std::string* id_dst) { | |
71 char buffer[33]; | |
72 int buffer_idx = 0; | |
73 for (int idx = 0; idx < 16; ++idx) { | |
74 int hi = (identifier[idx] >> 4) & 0x0F; | |
75 int lo = (identifier[idx]) & 0x0F; | |
76 | |
77 buffer[buffer_idx++] = | |
78 static_cast<char>((hi >= 10) ? ('A' + hi - 10) : ('0' + hi)); | |
79 buffer[buffer_idx++] = | |
80 static_cast<char>((lo >= 10) ? ('A' + lo - 10) : ('0' + lo)); | |
81 } | |
82 | |
83 // NULL terminate | |
84 buffer[32] = 0; | |
85 *id_dst = std::string(buffer); | |
86 } | |
87 | |
88 // Returns offset in bytes where the x86_64 file header is located in |fd|. | |
89 // Returns 0 if any read fails, or |fd| is not a valid FAT Mach-O file or has | |
90 // not been built for x86_64. | |
91 off_t GetMach64HeaderOffset(int fd) { | |
92 fat_header hdr; | |
93 if (!pread(fd, &hdr, sizeof(hdr), 0) || | |
94 (hdr.magic != FAT_MAGIC && hdr.magic != FAT_CIGAM)) | |
95 return 0; | |
96 | |
97 if (hdr.magic == FAT_CIGAM) | |
98 swap_fat_header(&hdr, NXHostByteOrder()); | |
99 | |
100 // Search all fat architectures for x86_64. | |
101 off_t offset = sizeof(hdr); | |
102 fat_arch arch; | |
103 for (uint32_t i = 0; i < hdr.nfat_arch; ++i) { | |
104 if (!pread(fd, &arch, sizeof(arch), offset)) | |
105 return 0; | |
106 | |
107 if (NXHostByteOrder() != NX_BigEndian) | |
108 swap_fat_arch(&arch, 1, NXHostByteOrder()); | |
109 | |
110 if (arch.cputype == CPU_TYPE_X86_64) | |
111 return arch.offset; | |
112 offset += sizeof(arch); | |
113 } | |
114 return 0; | |
115 } | |
116 | |
117 // Returns true if Mach-O file referenced by |fd| is built specifically for | |
118 // x86_64 architectures. Also returns false if read fails. | |
119 bool IsX64Header(int fd) { | |
120 mach_header_64 hdr; | |
121 if (!pread(fd, &hdr, sizeof(hdr), 0) || | |
122 (hdr.magic != MH_MAGIC_64 && hdr.magic != MH_CIGAM_64)) | |
123 return false; | |
124 if (hdr.magic == MH_CIGAM_64) | |
125 swap_mach_header_64(&hdr, NXHostByteOrder()); | |
126 if (hdr.cputype != CPU_TYPE_X86_64) | |
127 return false; | |
128 return true; | |
129 } | |
130 | |
131 // Fills |id| with UUID of x86_64 Mach-O file specified by |fd|. |offset| is | |
132 // the offset in bytes into |fd|, where the x86_64 header is located. Returns | |
133 // false if header is malformed, a read fails, or the header does not specify | |
134 // the UUID load command. | |
135 bool GetX64UUIDAt(int fd, unsigned char id[16], off_t offset) { | |
136 mach_header_64 hdr; | |
137 if (!pread(fd, &hdr, sizeof(hdr), offset) || | |
138 (hdr.magic != MH_MAGIC_64 && hdr.magic != MH_CIGAM_64)) | |
139 return false; | |
140 | |
141 bool swap = hdr.magic == MH_CIGAM_64; | |
142 if (swap) | |
143 swap_mach_header_64(&hdr, NXHostByteOrder()); | |
144 | |
145 // Look through load commands for UUID command. | |
146 offset += sizeof(hdr); | |
147 for (uint32_t i = 0; i < hdr.ncmds; ++i) { | |
148 load_command cmd; | |
149 if (!pread(fd, &cmd, sizeof(cmd), offset)) | |
150 return false; | |
151 | |
152 if (swap) | |
153 swap_load_command(&cmd, NXHostByteOrder()); | |
154 if (cmd.cmd == LC_UUID) { | |
155 uuid_command uuid_cmd; | |
156 if (!pread(fd, &uuid_cmd, sizeof(uuid_cmd), offset)) | |
157 return false; | |
158 if (swap) | |
159 swap_uuid_command(&uuid_cmd, NXHostByteOrder()); | |
160 memcpy(id, &uuid_cmd, sizeof(uuid_cmd)); | |
161 return true; | |
162 } | |
163 offset += cmd.cmdsize; | |
164 } | |
165 return false; | |
166 } | |
167 | |
168 // Fills |id| with Mach-O UUID retrieved from Mach-O file specified by |fd|. | |
169 // This function returns false if |fd| has not been built for X86_64 or if UUID | |
170 // cannot be found. | |
171 bool GetUUID(int fd, unsigned char id[16]) { | |
172 off_t offset = 0; | |
173 if (!IsX64Header(fd) && !(offset = GetMach64HeaderOffset(fd))) | |
174 return false; | |
175 return GetX64UUIDAt(fd, id, offset); | |
176 } | |
177 | |
178 // Obtains an ID of binary specified by |path| and loads it into |id_dst|. | |
179 // Returns false and sets |id_dst| to empty string if GetUUID fails. | |
180 bool GetUniqueId(const char* path, std::string* id_dst) { | |
181 unsigned char id[16]; | |
182 int fd = open(path, O_RDONLY); | |
183 if (!GetUUID(fd, id)) { | |
184 close(fd); | |
185 *id_dst = ""; | |
186 return false; | |
187 } | |
188 close(fd); | |
189 ConvertIdentifierToString(id, id_dst); | |
190 return true; | |
191 } | |
192 | |
193 // Functions related to grabbing a stack trace -------------------------------- | |
194 | |
195 // Fills |state| with |target_thread|'s context. | |
196 bool GetThreadState(thread_act_t target_thread, ThreadContext* state) { | |
197 mach_msg_type_number_t count = | |
198 static_cast<mach_msg_type_number_t>(MACHINE_THREAD_STATE_COUNT); | |
199 return thread_get_state(target_thread, x86_THREAD_STATE64, | |
200 reinterpret_cast<thread_state_t>(state), | |
201 &count) == KERN_SUCCESS; | |
202 } | |
203 | |
204 // Walks |thread_handle|'s stack and fills |instruction_pointers|. | |
205 // Returns number of frames in stack, unless there's a corrupt ebp, in which | |
206 // this function will return the number of frames up until the frame with | |
207 // a corrupt ebp. This procedure occurs while thread is suspended, so it should | |
208 // take as little time as possible. | |
209 int RecordStack(mach_port_t thread_handle, | |
210 int max_stack_size, | |
211 const void* instruction_pointers[]) { | |
212 ThreadContext state; | |
213 if (!GetThreadState(thread_handle, &state)) | |
214 return 0; | |
215 | |
216 StackFrame frame; | |
217 frame.prev = reinterpret_cast<StackFrame*>(state.rbp); | |
218 int i = 0; | |
219 for (; i < max_stack_size; i++) { | |
220 if (!SafeJump(frame.prev, &frame) || !frame.return_addr) | |
221 return i; | |
222 instruction_pointers[i] = reinterpret_cast<const void*>(frame.return_addr); | |
223 } | |
224 return i; | |
225 } | |
226 | |
227 // Adds library referenced by |instruction_pointer| to |sample| and |modules|. | |
228 // Records library's (1) filepath, (2) base address, and (3) unique ID. | |
229 void AddModule(StackSamplingProfiler::Sample* sample, | |
230 std::vector<StackSamplingProfiler::Module>* modules, | |
231 std::map<void*, size_t>* module_to_index, | |
232 const void* instruction_pointer) { | |
233 char filepath[PATH_MAX + 1]; | |
234 Dl_info inf; | |
235 dladdr(instruction_pointer, &inf); | |
236 auto loc = module_to_index->find(inf.dli_fbase); | |
237 if (loc == module_to_index->end()) { | |
238 realpath(inf.dli_fname, filepath); | |
239 std::string unique_id; | |
240 GetUniqueId(inf.dli_fname, &unique_id); | |
Robert Sesek
2015/09/14 18:55:54
Touching disk here is going to be a huge hit on so
| |
241 StackSamplingProfiler::Module module(inf.dli_fbase, unique_id, | |
242 base::FilePath(filepath)); | |
243 modules->push_back(module); | |
244 | |
245 loc = module_to_index->insert( | |
246 std::make_pair(inf.dli_fbase, modules->size() - 1)) | |
247 .first; | |
248 } | |
249 sample->push_back( | |
250 StackSamplingProfiler::Frame(instruction_pointer, loc->second)); | |
251 } | |
252 | |
253 // Fills |sample| with Frames and Modules referenced by |instruction_pointers|. | |
254 void FillSample(const void* instruction_pointers[], | |
255 std::vector<StackSamplingProfiler::Module>* modules, | |
256 int stack_depth, | |
257 StackSamplingProfiler::Sample* sample) { | |
258 std::map<void*, size_t> module_index; | |
259 for (int i = 0; i < stack_depth; i++) { | |
260 AddModule(sample, modules, &module_index, instruction_pointers[i]); | |
261 } | |
262 } | |
263 | |
264 // NativeStackSamplerMac ------------------------------------------------------ | |
265 | |
266 class NativeStackSamplerMac : public NativeStackSampler { | |
Robert Sesek
2015/09/14 18:55:54
This should move into native_stack_sampler_mac.cc
| |
267 public: | |
268 explicit NativeStackSamplerMac(pid_t thread_handle); | |
269 ~NativeStackSamplerMac() override; | |
270 | |
271 // StackSamplingProfiler::NativeStackSampler: | |
272 void ProfileRecordingStarting( | |
273 std::vector<StackSamplingProfiler::Module>* modules) override; | |
274 void RecordStackSample(StackSamplingProfiler::Sample* sample) override; | |
275 void ProfileRecordingStopped() override; | |
276 | |
277 private: | |
278 mach_port_t thread_handle_; | |
279 | |
280 // Weak. Points to the modules associated with the profile being recorded | |
281 // between ProfileRecordingStarting() and ProfileRecordingStopped(). | |
282 std::vector<StackSamplingProfiler::Module>* current_modules_; | |
283 | |
284 DISALLOW_COPY_AND_ASSIGN(NativeStackSamplerMac); | |
285 }; | |
286 | |
287 // The PlatformThreadId of the given thread is actually type mach_port_t. | |
288 // (See base/threading/platform_thread_posix.cc:128) | |
Robert Sesek
2015/09/14 18:55:54
This seems like a bug. May want to file one for th
| |
289 NativeStackSamplerMac::NativeStackSamplerMac(pid_t thread_handle) | |
290 : thread_handle_(static_cast<mach_port_t>(thread_handle)) {} | |
291 | |
292 NativeStackSamplerMac::~NativeStackSamplerMac() {} | |
293 | |
294 void NativeStackSamplerMac::ProfileRecordingStarting( | |
295 std::vector<StackSamplingProfiler::Module>* modules) { | |
296 current_modules_ = modules; | |
297 } | |
298 | |
299 void NativeStackSamplerMac::RecordStackSample( | |
300 StackSamplingProfiler::Sample* sample) { | |
301 DCHECK(current_modules_); | |
302 const int max_stack_size = 64; | |
303 const void* instruction_pointers[max_stack_size] = {0}; | |
304 | |
305 thread_suspend(thread_handle_); | |
306 int stack_depth = | |
307 RecordStack(thread_handle_, max_stack_size, instruction_pointers); | |
308 thread_resume(thread_handle_); | |
309 FillSample(instruction_pointers, current_modules_, stack_depth, sample); | |
310 } | |
311 | |
312 void NativeStackSamplerMac::ProfileRecordingStopped() { | |
313 current_modules_ = nullptr; | |
314 } | |
315 | |
316 } // namespace | |
317 | |
9 scoped_ptr<NativeStackSampler> NativeStackSampler::Create( | 318 scoped_ptr<NativeStackSampler> NativeStackSampler::Create( |
10 PlatformThreadId thread_id) { | 319 PlatformThreadId thread_id) { |
320 #if defined(OS_MACOSX) | |
321 return scoped_ptr<NativeStackSampler>(new NativeStackSamplerMac(thread_id)); | |
322 #endif | |
11 return scoped_ptr<NativeStackSampler>(); | 323 return scoped_ptr<NativeStackSampler>(); |
12 } | 324 } |
13 | 325 |
14 } // namespace base | 326 } // namespace base |
OLD | NEW |