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

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: 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
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/logging.h"
Mike Wittman 2015/09/15 00:34:54 unused?
sydli 2015/09/15 01:12:20 Removed all of these.
14 #include "base/md5.h"
Mike Wittman 2015/09/15 00:34:54 unused?
15 #include "base/profiler/native_stack_sampler.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/time/time.h"
Mike Wittman 2015/09/15 00:34:54 unused?
18
19 namespace base {
20
21 namespace {
22
23 // Copy of x86_64 thread context structure from x86_thread_state64_t type.
24 // Copied struct since fields can have different names on different versions of
25 // Darwin.
26 struct ThreadContext {
27 uint64_t rax;
28 uint64_t rbx;
29 uint64_t rcx;
30 uint64_t rdx;
31 uint64_t rdi;
32 uint64_t rsi;
33 uint64_t rbp;
34 uint64_t rsp;
35 uint64_t r8;
36 uint64_t r9;
37 uint64_t r10;
38 uint64_t r11;
39 uint64_t r12;
40 uint64_t r13;
41 uint64_t r14;
42 uint64_t r15;
43 uint64_t rip;
44 uint64_t rflags;
45 uint64_t cs;
46 uint64_t fs;
47 uint64_t gs;
48 };
49
50 // Struct for stack walking (represents stack state on any function call that
51 // pushes a frame pointer).
52 struct StackFrame {
53 // Pointer to caller's frame (rbp).
54 uintptr_t prev;
55 // Address in caller for callee to return to.
56 uintptr_t return_addr;
57 };
58
59 // Overwrites |dst| with StackFrame referenced in |src|. Returns true if stack
60 // jump was successful. Failure implies address in |src| is corrupt.
61 bool SafeJump(uintptr_t src, StackFrame* dst) {
62 vm_size_t ignored_bytes_copied = 0;
63 return vm_read_overwrite(mach_task_self(), (vm_address_t)src,
Mike Wittman 2015/09/15 00:34:54 use C++-style casts
sydli 2015/09/15 01:12:19 Done.
64 (vm_size_t)sizeof(StackFrame), (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 // Returns offset in bytes where the x86_64 header is located in binary
75 // loaded at |module_addr|. Returns 0 if |module_addr| is not a valid FAT
76 // Mach-O binary or has not been built for x86_64.
77 off_t GetMach64HeaderOffset(const void* module_addr) {
78 fat_header hdr;
79 memcpy(&hdr, module_addr, sizeof(hdr));
80 if (hdr.magic != FAT_MAGIC && hdr.magic != FAT_CIGAM)
81 return 0;
82
83 if (hdr.magic == FAT_CIGAM)
84 swap_fat_header(&hdr, NXHostByteOrder());
85
86 // Search all FAT architectures for x86_64.
87 off_t offset = sizeof(hdr);
88 fat_arch arch;
89 for (uint32_t i = 0; i < hdr.nfat_arch; ++i) {
90 memcpy(&arch, reinterpret_cast<const char*>(module_addr) + offset,
91 sizeof(arch));
92
93 if (NXHostByteOrder() != NX_BigEndian)
94 swap_fat_arch(&arch, 1, NXHostByteOrder());
95
96 if (arch.cputype == CPU_TYPE_X86_64)
97 return arch.offset;
98 offset += sizeof(arch);
99 }
100 return 0;
101 }
102
103 // Returns true if Mach-O binary at |module_addr| was built specifically for
104 // x86_64 cpu architecture.
105 bool IsX64Header(const void* module_addr) {
106 mach_header_64 hdr;
107 memcpy(&hdr, module_addr, sizeof(hdr));
108 if (hdr.magic != MH_MAGIC_64 && hdr.magic != MH_CIGAM_64)
109 return false;
110 if (hdr.magic == MH_CIGAM_64)
111 swap_mach_header_64(&hdr, NXHostByteOrder());
112
113 return (hdr.cputype == CPU_TYPE_X86_64);
Mike Wittman 2015/09/15 00:34:54 nit: no parens
sydli 2015/09/15 01:12:19 Done.
114 }
115
116 // Fills |id| with the UUID of the x86_64 Mach-O binary loaded at |module_addr|.
117 // |offset| is the offset in bytes into |module_addr| where the x86_64 header
118 // is located. |offset| is only relevant if binary is FAT and contains
119 // multiple architecture headers. See spec here:
120 // https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptu al/MachORuntime/
121 // Returns false if header is malformed or the
122 // header does not specify the UUID load command.
123 bool GetX64UUIDAt(const void* module_addr, unsigned char id[16], off_t offset) {
124 mach_header_64 hdr;
125 if (!memcpy(&hdr, reinterpret_cast<const char*>(module_addr) + offset,
126 sizeof(hdr)) ||
127 (hdr.magic != MH_MAGIC_64 && hdr.magic != MH_CIGAM_64)) {
128 return false;
129 }
130
131 bool swap = hdr.magic == MH_CIGAM_64;
132 if (swap)
133 swap_mach_header_64(&hdr, NXHostByteOrder());
134
135 // Search all load commands for UUID command.
136 offset += sizeof(hdr);
137 load_command cmd;
138 for (uint32_t i = 0; i < hdr.ncmds; ++i) {
139 const void* command_loc =
140 reinterpret_cast<const char*>(module_addr) + offset;
141 memcpy(&cmd, command_loc, sizeof(cmd));
142
143 if (swap)
144 swap_load_command(&cmd, NXHostByteOrder());
145 if (cmd.cmd == LC_UUID) {
146 uuid_command uuid_cmd;
147 memcpy(&uuid_cmd, command_loc, sizeof(uuid_cmd));
148 if (swap)
149 swap_uuid_command(&uuid_cmd, NXHostByteOrder());
150
151 static_assert(sizeof(uuid_cmd.uuid) == 16,
152 "UUID field of UUID command should be 16 bytes.");
153 memcpy(id, &uuid_cmd.uuid, sizeof(uuid_cmd));
154 return true;
155 }
156 offset += cmd.cmdsize;
157 }
158 return false;
159 }
160
161 // Fills |id| with Mach-O UUID retrieved from Mach-O binary loaded at
162 // |module_addr|. This function returns false if the binary was not built for
163 // X86_64 or if UUID cannot be found.
164 bool GetUUID(const void* module_addr, unsigned char id[16]) {
165 off_t offset = 0;
166 if (!IsX64Header(module_addr) &&
Mike Wittman 2015/09/15 00:34:54 shouldn't this be || ?
sydli 2015/09/15 01:12:19 No, if it's not an x86_64-exclusive module it coul
167 !(offset = GetMach64HeaderOffset(module_addr)))
168 return false;
169 return GetX64UUIDAt(module_addr, id, offset);
170 }
171
172 // Returns hex encoding of a 16-byte ID for binary loaded at |module_addr|.
173 // Returns empty string if UUID cannot be found at |module_addr|.
174 std::string GetUniqueId(const void* module_addr) {
175 unsigned char id[16];
176 if (!GetUUID(module_addr, id))
177 return "";
178 return HexEncode(id, 16);
179 }
180
181 // Functions related to grabbing a stack trace --------------------------------
182
183 // Fills |state| with |target_thread|'s context.
184 bool GetThreadState(thread_act_t target_thread, ThreadContext* state) {
Mike Wittman 2015/09/15 00:34:54 can we call this GetThreadContext?
sydli 2015/09/15 01:12:19 Done.
185 mach_msg_type_number_t count =
186 static_cast<mach_msg_type_number_t>(MACHINE_THREAD_STATE_COUNT);
187 return thread_get_state(target_thread, x86_THREAD_STATE64,
188 reinterpret_cast<thread_state_t>(state),
189 &count) == KERN_SUCCESS;
190 }
191
192 // Walks |thread_handle|'s stack and fills |instruction_pointers|.
193 // Returns number of frames in stack, unless there's a corrupt frame pointer
194 // (likely if module compiled with -fno_omit_frame_pointer), in which this
195 // function will return the number of frames up until the frame with a corrupt
196 // frame pointer. This procedure occurs while thread is suspended, so it should
197 // take as little time as possible.
198 int RecordStack(mach_port_t thread_handle,
199 int instruction_pointers_size,
Mike Wittman 2015/09/15 00:34:54 can we call this max_stack_size, as in the Windows
sydli 2015/09/15 01:12:19 Changed back to max_stack_size for consistency wit
200 uintptr_t instruction_pointers[]) {
201 ThreadContext state;
202 if (!GetThreadState(thread_handle, &state))
203 return 0;
204
205 StackFrame frame;
206 frame.prev = state.rbp;
207 int i = 0;
208 for (; i < instruction_pointers_size; i++) {
209 // Three cases for end-of-stack condition:
210 // 1) We tried to jump off the stack.
211 // 2) We jumped to a lower address.
212 // 3) We reached a return (instruction) address of 0.
213 uintptr_t old_esp = frame.prev;
214 if (!SafeJump(frame.prev, &frame) || frame.prev < old_esp ||
215 !frame.return_addr) {
216 return i;
217 }
218 instruction_pointers[i] = frame.return_addr;
219 }
220 return i;
221 }
222
223 // Adds library referenced by |instruction_pointer| to |sample| and |modules|.
224 // Records library's (1) filepath, (2) base address, and (3) unique ID.
225 void AddModule(StackSamplingProfiler::Sample* sample,
226 std::vector<StackSamplingProfiler::Module>* modules,
227 std::map<void*, size_t>* module_to_index,
228 const uintptr_t instruction_pointer) {
229 char filepath[PATH_MAX + 1];
230 Dl_info inf;
231 dladdr(reinterpret_cast<const void*>(instruction_pointer), &inf);
232 auto module_index = module_to_index->find(inf.dli_fbase);
233 if (module_index == module_to_index->end()) {
234 realpath(inf.dli_fname, filepath);
235 StackSamplingProfiler::Module module(
236 inf.dli_fbase, GetUniqueId(inf.dli_fbase), base::FilePath(filepath));
237 modules->push_back(module);
238
239 module_index = module_to_index->
240 insert(std::make_pair(inf.dli_fbase, modules->size() - 1)).first;
241 }
242 sample->push_back(StackSamplingProfiler::Frame(
243 reinterpret_cast<const void*>(instruction_pointer),
244 module_index->second));
245 }
246
247 // Fills |sample| with Frames and Modules referenced by |instruction_pointers|.
248 void FillSample(const uintptr_t instruction_pointers[],
249 std::vector<StackSamplingProfiler::Module>* modules,
250 int stack_depth,
251 StackSamplingProfiler::Sample* sample) {
252 std::map<void*, size_t> module_index;
Mike Wittman 2015/09/15 00:34:54 This needs to be a member variable, as in the Wind
sydli 2015/09/15 01:12:19 Sure.
253 for (int i = 0; i < stack_depth; i++) {
254 AddModule(sample, modules, &module_index, instruction_pointers[i]);
255 }
256 }
257
258 // NativeStackSamplerMac ------------------------------------------------------
259
260 class NativeStackSamplerMac : public NativeStackSampler {
261 public:
262 explicit NativeStackSamplerMac(pid_t thread_handle);
263 ~NativeStackSamplerMac() override;
264
265 // StackSamplingProfiler::NativeStackSampler:
266 void ProfileRecordingStarting(
267 std::vector<StackSamplingProfiler::Module>* modules) override;
268 void RecordStackSample(StackSamplingProfiler::Sample* sample) override;
269 void ProfileRecordingStopped() override;
270
271 static const int kMaxStackSize = 64;
Mike Wittman 2015/09/15 00:34:54 this can be a local variable in RecordStackSample(
sydli 2015/09/15 01:12:19 Done.
272
273 private:
274 mach_port_t thread_handle_;
275
276 // Weak. Points to the modules associated with the profile being recorded
277 // between ProfileRecordingStarting() and ProfileRecordingStopped().
278 std::vector<StackSamplingProfiler::Module>* current_modules_;
279
280 DISALLOW_COPY_AND_ASSIGN(NativeStackSamplerMac);
281 };
282
283 // The PlatformThreadId of the given thread is actually a typedef of
284 // mach_port_t.
285 // (base/threading/platform_thread_posix.cc:128)
286 NativeStackSamplerMac::NativeStackSamplerMac(pid_t thread_handle)
287 : thread_handle_(static_cast<mach_port_t>(thread_handle)) {}
Mike Wittman 2015/09/15 00:34:54 current_modules_(nullptr)
sydli 2015/09/15 01:12:20 Done.
288
289 NativeStackSamplerMac::~NativeStackSamplerMac() {}
290
291 void NativeStackSamplerMac::ProfileRecordingStarting(
292 std::vector<StackSamplingProfiler::Module>* modules) {
293 current_modules_ = modules;
294 }
295
296 void NativeStackSamplerMac::RecordStackSample(
297 StackSamplingProfiler::Sample* sample) {
298 DCHECK(current_modules_);
299 uintptr_t instruction_pointers[kMaxStackSize] = {0};
300
301 thread_suspend(thread_handle_);
302 int stack_depth =
303 RecordStack(thread_handle_, kMaxStackSize, instruction_pointers);
304 thread_resume(thread_handle_);
305 FillSample(instruction_pointers, current_modules_, stack_depth, sample);
306 }
307
308 void NativeStackSamplerMac::ProfileRecordingStopped() {
309 current_modules_ = nullptr;
310 }
311
312 } // namespace
313
314 scoped_ptr<NativeStackSampler> NativeStackSampler::Create(
315 PlatformThreadId thread_id) {
316 #if defined(OS_MACOSX) && !defined(OS_NACL)
Mike Wittman 2015/09/15 00:34:54 you can remove the "defined(OS_MACOSX)", and exclu
sydli 2015/09/15 01:12:19 Done.
317 return scoped_ptr<NativeStackSampler>(new NativeStackSamplerMac(thread_id));
318 #endif
319 return scoped_ptr<NativeStackSampler>();
320 }
321
322 } // namespace base
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698