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

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: Adjusted comments. 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 <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
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