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

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: adress 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
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>
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698