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> | |
Robert Sesek
2015/09/15 20:53:00
#include <libkern/OSByteOrder.h>
sydli
2015/09/15 21:49:42
Done.
| |
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 const int kUUIDLengthBytes = 16; | |
Robert Sesek
2015/09/15 20:53:00
You use sizeof(uuid_t) instead of declaring your o
Robert Sesek
2015/09/15 21:11:37
You *can* use
sydli
2015/09/15 21:49:42
That's helpful. Thanks!
| |
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; | |
Robert Sesek
2015/09/15 20:53:00
naming: base_pointer ?
See the styleguide's remar
sydli
2015/09/15 21:49:42
Done. Expanded the other abbreviations in naming.
| |
54 // Address in caller for callee to return to. | |
55 uintptr_t return_addr; | |
56 }; | |
57 | |
58 // Helper that swaps byte order in |x| if |big| (big-endian) flag is set. | |
59 uint32_t SwapIfBig32(uint32_t x, bool big) { | |
60 if (big) | |
61 return OSSwapBigToHostInt32(x); | |
62 return x; | |
63 } | |
64 | |
65 // Overwrites |dst| with StackFrame referenced in |src|. Returns true if stack | |
66 // read was successful. Failure implies address in |src| is corrupt. | |
67 bool SafeStackFrameRead(uintptr_t src, StackFrame* dst) { | |
68 vm_size_t ignored_bytes_copied = 0; | |
69 return vm_read_overwrite(mach_task_self(), static_cast<vm_address_t>(src), | |
70 static_cast<vm_size_t>(sizeof(StackFrame)), | |
71 reinterpret_cast<vm_address_t>(dst), | |
72 &ignored_bytes_copied) == KERN_SUCCESS; | |
73 } | |
74 | |
75 // Functions related to retrieving Mach-O Identifer | |
76 // | |
77 // These functions were cannibalized from Mach-O Identifier procedures found in | |
Robert Sesek
2015/09/15 20:53:00
Is this comment still true?
sydli
2015/09/15 21:49:42
Guess not! Updated comment.
| |
78 // breakpad/src/common/mac. Support for non-X86_64 architectures and MD5 IDs | |
79 // were removed to simplify the code. | |
80 // | |
81 // This walker only supports modules built for x86_64 architecture. More detail | |
82 // can be found in the OS X ABI Mach-O File Format Reference. | |
83 | |
84 // Returns offset in bytes where the x86_64 header is located in binary | |
85 // loaded at |module_addr|. Returns 0 if |module_addr| is not a valid FAT | |
86 // Mach-O binary or has not been built for x86_64. | |
87 off_t GetMach64HeaderOffset(const void* module_addr) { | |
88 const fat_header* hdr = reinterpret_cast<const fat_header*>(module_addr); | |
Robert Sesek
2015/09/15 20:53:00
naming: header
sydli
2015/09/15 21:49:42
Done for all instances.
| |
89 if (hdr->magic != FAT_MAGIC && hdr->magic != FAT_CIGAM) | |
90 return 0; | |
91 bool swap = hdr->magic == FAT_CIGAM; | |
92 | |
93 // Search all FAT architectures for x86_64. | |
94 const fat_arch* fat_arches = reinterpret_cast<const fat_arch*>( | |
95 reinterpret_cast<const uint8_t*>(module_addr) + sizeof(hdr)); | |
96 for (uint32_t i = 0; i < SwapIfBig32(hdr->nfat_arch, swap); ++i) { | |
Robert Sesek
2015/09/15 20:53:00
The fat header is *always* in big endian. So OSSwa
sydli
2015/09/15 21:49:42
Ok. Is there no such guarantee for other types of
| |
97 const fat_arch& arch = fat_arches[i]; | |
98 if (SwapIfBig32(arch.cputype, swap) == CPU_TYPE_X86_64) | |
99 return SwapIfBig32(arch.offset, swap); | |
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 const mach_header_64* hdr = | |
Robert Sesek
2015/09/15 20:53:00
naming: header
sydli
2015/09/15 21:49:42
Done.
| |
108 reinterpret_cast<const mach_header_64*>(module_addr); | |
109 if (hdr->magic != MH_MAGIC_64 && hdr->magic != MH_CIGAM_64) | |
110 return false; | |
111 bool swap = hdr->magic == MH_CIGAM_64; | |
Robert Sesek
2015/09/15 20:53:00
This is named |swap| in functions but |big| in the
sydli
2015/09/15 21:49:42
Kept function (since it's useful in GetX64UUIDAt),
| |
112 return SwapIfBig32((*hdr).cputype, swap) == CPU_TYPE_X86_64; | |
Robert Sesek
2015/09/15 20:53:00
header->cputype
sydli
2015/09/15 21:49:42
Done.
| |
113 } | |
114 | |
115 // Fills |id| with the UUID of the x86_64 Mach-O binary loaded at |module_addr|. | |
116 // |offset| is the offset in bytes into |module_addr| where the x86_64 header | |
117 // is located. |offset| is only relevant if binary is FAT and contains | |
118 // multiple architecture headers. Returns false if header is malformed or the | |
119 // header does not specify the UUID load command. | |
120 bool GetX64UUIDAt(const void* module_addr, unsigned char* id, off_t offset) { | |
121 const mach_header_64* hdr = reinterpret_cast<const mach_header_64*>( | |
Robert Sesek
2015/09/15 20:53:00
naming: header
sydli
2015/09/15 21:49:42
Done.
| |
122 reinterpret_cast<const uint8_t*>(module_addr) + offset); | |
123 if (hdr->magic != MH_MAGIC_64 && hdr->magic != MH_CIGAM_64) | |
124 return false; | |
125 bool swap = hdr->magic == MH_CIGAM_64; | |
126 | |
127 // Search all load commands for UUID command. | |
128 offset += sizeof(mach_header_64); | |
129 for (uint32_t i = 0; i < SwapIfBig32(hdr->ncmds, swap); ++i) { | |
130 const load_command* cmd = reinterpret_cast<const load_command*>( | |
131 reinterpret_cast<const uint8_t*>(module_addr) + offset); | |
132 if (SwapIfBig32(cmd->cmd, swap) == LC_UUID) { | |
133 const uuid_command* uuid_cmd = reinterpret_cast<const uuid_command*>(cmd); | |
134 static_assert(sizeof(uuid_cmd->uuid) == kUUIDLengthBytes, | |
Robert Sesek
2015/09/15 20:53:00
Using sizeof(uuid_t) would then mean removing this
sydli
2015/09/15 21:49:43
Done.
robliao
2015/09/16 01:41:45
I might be looking at the wrong header, but it app
Robert Sesek
2015/09/17 22:06:56
Fair point. Keeping the static_assert would be goo
sydli
2015/09/18 20:29:20
Ah-- makes sense. Put it back.
| |
135 "UUID field of UUID command should be 16 bytes."); | |
136 memcpy(id, &uuid_cmd->uuid, kUUIDLengthBytes); | |
137 return true; | |
138 } | |
139 offset += SwapIfBig32(cmd->cmdsize, swap); | |
140 } | |
141 return false; | |
142 } | |
143 | |
144 // Fills |id| with Mach-O UUID retrieved from Mach-O binary loaded at | |
145 // |module_addr|. This function returns false if the binary was not built for | |
146 // X86_64 or if UUID cannot be found. | |
147 bool GetUUID(const void* module_addr, unsigned char* id) { | |
148 off_t offset = 0; | |
149 // If module is not x86_64 exclusive, it could be a module that supports | |
150 // multiple architectures. In this case, the appropriate header will be at | |
151 // some non-zero offset. | |
152 if (!IsX64Header(module_addr) && | |
153 !(offset = GetMach64HeaderOffset(module_addr))) | |
Robert Sesek
2015/09/15 20:53:00
nit: needs braces since it is multi-line
sydli
2015/09/15 21:49:42
Done.
| |
154 return false; | |
155 return GetX64UUIDAt(module_addr, id, offset); | |
156 } | |
157 | |
158 // Returns hex encoding of a 16-byte ID for binary loaded at |module_addr|. | |
159 // Returns empty string if UUID cannot be found at |module_addr|. | |
160 std::string GetUniqueId(const void* module_addr) { | |
161 unsigned char id[kUUIDLengthBytes]; | |
162 if (!GetUUID(module_addr, id)) | |
163 return ""; | |
164 return HexEncode(id, kUUIDLengthBytes); | |
165 } | |
166 | |
167 // Functions related to grabbing a stack trace --------------------------------- | |
168 | |
169 // Fills |state| with |target_thread|'s context. | |
170 bool GetThreadContext(thread_act_t target_thread, ThreadContext* state) { | |
171 mach_msg_type_number_t count = | |
172 static_cast<mach_msg_type_number_t>(MACHINE_THREAD_STATE_COUNT); | |
173 return thread_get_state(target_thread, x86_THREAD_STATE64, | |
174 reinterpret_cast<thread_state_t>(state), | |
175 &count) == KERN_SUCCESS; | |
176 } | |
177 | |
178 // Walks |thread_handle|'s stack and fills |instruction_pointers|. | |
179 // Returns number of frames in stack, unless there's a corrupt frame pointer | |
180 // (likely if module compiled with -fno_omit_frame_pointer), in which this | |
181 // function will return the number of frames up until the frame with a corrupt | |
182 // frame pointer. This procedure occurs while thread is suspended, so it should | |
183 // take as little time as possible. | |
184 int RecordStack(mach_port_t thread_handle, | |
185 int max_stack_size, | |
186 uintptr_t instruction_pointers[]) { | |
187 ThreadContext state; | |
188 if (!GetThreadContext(thread_handle, &state)) | |
189 return 0; | |
190 | |
191 StackFrame frame; | |
192 frame.prev = state.rbp; | |
193 int i = 0; | |
194 for (; i < max_stack_size; i++) { | |
195 // Three cases for end-of-stack condition: | |
196 // 1) A frame pointer was corrupt. | |
197 // 2) The next recovered rsp is not lower than the previously recovered | |
198 // one, indicating the stack is not growing down. | |
199 // 3) Return (instruction) address is 0. | |
200 uintptr_t old_rsp = frame.prev; | |
201 if (!SafeStackFrameRead(frame.prev, &frame) || frame.prev < old_rsp || | |
202 !frame.return_addr) { | |
203 return i; | |
204 } | |
205 instruction_pointers[i] = frame.return_addr; | |
206 } | |
207 return i; | |
208 } | |
209 | |
210 // NativeStackSamplerMac ------------------------------------------------------ | |
211 | |
212 class NativeStackSamplerMac : public NativeStackSampler { | |
213 public: | |
214 explicit NativeStackSamplerMac(pid_t thread_handle); | |
215 ~NativeStackSamplerMac() override; | |
216 | |
217 // StackSamplingProfiler::NativeStackSampler: | |
218 void ProfileRecordingStarting( | |
219 std::vector<StackSamplingProfiler::Module>* modules) override; | |
220 void RecordStackSample(StackSamplingProfiler::Sample* sample) override; | |
221 void ProfileRecordingStopped() override; | |
222 | |
223 private: | |
224 // Gets the index for the Module containing |instruction_pointer| in | |
225 // |modules|, adding it if it's not already present. Returns | |
226 // StackSamplingProfiler::Frame::kUnknownModuleIndex if no Module can be | |
227 // determined for |module|. | |
228 size_t GetModuleIndex(const uintptr_t instruction_pointer, | |
Robert Sesek
2015/09/15 20:53:00
Doesn't need |const| since it's a scalar.
robliao
2015/09/15 21:04:30
This is probably okay here since the intent is to
| |
229 std::vector<StackSamplingProfiler::Module>* modules); | |
230 | |
231 // Copies the stack information represented by |instruction_pointers| into | |
232 // |sample| and |modules|. | |
233 void CopyToSample(const uintptr_t instruction_pointers[], | |
234 int stack_depth, | |
235 StackSamplingProfiler::Sample* sample, | |
236 std::vector<StackSamplingProfiler::Module>* modules); | |
237 | |
238 // Weak reference: Mach handle for thread being profiled. | |
239 mach_port_t thread_handle_; | |
240 | |
241 // Weak. Points to the modules associated with the profile being recorded | |
242 // between ProfileRecordingStarting() and ProfileRecordingStopped(). | |
243 std::vector<StackSamplingProfiler::Module>* current_modules_; | |
244 | |
245 // Maps a module's base address to the corresponding Module's index within | |
246 // current_modules_. | |
247 std::map<const void*, size_t> profile_module_index_; | |
248 | |
249 DISALLOW_COPY_AND_ASSIGN(NativeStackSamplerMac); | |
250 }; | |
251 | |
252 // The PlatformThreadId of the given thread is actually a typedef of | |
253 // mach_port_t. | |
254 // (base/threading/platform_thread_posix.cc:128) | |
255 NativeStackSamplerMac::NativeStackSamplerMac(pid_t thread_handle) | |
256 : thread_handle_(static_cast<mach_port_t>(thread_handle)), | |
257 current_modules_(nullptr) {} | |
258 | |
259 NativeStackSamplerMac::~NativeStackSamplerMac() {} | |
260 | |
261 void NativeStackSamplerMac::ProfileRecordingStarting( | |
262 std::vector<StackSamplingProfiler::Module>* modules) { | |
263 current_modules_ = modules; | |
264 profile_module_index_.clear(); | |
265 } | |
266 | |
267 void NativeStackSamplerMac::RecordStackSample( | |
268 StackSamplingProfiler::Sample* sample) { | |
269 DCHECK(current_modules_); | |
270 | |
271 const int max_stack_size = 64; | |
272 uintptr_t instruction_pointers[max_stack_size] = {0}; | |
273 | |
274 thread_suspend(thread_handle_); | |
275 int stack_depth = | |
276 RecordStack(thread_handle_, max_stack_size, instruction_pointers); | |
277 thread_resume(thread_handle_); | |
278 CopyToSample(instruction_pointers, stack_depth, sample, current_modules_); | |
279 } | |
280 | |
281 void NativeStackSamplerMac::ProfileRecordingStopped() { | |
282 current_modules_ = nullptr; | |
283 } | |
284 | |
285 size_t NativeStackSamplerMac::GetModuleIndex( | |
286 const uintptr_t instruction_pointer, | |
287 std::vector<StackSamplingProfiler::Module>* modules) { | |
288 Dl_info inf; | |
289 if (!dladdr(reinterpret_cast<const void*>(instruction_pointer), &inf)) | |
290 return StackSamplingProfiler::Frame::kUnknownModuleIndex; | |
291 | |
292 auto module_index = profile_module_index_.find(inf.dli_fbase); | |
293 if (module_index == profile_module_index_.end()) { | |
294 StackSamplingProfiler::Module module(inf.dli_fbase, | |
295 GetUniqueId(inf.dli_fbase), | |
296 base::FilePath(inf.dli_fname)); | |
297 modules->push_back(module); | |
298 module_index = profile_module_index_.insert( | |
299 std::make_pair(inf.dli_fbase, modules->size() - 1)).first; | |
300 } | |
301 return module_index->second; | |
302 } | |
303 | |
304 void NativeStackSamplerMac::CopyToSample( | |
305 const uintptr_t instruction_pointers[], | |
306 int stack_depth, | |
307 StackSamplingProfiler::Sample* sample, | |
308 std::vector<StackSamplingProfiler::Module>* modules) { | |
309 sample->clear(); | |
310 sample->reserve(stack_depth); | |
311 | |
312 for (int i = 0; i < stack_depth; i++) { | |
313 sample->push_back(StackSamplingProfiler::Frame( | |
314 reinterpret_cast<const void*>(instruction_pointers[i]), | |
315 GetModuleIndex(instruction_pointers[i], modules))); | |
316 } | |
317 } | |
318 | |
319 } // namespace | |
320 | |
321 scoped_ptr<NativeStackSampler> NativeStackSampler::Create( | |
322 PlatformThreadId thread_id) { | |
323 #if defined(__i386__) | |
324 return nullptr; | |
325 #else | |
Mike Wittman
2015/09/15 20:16:49
nit: #endif here
sydli
2015/09/15 20:37:40
Done.
| |
326 return scoped_ptr<NativeStackSampler>(new NativeStackSamplerMac(thread_id)); | |
327 #endif | |
328 } | |
329 | |
330 } // namespace base | |
OLD | NEW |