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

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: wittman's 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 <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
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