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 <stdio.h> | |
10 #include <stdlib.h> | |
11 | |
12 #include <map> | |
13 | |
14 #include "base/logging.h" | |
15 #include "base/md5.h" | |
16 #include "base/profiler/native_stack_sampler.h" | |
17 #include "base/strings/string_number_conversions.h" | |
18 #include "base/time/time.h" | |
19 | |
20 namespace base { | |
21 | |
22 namespace { | |
23 | |
robliao
2015/09/14 21:58:17
Nit: Remove extra line.
sydli
2015/09/14 23:00:13
Done.
| |
24 | |
25 // Copy of x86_64 thread context structure from x86_thread_state64_t type. | |
26 // Copied struct since fields can have different names on different versions of | |
27 // Darwin. | |
28 struct ThreadContext { | |
29 uint64_t rax; | |
30 uint64_t rbx; | |
31 uint64_t rcx; | |
32 uint64_t rdx; | |
33 uint64_t rdi; | |
34 uint64_t rsi; | |
35 uint64_t rbp; | |
36 uint64_t rsp; | |
37 uint64_t r8; | |
38 uint64_t r9; | |
39 uint64_t r10; | |
40 uint64_t r11; | |
41 uint64_t r12; | |
42 uint64_t r13; | |
43 uint64_t r14; | |
44 uint64_t r15; | |
45 uint64_t rip; | |
46 uint64_t rflags; | |
47 uint64_t cs; | |
48 uint64_t fs; | |
49 uint64_t gs; | |
50 }; | |
51 | |
52 // Struct for stack walking (represents stack state on any function call that | |
53 // pushes a frame pointer). | |
54 struct StackFrame { | |
55 // Pointer to caller's frame (rbp). | |
56 struct StackFrame* prev; | |
57 // Address in caller for callee to return to. | |
58 uintptr_t return_addr; | |
59 }; | |
60 | |
61 // Overwrites |dst| with StackFrame referenced in |src|. Returns true if stack | |
62 // jump was successful. Failure implies address in |src| is corrupt. | |
63 bool SafeJump(const StackFrame* src, StackFrame* dst) { | |
robliao
2015/09/14 21:58:17
Can src be a const StackFrame& ?
sydli
2015/09/14 23:00:13
Don't think so; I pass in frame.prev (which is a S
| |
64 vm_size_t bytesCopied = 0; | |
robliao
2015/09/14 21:58:16
ignored_bytes_copied
sydli
2015/09/14 23:00:13
Done.
| |
65 return vm_read_overwrite(mach_task_self(), (vm_address_t)src, | |
66 (vm_size_t)sizeof(StackFrame), (vm_address_t)dst, | |
67 &bytesCopied) == KERN_SUCCESS; | |
68 } | |
69 | |
70 // Functions related to retrieving Mach-O Identifer ---------------------------- | |
robliao
2015/09/14 21:58:17
Remove trailing -'s
sydli
2015/09/14 23:00:13
Done.
| |
71 // | |
72 // These functions were cannibalized from Mach-O Identifier procedures found in | |
73 // breakpad/src/common/mac. Support for non-X86_64 architectures and MD5 IDs | |
74 // were removed to simplify the code. | |
75 | |
76 // Returns offset in bytes where the x86_64 header is located in binary | |
77 // loaded at |module_addr|. Returns 0 if |module_addr| is not a valid FAT | |
78 // Mach-O binary or has not been built for x86_64. | |
79 off_t GetMach64HeaderOffset(const void* module_addr) { | |
80 fat_header hdr; | |
81 memcpy(&hdr, module_addr, sizeof(hdr)); | |
robliao
2015/09/14 21:58:17
Safety: Where do we get the assurance that module_
sydli
2015/09/14 23:00:13
This is an assumption we make since |module_addr|
robliao
2015/09/14 23:28:05
The sections don't appear to have size info, so I
| |
82 if (hdr.magic != FAT_MAGIC && hdr.magic != FAT_CIGAM) | |
83 return 0; | |
84 | |
85 if (hdr.magic == FAT_CIGAM) | |
86 swap_fat_header(&hdr, NXHostByteOrder()); | |
87 | |
88 // Search all FAT architectures for x86_64. | |
89 off_t offset = sizeof(hdr); | |
90 fat_arch arch; | |
91 for (uint32_t i = 0; i < hdr.nfat_arch; ++i) { | |
92 memcpy(&arch, (const char*)module_addr + offset, sizeof(arch)); | |
robliao
2015/09/14 21:58:17
Would casting module_addr+offset to a fat_arch[] w
sydli
2015/09/14 23:00:13
module_addr has to be a pointer of a byte-wide typ
robliao
2015/09/14 23:28:05
const char* is likely what you want since you're o
| |
93 | |
94 if (NXHostByteOrder() != NX_BigEndian) | |
95 swap_fat_arch(&arch, 1, NXHostByteOrder()); | |
96 | |
97 if (arch.cputype == CPU_TYPE_X86_64) | |
98 return arch.offset; | |
99 offset += sizeof(arch); | |
100 } | |
101 return 0; | |
102 } | |
103 | |
104 // Returns true if Mach-O binary at |module_addr| was built specifically for | |
105 // x86_64 cpu architecture. | |
106 bool IsX64Header(const void* module_addr) { | |
107 mach_header_64 hdr; | |
108 memcpy(&hdr, module_addr, sizeof(hdr)); | |
109 if (hdr.magic != MH_MAGIC_64 && hdr.magic != MH_CIGAM_64) | |
110 return false; | |
111 if (hdr.magic == MH_CIGAM_64) | |
112 swap_mach_header_64(&hdr, NXHostByteOrder()); | |
113 | |
114 if (hdr.cputype != CPU_TYPE_X86_64) | |
robliao
2015/09/14 21:58:16
return hdr.cputype == CPU_TYPE_X86_64;
sydli
2015/09/14 23:00:13
Done.
| |
115 return false; | |
116 return true; | |
117 } | |
118 | |
119 // Fills |id| with UUID of x86_64 Mach-O binary loaded at |module_addr|. | |
robliao
2015/09/14 21:58:17
Nit: {the} UUID of {the} x86_64
sydli
2015/09/14 23:00:13
Done.
| |
120 // |offset| is the offset in bytes into |module_addr| where the x86_64 header | |
robliao
2015/09/14 21:58:17
It might be instructive to do some ASCII Art towar
sydli
2015/09/14 23:00:13
This is only accurate for FAT Mach-O headers. Upda
robliao
2015/09/14 23:28:05
The link is good, but the diagram is more helpful
sydli
2015/09/15 00:20:13
Ok.
| |
121 // is located. |offset| should be non-zero if binary is FAT and contains | |
122 // multiple architecture headers. Returns false if header is malformed or the | |
123 // header does not specify the UUID load command. | |
124 bool GetX64UUIDAt(const void* module_addr, unsigned char id[16], off_t offset) { | |
125 mach_header_64 hdr; | |
126 if (!memcpy(&hdr, (const char*)module_addr + offset, sizeof(hdr)) || | |
127 (hdr.magic != MH_MAGIC_64 && hdr.magic != MH_CIGAM_64)) | |
128 return false; | |
robliao
2015/09/14 21:58:17
When a conditional spans multiple lines, use brace
sydli
2015/09/14 23:00:13
Done.
robliao
2015/09/14 23:28:05
I assume this is the one you forgot?
sydli
2015/09/15 00:20:13
Yep! Added it in new patchset.
| |
129 | |
130 bool swap = hdr.magic == MH_CIGAM_64; | |
131 if (swap) | |
132 swap_mach_header_64(&hdr, NXHostByteOrder()); | |
133 | |
134 // Search all load commands for UUID command. | |
135 offset += sizeof(hdr); | |
136 for (uint32_t i = 0; i < hdr.ncmds; ++i) { | |
137 load_command cmd; | |
robliao
2015/09/14 21:58:17
Move cmd outside the loop to avoid constructing th
sydli
2015/09/14 23:00:13
Done.
| |
138 memcpy(&cmd, (const char*)module_addr + offset, sizeof(cmd)); | |
robliao
2015/09/14 21:58:16
Could represent module_addr + offset as a temp val
sydli
2015/09/14 23:00:13
Done.
| |
139 | |
140 if (swap) | |
141 swap_load_command(&cmd, NXHostByteOrder()); | |
142 if (cmd.cmd == LC_UUID) { | |
143 uuid_command uuid_cmd; | |
144 memcpy(&uuid_cmd, (const char*)module_addr + offset, sizeof(uuid_cmd)); | |
145 if (swap) | |
146 swap_uuid_command(&uuid_cmd, NXHostByteOrder()); | |
147 memcpy(id, &uuid_cmd, sizeof(uuid_cmd)); | |
robliao
2015/09/14 21:58:17
Safety: This copy probably should be no longer tha
sydli
2015/09/14 23:00:13
Done.
| |
148 return true; | |
149 } | |
150 offset += cmd.cmdsize; | |
151 } | |
152 return false; | |
153 } | |
154 | |
155 // Fills |id| with Mach-O UUID retrieved from Mach-O binary loaded at | |
156 // |module_addr|. This function returns false if the binary was not built for | |
157 // X86_64 or if UUID cannot be found. | |
158 bool GetUUID(const void* module_addr, unsigned char id[16]) { | |
159 off_t offset = 0; | |
160 if (!IsX64Header(module_addr) && | |
161 !(offset = GetMach64HeaderOffset(module_addr))) | |
162 return false; | |
163 return GetX64UUIDAt(module_addr, id, offset); | |
164 } | |
165 | |
166 // Returns hex encoding of a 16-byte ID for binary loaded at |module_addr|. | |
167 // Returns empty string if UUID cannot be found at |module_addr|. | |
168 std::string GetUniqueId(const void* module_addr) { | |
169 unsigned char id[16]; | |
170 if (!GetUUID(module_addr, id)) | |
171 return ""; | |
172 return HexEncode(id, 16); | |
173 } | |
174 | |
175 // Functions related to grabbing a stack trace -------------------------------- | |
176 | |
177 // Fills |state| with |target_thread|'s context. | |
178 bool GetThreadState(thread_act_t target_thread, ThreadContext* state) { | |
179 mach_msg_type_number_t count = | |
180 static_cast<mach_msg_type_number_t>(MACHINE_THREAD_STATE_COUNT); | |
181 return thread_get_state(target_thread, x86_THREAD_STATE64, | |
182 reinterpret_cast<thread_state_t>(state), | |
183 &count) == KERN_SUCCESS; | |
184 } | |
185 | |
186 // Walks |thread_handle|'s stack and fills |instruction_pointers|. | |
187 // Returns number of frames in stack, unless there's a corrupt ebp, in which | |
robliao
2015/09/14 21:58:17
corrupt ebp or missing ebp or both?
sydli
2015/09/14 23:00:13
A corrupt base pointer implies that it is missing
| |
188 // this function will return the number of frames up until the frame with | |
189 // a corrupt ebp. This procedure occurs while thread is suspended, so it should | |
190 // take as little time as possible. | |
191 int RecordStack(mach_port_t thread_handle, | |
192 int max_stack_size, | |
robliao
2015/09/14 21:58:17
Maybe instruction_pointers_size to make it obvious
sydli
2015/09/14 23:00:14
Done.
| |
193 const void* instruction_pointers[]) { | |
robliao
2015/09/14 21:58:17
Should this be const uintptr_t?
sydli
2015/09/14 23:00:13
Done. Changed where appropriate.
| |
194 ThreadContext state; | |
195 if (!GetThreadState(thread_handle, &state)) | |
196 return 0; | |
197 | |
198 StackFrame frame; | |
199 frame.prev = reinterpret_cast<StackFrame*>(state.rbp); | |
200 int i = 0; | |
201 for (; i < max_stack_size; i++) { | |
202 // Three cases for end-of-stack condition: | |
203 // 1) We tried to jump off the stack. | |
204 // 2) We jumped to a lower address. | |
205 // 3) We reached a return (instruction) address of 0. | |
206 StackFrame* old_esp = frame.prev; | |
207 if (!SafeJump(frame.prev, &frame) || frame.prev < old_esp || | |
208 !frame.return_addr) | |
209 return i; | |
robliao
2015/09/14 21:58:17
When a conditional spans multiple lines, add brace
sydli
2015/09/14 23:00:13
Done.
| |
210 instruction_pointers[i] = reinterpret_cast<const void*>(frame.return_addr); | |
211 } | |
212 return i; | |
213 } | |
214 | |
215 // Adds library referenced by |instruction_pointer| to |sample| and |modules|. | |
216 // Records library's (1) filepath, (2) base address, and (3) unique ID. | |
217 void AddModule(StackSamplingProfiler::Sample* sample, | |
218 std::vector<StackSamplingProfiler::Module>* modules, | |
219 std::map<void*, size_t>* module_to_index, | |
220 const void* instruction_pointer) { | |
221 char filepath[PATH_MAX + 1]; | |
222 Dl_info inf; | |
223 dladdr(instruction_pointer, &inf); | |
224 auto loc = module_to_index->find(inf.dli_fbase); | |
robliao
2015/09/14 21:58:16
What is loc?
sydli
2015/09/14 23:00:13
Pair result from module -> index map. Updated name
| |
225 if (loc == module_to_index->end()) { | |
226 realpath(inf.dli_fname, filepath); | |
227 StackSamplingProfiler::Module module( | |
228 inf.dli_fbase, GetUniqueId(inf.dli_fbase), base::FilePath(filepath)); | |
229 modules->push_back(module); | |
230 | |
231 loc = module_to_index->insert( | |
robliao
2015/09/14 21:58:17
Linebreak after the -> and reflow.
sydli
2015/09/14 23:00:13
Done.
| |
232 std::make_pair(inf.dli_fbase, modules->size() - 1)) | |
233 .first; | |
234 } | |
235 sample->push_back( | |
236 StackSamplingProfiler::Frame(instruction_pointer, loc->second)); | |
237 } | |
238 | |
239 // Fills |sample| with Frames and Modules referenced by |instruction_pointers|. | |
240 void FillSample(const void* instruction_pointers[], | |
241 std::vector<StackSamplingProfiler::Module>* modules, | |
242 int stack_depth, | |
243 StackSamplingProfiler::Sample* sample) { | |
244 std::map<void*, size_t> module_index; | |
245 for (int i = 0; i < stack_depth; i++) { | |
246 AddModule(sample, modules, &module_index, instruction_pointers[i]); | |
247 } | |
248 } | |
249 | |
250 // NativeStackSamplerMac ------------------------------------------------------ | |
251 | |
252 class NativeStackSamplerMac : public NativeStackSampler { | |
253 public: | |
254 explicit NativeStackSamplerMac(pid_t thread_handle); | |
255 ~NativeStackSamplerMac() override; | |
256 | |
257 // StackSamplingProfiler::NativeStackSampler: | |
258 void ProfileRecordingStarting( | |
259 std::vector<StackSamplingProfiler::Module>* modules) override; | |
260 void RecordStackSample(StackSamplingProfiler::Sample* sample) override; | |
261 void ProfileRecordingStopped() override; | |
262 | |
263 private: | |
264 mach_port_t thread_handle_; | |
265 | |
266 // Weak. Points to the modules associated with the profile being recorded | |
267 // between ProfileRecordingStarting() and ProfileRecordingStopped(). | |
268 std::vector<StackSamplingProfiler::Module>* current_modules_; | |
269 | |
270 DISALLOW_COPY_AND_ASSIGN(NativeStackSamplerMac); | |
271 }; | |
272 | |
273 // The PlatformThreadId of the given thread is actually a typedef of | |
274 // mach_port_t. | |
275 // (base/threading/platform_thread_posix.cc:128) | |
276 NativeStackSamplerMac::NativeStackSamplerMac(pid_t thread_handle) | |
277 : thread_handle_(static_cast<mach_port_t>(thread_handle)) {} | |
278 | |
279 NativeStackSamplerMac::~NativeStackSamplerMac() {} | |
280 | |
281 void NativeStackSamplerMac::ProfileRecordingStarting( | |
282 std::vector<StackSamplingProfiler::Module>* modules) { | |
283 current_modules_ = modules; | |
284 } | |
285 | |
286 void NativeStackSamplerMac::RecordStackSample( | |
287 StackSamplingProfiler::Sample* sample) { | |
288 DCHECK(current_modules_); | |
289 const int max_stack_size = 64; | |
robliao
2015/09/14 21:58:17
MAX_STACK_SIZE. Consider moving this to the top of
sydli
2015/09/14 23:00:13
Done. Made this a static const in NativeStackSampl
| |
290 const void* instruction_pointers[max_stack_size] = {0}; | |
291 | |
292 thread_suspend(thread_handle_); | |
293 int stack_depth = | |
294 RecordStack(thread_handle_, max_stack_size, instruction_pointers); | |
295 thread_resume(thread_handle_); | |
296 FillSample(instruction_pointers, current_modules_, stack_depth, sample); | |
297 } | |
298 | |
299 void NativeStackSamplerMac::ProfileRecordingStopped() { | |
300 current_modules_ = nullptr; | |
301 } | |
302 | |
303 } // namespace | |
304 | |
305 scoped_ptr<NativeStackSampler> NativeStackSampler::Create( | |
306 PlatformThreadId thread_id) { | |
307 #if defined(OS_MACOSX) && !defined(OS_NACL) | |
308 return scoped_ptr<NativeStackSampler>(new NativeStackSamplerMac(thread_id)); | |
309 #endif | |
310 return scoped_ptr<NativeStackSampler>(); | |
311 } | |
312 | |
313 } // namespace base | |
OLD | NEW |