OLD | NEW |
---|---|
(Empty) | |
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 | |
3 // found in the LICENSE file. | |
4 | |
5 #include <dlfcn.h> | |
6 #include <mach/mach.h> | |
7 #include <mach/thread_status.h> | |
8 #include <mach-o/swap.h> | |
9 #include <stdlib.h> | |
10 | |
11 #include <map> | |
12 | |
13 #include "base/profiler/native_stack_sampler.h" | |
14 #include "base/strings/string_number_conversions.h" | |
15 | |
16 namespace base { | |
17 | |
18 namespace { | |
19 | |
20 static const int kUUIDLengthBytes = 16; | |
Mike Wittman
2015/09/15 16:00:15
static is unnecessary when declaring in an unnamed
sydli
2015/09/15 18:01:05
Done.
| |
21 | |
22 // Copy of x86_64 thread context structure from x86_thread_state64_t type. | |
23 // Copied struct since fields can have different names on different versions of | |
24 // Darwin. | |
25 struct ThreadContext { | |
26 uint64_t rax; | |
27 uint64_t rbx; | |
28 uint64_t rcx; | |
29 uint64_t rdx; | |
30 uint64_t rdi; | |
31 uint64_t rsi; | |
32 uint64_t rbp; | |
33 uint64_t rsp; | |
34 uint64_t r8; | |
35 uint64_t r9; | |
36 uint64_t r10; | |
37 uint64_t r11; | |
38 uint64_t r12; | |
39 uint64_t r13; | |
40 uint64_t r14; | |
41 uint64_t r15; | |
42 uint64_t rip; | |
43 uint64_t rflags; | |
44 uint64_t cs; | |
45 uint64_t fs; | |
46 uint64_t gs; | |
47 }; | |
48 | |
49 // Struct for stack walking (represents stack state on any function call that | |
50 // pushes a frame pointer). | |
51 struct StackFrame { | |
52 // Pointer to caller's frame (rbp). | |
53 uintptr_t prev; | |
54 // Address in caller for callee to return to. | |
55 uintptr_t return_addr; | |
56 }; | |
57 | |
58 // Overwrites |dst| with StackFrame referenced in |src|. Returns true if stack | |
59 // jump was successful. Failure implies address in |src| is corrupt. | |
60 bool SafeJump(uintptr_t src, StackFrame* dst) { | |
Robert Sesek
2015/09/15 13:28:19
How is this a "jump"? This seems more like ReadSta
sydli
2015/09/15 18:01:05
Changed function name + comments.
| |
61 vm_size_t ignored_bytes_copied = 0; | |
62 return vm_read_overwrite(mach_task_self(), static_cast<vm_address_t>(src), | |
63 static_cast<vm_size_t>(sizeof(StackFrame)), | |
64 reinterpret_cast<vm_address_t>(dst), | |
65 &ignored_bytes_copied) == KERN_SUCCESS; | |
66 } | |
67 | |
68 // Functions related to retrieving Mach-O Identifer | |
69 // | |
70 // These functions were cannibalized from Mach-O Identifier procedures found in | |
71 // breakpad/src/common/mac. Support for non-X86_64 architectures and MD5 IDs | |
72 // were removed to simplify the code. | |
73 // | |
74 // Thus, this walker only supports modules built for x86_64 architectures. | |
75 // There are two such types of Mach-O modules: x86_64, which is built only | |
76 // for x86_64, and FAT64, which are built for multiple 64-bit cpu architectures. | |
77 // Brief demonstration of the two Mach-O header formats: (more information | |
78 // can be found in the OS X ABI Mach-O File Format Reference). | |
79 // | |
80 // x86_64 Mach-O FAT64 Mach-O | |
Robert Sesek
2015/09/15 13:28:19
I see that another reviewer asked for it, but I'm
sydli
2015/09/15 18:01:04
Done.
| |
81 // ------------- ---------- | |
82 // + <-- |module_addr|, X86 header + <-- |module_addr|, fat_header | |
83 // | MH_MAGIC_64 or MH_CIGAM_64 | FAT_MAGIC or FAT_CIGAM | |
84 // . . | |
85 // . . | |
86 // . | <-- X86 header | |
87 // . | MH_MAGIC_64 or MH_CIGAM_64 | |
88 // . . | |
89 | |
90 // Returns offset in bytes where the x86_64 header is located in binary | |
91 // loaded at |module_addr|. Returns 0 if |module_addr| is not a valid FAT | |
92 // Mach-O binary or has not been built for x86_64. | |
93 off_t GetMach64HeaderOffset(const void* module_addr) { | |
94 fat_header hdr; | |
95 memcpy(&hdr, module_addr, sizeof(hdr)); | |
Robert Sesek
2015/09/15 13:28:19
Do you actually need to copy data in these routine
sydli
2015/09/15 18:01:05
No-- not with the OSSwap procedure. Changed code t
| |
96 if (hdr.magic != FAT_MAGIC && hdr.magic != FAT_CIGAM) | |
97 return 0; | |
98 | |
99 if (hdr.magic == FAT_CIGAM) | |
100 swap_fat_header(&hdr, NXHostByteOrder()); | |
101 | |
102 // Search all FAT architectures for x86_64. | |
103 off_t offset = sizeof(hdr); | |
104 fat_arch arch; | |
105 for (uint32_t i = 0; i < hdr.nfat_arch; ++i) { | |
106 memcpy(&arch, reinterpret_cast<const char*>(module_addr) + offset, | |
107 sizeof(arch)); | |
108 | |
109 if (NXHostByteOrder() != NX_BigEndian) | |
110 swap_fat_arch(&arch, 1, NXHostByteOrder()); | |
111 | |
112 if (arch.cputype == CPU_TYPE_X86_64) | |
113 return arch.offset; | |
114 offset += sizeof(arch); | |
115 } | |
116 return 0; | |
117 } | |
118 | |
119 // Returns true if Mach-O binary at |module_addr| was built specifically for | |
120 // x86_64 cpu architecture. | |
121 bool IsX64Header(const void* module_addr) { | |
122 mach_header_64 hdr; | |
123 memcpy(&hdr, module_addr, sizeof(hdr)); | |
124 if (hdr.magic != MH_MAGIC_64 && hdr.magic != MH_CIGAM_64) | |
125 return false; | |
126 if (hdr.magic == MH_CIGAM_64) | |
127 swap_mach_header_64(&hdr, NXHostByteOrder()); | |
128 | |
129 return hdr.cputype == CPU_TYPE_X86_64; | |
130 } | |
131 | |
132 // Fills |id| with the UUID of the x86_64 Mach-O binary loaded at |module_addr|. | |
133 // |offset| is the offset in bytes into |module_addr| where the x86_64 header | |
134 // is located. |offset| is only relevant if binary is FAT and contains | |
135 // multiple architecture headers. Returns false if header is malformed or the | |
136 // header does not specify the UUID load command. | |
137 bool GetX64UUIDAt(const void* module_addr, unsigned char* id, off_t offset) { | |
138 mach_header_64 hdr; | |
139 if (!memcpy(&hdr, reinterpret_cast<const char*>(module_addr) + offset, | |
140 sizeof(hdr)) || | |
141 (hdr.magic != MH_MAGIC_64 && hdr.magic != MH_CIGAM_64)) { | |
142 return false; | |
143 } | |
144 | |
145 bool swap = hdr.magic == MH_CIGAM_64; | |
146 if (swap) | |
147 swap_mach_header_64(&hdr, NXHostByteOrder()); | |
148 | |
149 // Search all load commands for UUID command. | |
150 offset += sizeof(hdr); | |
151 load_command cmd; | |
152 for (uint32_t i = 0; i < hdr.ncmds; ++i) { | |
153 const void* command_loc = | |
154 reinterpret_cast<const char*>(module_addr) + offset; | |
155 memcpy(&cmd, command_loc, sizeof(cmd)); | |
156 | |
157 if (swap) | |
158 swap_load_command(&cmd, NXHostByteOrder()); | |
159 if (cmd.cmd == LC_UUID) { | |
160 uuid_command uuid_cmd; | |
161 memcpy(&uuid_cmd, command_loc, sizeof(uuid_cmd)); | |
162 if (swap) | |
163 swap_uuid_command(&uuid_cmd, NXHostByteOrder()); | |
164 | |
165 static_assert(sizeof(uuid_cmd.uuid) == kUUIDLengthBytes, | |
166 "UUID field of UUID command should be 16 bytes."); | |
167 memcpy(id, &uuid_cmd.uuid, kUUIDLengthBytes); | |
168 return true; | |
169 } | |
170 offset += cmd.cmdsize; | |
171 } | |
172 return false; | |
173 } | |
174 | |
175 // Fills |id| with Mach-O UUID retrieved from Mach-O binary loaded at | |
176 // |module_addr|. This function returns false if the binary was not built for | |
177 // X86_64 or if UUID cannot be found. | |
178 bool GetUUID(const void* module_addr, unsigned char* id) { | |
179 off_t offset = 0; | |
180 // If module is not x86_64 exclusive, it could be a module that supports | |
181 // multiple architectures. In this case, the appropriate header will be at | |
182 // some non-zero offset. | |
183 if (!IsX64Header(module_addr) && | |
184 !(offset = GetMach64HeaderOffset(module_addr))) | |
185 return false; | |
186 return GetX64UUIDAt(module_addr, id, offset); | |
187 } | |
188 | |
189 // Returns hex encoding of a 16-byte ID for binary loaded at |module_addr|. | |
190 // Returns empty string if UUID cannot be found at |module_addr|. | |
191 std::string GetUniqueId(const void* module_addr) { | |
192 unsigned char id[kUUIDLengthBytes]; | |
193 if (!GetUUID(module_addr, id)) | |
194 return ""; | |
195 return HexEncode(id, kUUIDLengthBytes); | |
196 } | |
197 | |
198 // Functions related to grabbing a stack trace -------------------------------- | |
199 | |
200 // Fills |state| with |target_thread|'s context. | |
201 bool GetThreadContext(thread_act_t target_thread, ThreadContext* state) { | |
202 mach_msg_type_number_t count = | |
203 static_cast<mach_msg_type_number_t>(MACHINE_THREAD_STATE_COUNT); | |
204 return thread_get_state(target_thread, x86_THREAD_STATE64, | |
205 reinterpret_cast<thread_state_t>(state), | |
206 &count) == KERN_SUCCESS; | |
207 } | |
208 | |
209 // Walks |thread_handle|'s stack and fills |instruction_pointers|. | |
210 // Returns number of frames in stack, unless there's a corrupt frame pointer | |
211 // (likely if module compiled with -fno_omit_frame_pointer), in which this | |
212 // function will return the number of frames up until the frame with a corrupt | |
213 // frame pointer. This procedure occurs while thread is suspended, so it should | |
214 // take as little time as possible. | |
215 int RecordStack(mach_port_t thread_handle, | |
216 int max_stack_size, | |
217 uintptr_t instruction_pointers[]) { | |
218 ThreadContext state; | |
219 if (!GetThreadContext(thread_handle, &state)) | |
220 return 0; | |
221 | |
222 StackFrame frame; | |
223 frame.prev = state.rbp; | |
224 int i = 0; | |
225 for (; i < max_stack_size; i++) { | |
226 // Three cases for end-of-stack condition: | |
227 // 1) We tried to jump off the stack. | |
Robert Sesek
2015/09/15 13:28:19
Avoid using "we" and other personal pronouns in co
sydli
2015/09/15 18:01:05
SafeReadFrame should only allow us to read from a
| |
228 // 2) We jumped to a lower address. | |
Robert Sesek
2015/09/15 13:28:19
"The next recovered rsp is not lower than the prev
sydli
2015/09/15 18:01:05
Done.
| |
229 // 3) We reached a return (instruction) address of 0. | |
230 uintptr_t old_esp = frame.prev; | |
Robert Sesek
2015/09/15 13:28:19
Since this is x86_64, this isn't esp, it's rsp.
sydli
2015/09/15 18:01:05
Oops-- my bad. Updated name.
| |
231 if (!SafeJump(frame.prev, &frame) || frame.prev < old_esp || | |
232 !frame.return_addr) { | |
233 return i; | |
234 } | |
235 instruction_pointers[i] = frame.return_addr; | |
236 } | |
237 return i; | |
238 } | |
239 | |
240 // NativeStackSamplerMac ------------------------------------------------------ | |
241 | |
242 class NativeStackSamplerMac : public NativeStackSampler { | |
243 public: | |
244 explicit NativeStackSamplerMac(pid_t thread_handle); | |
245 ~NativeStackSamplerMac() override; | |
246 | |
247 // StackSamplingProfiler::NativeStackSampler: | |
248 void ProfileRecordingStarting( | |
249 std::vector<StackSamplingProfiler::Module>* modules) override; | |
250 void RecordStackSample(StackSamplingProfiler::Sample* sample) override; | |
251 void ProfileRecordingStopped() override; | |
252 | |
253 private: | |
254 // Adds module represented by |instruction_pointer| into | |
255 // |sample| and |modules|. | |
256 void AddModule(StackSamplingProfiler::Sample* sample, | |
257 std::vector<StackSamplingProfiler::Module>* modules, | |
258 const uintptr_t instruction_pointer); | |
Mike Wittman
2015/09/15 16:00:15
input arguments should appear before output argume
sydli
2015/09/15 18:01:05
Done.
| |
259 | |
260 // Copies the stack information represented by |instruction_pointers| into | |
261 // |sample| and |modules|. | |
262 void FillSample(const uintptr_t instruction_pointers[], | |
263 int stack_depth, | |
264 std::vector<StackSamplingProfiler::Module>* modules, | |
265 StackSamplingProfiler::Sample* sample); | |
Mike Wittman
2015/09/15 16:00:15
|sample| can go before |modules|, to be consistent
sydli
2015/09/15 18:01:05
Done.
| |
266 | |
267 // Mach handle for thread being profiled. | |
Robert Sesek
2015/09/15 13:28:19
Weak or strong right reference, here?
sydli
2015/09/15 18:01:05
Weak. updated comment.
| |
268 mach_port_t thread_handle_; | |
269 | |
270 // Weak. Points to the modules associated with the profile being recorded | |
271 // between ProfileRecordingStarting() and ProfileRecordingStopped(). | |
272 std::vector<StackSamplingProfiler::Module>* current_modules_; | |
273 | |
274 // Maps a module handle to the corresponding Module's index within | |
275 // current_modules_. | |
276 std::map<const void*, size_t> profile_module_index_; | |
277 | |
278 DISALLOW_COPY_AND_ASSIGN(NativeStackSamplerMac); | |
279 }; | |
280 | |
281 // The PlatformThreadId of the given thread is actually a typedef of | |
282 // mach_port_t. | |
283 // (base/threading/platform_thread_posix.cc:128) | |
284 NativeStackSamplerMac::NativeStackSamplerMac(pid_t thread_handle) | |
285 : thread_handle_(static_cast<mach_port_t>(thread_handle)), | |
286 current_modules_(nullptr) {} | |
287 | |
288 NativeStackSamplerMac::~NativeStackSamplerMac() {} | |
289 | |
290 void NativeStackSamplerMac::ProfileRecordingStarting( | |
291 std::vector<StackSamplingProfiler::Module>* modules) { | |
292 current_modules_ = modules; | |
293 profile_module_index_.clear(); | |
294 } | |
295 | |
296 void NativeStackSamplerMac::RecordStackSample( | |
297 StackSamplingProfiler::Sample* sample) { | |
298 DCHECK(current_modules_); | |
299 | |
300 const int max_stack_size = 64; | |
301 uintptr_t instruction_pointers[max_stack_size] = {0}; | |
302 | |
303 thread_suspend(thread_handle_); | |
304 int stack_depth = | |
305 RecordStack(thread_handle_, max_stack_size, instruction_pointers); | |
306 thread_resume(thread_handle_); | |
307 FillSample(instruction_pointers, stack_depth, current_modules_, sample); | |
308 } | |
309 | |
310 void NativeStackSamplerMac::ProfileRecordingStopped() { | |
311 current_modules_ = nullptr; | |
312 } | |
313 | |
314 // Adds library referenced by |instruction_pointer| to |sample| and |modules|. | |
315 // Records library's (1) filepath, (2) base address, and (3) unique ID. | |
316 void NativeStackSamplerMac::AddModule( | |
Mike Wittman
2015/09/15 16:00:15
This function feels like it's doing too many thing
sydli
2015/09/15 18:01:05
Split the function into CopyToSample and Getmodule
| |
317 StackSamplingProfiler::Sample* sample, | |
318 std::vector<StackSamplingProfiler::Module>* modules, | |
319 const uintptr_t instruction_pointer) { | |
320 char filepath[PATH_MAX + 1]; | |
321 Dl_info inf; | |
322 if (!dladdr(reinterpret_cast<const void*>(instruction_pointer), &inf)) { | |
323 sample->push_back(StackSamplingProfiler::Frame( | |
324 reinterpret_cast<const void*>(instruction_pointer), | |
325 StackSamplingProfiler::Frame::kUnknownModuleIndex)); | |
326 return; | |
327 } | |
328 auto module_index = profile_module_index_.find(inf.dli_fbase); | |
329 if (module_index == profile_module_index_.end()) { | |
330 realpath(inf.dli_fname, filepath); | |
Robert Sesek
2015/09/15 13:28:19
realpath will still touch disk. Is this necessary?
sydli
2015/09/15 18:01:05
Suppose not; this just means ./ or ../ won't neces
| |
331 StackSamplingProfiler::Module module( | |
332 inf.dli_fbase, GetUniqueId(inf.dli_fbase), base::FilePath(filepath)); | |
333 modules->push_back(module); | |
334 module_index = profile_module_index_.insert( | |
335 std::make_pair(inf.dli_fbase, modules->size() - 1)) .first; | |
336 } | |
337 sample->push_back(StackSamplingProfiler::Frame( | |
338 reinterpret_cast<const void*>(instruction_pointer), | |
339 module_index->second)); | |
340 } | |
341 | |
342 // Fills |sample| with Frames and Modules referenced by |instruction_pointers|. | |
343 void NativeStackSamplerMac::FillSample( | |
344 const uintptr_t instruction_pointers[], | |
345 int stack_depth, | |
346 std::vector<StackSamplingProfiler::Module>* modules, | |
347 StackSamplingProfiler::Sample* sample) { | |
348 for (int i = 0; i < stack_depth; i++) { | |
Mike Wittman
2015/09/15 16:00:15
nit: no braces
sydli
2015/09/15 18:01:05
Done.
| |
349 AddModule(sample, modules, instruction_pointers[i]); | |
350 } | |
351 } | |
352 | |
353 } // namespace | |
354 | |
355 scoped_ptr<NativeStackSampler> NativeStackSampler::Create( | |
356 PlatformThreadId thread_id) { | |
Robert Sesek
2015/09/15 13:28:19
#if defined(__i386__)
NOTIMPLEMENTED() << "This cl
Mike Wittman
2015/09/15 16:00:15
Just returning the null pointer is sufficient. The
sydli
2015/09/15 18:01:05
Done.
| |
357 return scoped_ptr<NativeStackSampler>(new NativeStackSamplerMac(thread_id)); | |
358 } | |
359 | |
360 } // namespace base | |
OLD | NEW |