Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(17)

Side by Side Diff: base/profiler/native_stack_sampler_mac.cc

Issue 1346453004: NativeStackSampler implementation for Mac. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Binding libraries and nits. Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « base/base.gypi ('k') | base/profiler/stack_sampling_profiler_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 <libkern/OSByteOrder.h>
7 #include <mach/mach.h>
8 #include <mach/thread_status.h>
9 #include <mach-o/swap.h>
10 #include <stdlib.h>
11 #include <uuid/uuid.h>
12
13 #include <map>
14
15 #include "base/profiler/native_stack_sampler.h"
16 #include "base/strings/string_number_conversions.h"
17
18 namespace base {
19
20 namespace {
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 base_pointer;
54 // Address in caller for callee to return to.
55 uintptr_t return_address;
56 };
57
58 // Helper that swaps byte order in |x| if |swap| flag is set.
59 uint32_t SwapIfBig32(uint32_t x, bool swap) {
60 if (swap)
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 // This walker only supports modules built for x86_64 architecture. More detail
77 // can be found in the OS X ABI Mach-O File Format Reference.
78
79 // Returns offset in bytes where the x86_64 header is located in binary
80 // loaded at |module_addr|. Returns 0 if |module_addr| is not a valid FAT
81 // Mach-O binary or has not been built for x86_64.
82 off_t GetMach64HeaderOffset(const void* module_addr) {
83 const fat_header* header = reinterpret_cast<const fat_header*>(module_addr);
84 if (header->magic != FAT_MAGIC && header->magic != FAT_CIGAM)
85 return 0;
86
87 // Search all FAT architectures for x86_64.
88 const fat_arch* fat_arches = reinterpret_cast<const fat_arch*>(
89 reinterpret_cast<const uint8_t*>(module_addr) + sizeof(header));
90 uint32_t n_arches = OSSwapBigToHostInt32(header->nfat_arch);
91 for (uint32_t i = 0; i < n_arches; ++i) {
92 const fat_arch& arch = fat_arches[i];
93 if (OSSwapBigToHostInt32(arch.cputype) == CPU_TYPE_X86_64)
94 return OSSwapBigToHostInt32(arch.offset);
95 }
96 return 0;
97 }
98
99 // Returns true if Mach-O binary at |module_addr| was built specifically for
100 // x86_64 cpu architecture.
101 bool IsX64Header(const void* module_addr) {
102 const mach_header_64* header =
103 reinterpret_cast<const mach_header_64*>(module_addr);
104 if (header->magic != MH_MAGIC_64 && header->magic != MH_CIGAM_64)
105 return false;
106 bool swap = header->magic == MH_CIGAM_64;
107 return SwapIfBig32(header->cputype, swap) == CPU_TYPE_X86_64;
108 }
109
110 // Fills |id| with the UUID of the x86_64 Mach-O binary loaded at |module_addr|.
111 // |offset| is the offset in bytes into |module_addr| where the x86_64 header
112 // is located. |offset| is only relevant if binary is FAT and contains
113 // multiple architecture headers. Returns false if header is malformed or the
114 // header does not specify the UUID load command.
115 bool GetX64UUIDAt(const void* module_addr, unsigned char* id, off_t offset) {
116 const mach_header_64* header = reinterpret_cast<const mach_header_64*>(
117 reinterpret_cast<const uint8_t*>(module_addr) + offset);
118 if (header->magic != MH_MAGIC_64 && header->magic != MH_CIGAM_64)
119 return false;
120
121 bool swap = header->magic == MH_CIGAM_64;
122 // Search all load commands for UUID command.
123 offset += sizeof(mach_header_64);
124 for (uint32_t i = 0; i < SwapIfBig32(header->ncmds, swap); ++i) {
125 const load_command* current_cmd = reinterpret_cast<const load_command*>(
126 reinterpret_cast<const uint8_t*>(module_addr) + offset);
127
128 if (SwapIfBig32(current_cmd->cmd, swap) == LC_UUID) {
129 const uuid_command* uuid_cmd =
130 reinterpret_cast<const uuid_command*>(current_cmd);
131 static_assert(sizeof(uuid_cmd->uuid) == sizeof(uuid_t),
132 "UUID field of UUID command should be 16 bytes.");
133 memcpy(id, &uuid_cmd->uuid, sizeof(uuid_t));
134 return true;
135 }
136 offset += SwapIfBig32(current_cmd->cmdsize, swap);
137 }
138 return false;
139 }
140
141 // Fills |id| with Mach-O UUID retrieved from Mach-O binary loaded at
142 // |module_addr|. This function returns false if the binary was not built for
143 // X86_64 or if UUID cannot be found.
144 bool GetUUID(const void* module_addr, unsigned char* id) {
145 off_t offset = 0;
146 // If module is not x86_64 exclusive, it could be a module that supports
147 // multiple architectures. In this case, the appropriate header will be at
148 // some non-zero offset.
149 if (!IsX64Header(module_addr) &&
150 !(offset = GetMach64HeaderOffset(module_addr))) {
151 return false;
152 }
153 return GetX64UUIDAt(module_addr, id, offset);
154 }
155
156 // Returns hex encoding of a 16-byte ID for binary loaded at |module_addr|.
157 // Returns empty string if UUID cannot be found at |module_addr|.
158 std::string GetUniqueId(const void* module_addr) {
159 unsigned char id[sizeof(uuid_t)];
160 if (!GetUUID(module_addr, id))
161 return "";
162 return HexEncode(id, sizeof(uuid_t));
163 }
164
165 // Functions related to grabbing a stack trace ---------------------------------
166
167 // Fills |state| with |target_thread|'s context.
168 bool GetThreadContext(thread_act_t target_thread, ThreadContext* state) {
169 mach_msg_type_number_t count =
170 static_cast<mach_msg_type_number_t>(MACHINE_THREAD_STATE_COUNT);
171 return thread_get_state(target_thread, x86_THREAD_STATE64,
172 reinterpret_cast<thread_state_t>(state),
173 &count) == KERN_SUCCESS;
174 }
175
176 // Walks |thread_handle|'s stack and fills |instruction_pointers|.
177 // Returns number of frames in stack, unless there's a corrupt frame pointer
178 // (likely if module compiled with -fno_omit_frame_pointer), in which this
179 // function will return the number of frames up until the frame with a corrupt
180 // frame pointer. This procedure occurs while thread is suspended, so it should
181 // take as little time as possible.
182 int RecordStack(mach_port_t thread_handle,
183 int max_stack_size,
184 uintptr_t instruction_pointers[]) {
185 ThreadContext state;
186 if (!GetThreadContext(thread_handle, &state))
187 return 0;
188
189 StackFrame frame;
190 frame.base_pointer = state.rbp;
191 int i = 0;
192 for (; i < max_stack_size; i++) {
193 // Three cases for end-of-stack condition:
194 // 1) A frame pointer was corrupt.
195 // 2) The next recovered rsp is not lower than the previously recovered
196 // one, indicating the stack is not growing down.
197 // 3) Return (instruction) address is 0.
198 uintptr_t old_rsp = frame.base_pointer;
199 if (!SafeStackFrameRead(frame.base_pointer, &frame) ||
200 frame.base_pointer < old_rsp || !frame.return_address) {
201 return i;
202 }
203 instruction_pointers[i] = frame.return_address;
204 }
205 return i;
206 }
207
208 // Forcibly binds the system calls we use during stackwalking to prevent
209 // deadlocking on dyld, including thread_get_state and vm_read_overwrite.
210 // Returns base address of loaded module.
211 void* BindSystemCalls() {
212 return dlopen("/usr/lib/system/libsystem_kernel.dylib", RTLD_NOW);
213 }
214
215 // NativeStackSamplerMac ------------------------------------------------------
216
217 class NativeStackSamplerMac : public NativeStackSampler {
218 public:
219 explicit NativeStackSamplerMac(pid_t thread_handle);
220 ~NativeStackSamplerMac() override;
221
222 // StackSamplingProfiler::NativeStackSampler:
223 void ProfileRecordingStarting(
224 std::vector<StackSamplingProfiler::Module>* modules) override;
225 void RecordStackSample(StackSamplingProfiler::Sample* sample) override;
226 void ProfileRecordingStopped() override;
227
228 private:
229 // Gets the index for the Module containing |instruction_pointer| in
230 // |modules|, adding it if it's not already present. Returns
231 // StackSamplingProfiler::Frame::kUnknownModuleIndex if no Module can be
232 // determined for |module|.
233 size_t GetModuleIndex(const uintptr_t instruction_pointer,
234 std::vector<StackSamplingProfiler::Module>* modules);
235
236 // Copies the stack information represented by |instruction_pointers| into
237 // |sample| and |modules|.
238 void CopyToSample(const uintptr_t instruction_pointers[],
239 int stack_depth,
240 StackSamplingProfiler::Sample* sample,
241 std::vector<StackSamplingProfiler::Module>* modules);
242
243 // Weak reference: Mach handle for thread being profiled.
244 mach_port_t thread_handle_;
245
246 // Weak. Points to the modules associated with the profile being recorded
247 // between ProfileRecordingStarting() and ProfileRecordingStopped().
248 std::vector<StackSamplingProfiler::Module>* current_modules_;
249
250 // Maps a module's base address to the corresponding Module's index within
251 // current_modules_.
252 std::map<const void*, size_t> profile_module_index_;
253
254 DISALLOW_COPY_AND_ASSIGN(NativeStackSamplerMac);
255 };
256
257 // The PlatformThreadId of the given thread is actually a typedef of
258 // mach_port_t.
259 // (base/threading/platform_thread_posix.cc:128)
260 NativeStackSamplerMac::NativeStackSamplerMac(pid_t thread_handle)
261 : thread_handle_(static_cast<mach_port_t>(thread_handle)),
262 current_modules_(nullptr) {}
263
264 NativeStackSamplerMac::~NativeStackSamplerMac() {}
265
266 void NativeStackSamplerMac::ProfileRecordingStarting(
267 std::vector<StackSamplingProfiler::Module>* modules) {
268 current_modules_ = modules;
269 profile_module_index_.clear();
270 }
271
272 void NativeStackSamplerMac::RecordStackSample(
273 StackSamplingProfiler::Sample* sample) {
274 DCHECK(current_modules_);
275
276 const int max_stack_size = 64;
277 uintptr_t instruction_pointers[max_stack_size] = {0};
278
279 void* syscall_handle = BindSystemCalls();
280 if (syscall_handle) {
281 thread_suspend(thread_handle_);
282 int stack_depth =
283 RecordStack(thread_handle_, max_stack_size, instruction_pointers);
284 thread_resume(thread_handle_);
285 dlclose(syscall_handle);
286 CopyToSample(instruction_pointers, stack_depth, sample, current_modules_);
287 }
288 }
289
290 void NativeStackSamplerMac::ProfileRecordingStopped() {
291 current_modules_ = nullptr;
292 }
293
294 size_t NativeStackSamplerMac::GetModuleIndex(
295 const uintptr_t instruction_pointer,
296 std::vector<StackSamplingProfiler::Module>* modules) {
297 Dl_info inf;
298 if (!dladdr(reinterpret_cast<const void*>(instruction_pointer), &inf))
299 return StackSamplingProfiler::Frame::kUnknownModuleIndex;
300
301 auto module_index = profile_module_index_.find(inf.dli_fbase);
302 if (module_index == profile_module_index_.end()) {
303 StackSamplingProfiler::Module module(inf.dli_fbase,
304 GetUniqueId(inf.dli_fbase),
305 base::FilePath(inf.dli_fname));
306 modules->push_back(module);
307 module_index = profile_module_index_.insert(
308 std::make_pair(inf.dli_fbase, modules->size() - 1)).first;
309 }
310 return module_index->second;
311 }
312
313 void NativeStackSamplerMac::CopyToSample(
314 const uintptr_t instruction_pointers[],
315 int stack_depth,
316 StackSamplingProfiler::Sample* sample,
317 std::vector<StackSamplingProfiler::Module>* modules) {
318 sample->clear();
319 sample->reserve(stack_depth);
320
321 for (int i = 0; i < stack_depth; i++) {
322 sample->push_back(StackSamplingProfiler::Frame(
323 reinterpret_cast<const void*>(instruction_pointers[i]),
324 GetModuleIndex(instruction_pointers[i], modules)));
325 }
326 }
327
328 } // namespace
329
330 scoped_ptr<NativeStackSampler> NativeStackSampler::Create(
331 PlatformThreadId thread_id) {
332 #if defined(__i386__)
333 return nullptr;
334 #endif
335 return scoped_ptr<NativeStackSampler>(new NativeStackSamplerMac(thread_id));
336 }
337
338 } // namespace base
OLDNEW
« no previous file with comments | « base/base.gypi ('k') | base/profiler/stack_sampling_profiler_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698