Index: base/profiler/native_stack_sampler_mac.cc |
diff --git a/base/profiler/native_stack_sampler_mac.cc b/base/profiler/native_stack_sampler_mac.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..bd20b6426ed168d44d72fb0f937d170c45b68913 |
--- /dev/null |
+++ b/base/profiler/native_stack_sampler_mac.cc |
@@ -0,0 +1,330 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include <dlfcn.h> |
Robert Sesek
2015/09/15 20:53:00
#include <libkern/OSByteOrder.h>
sydli
2015/09/15 21:49:42
Done.
|
+#include <mach/mach.h> |
+#include <mach/thread_status.h> |
+#include <mach-o/swap.h> |
+#include <stdlib.h> |
+ |
+#include <map> |
+ |
+#include "base/profiler/native_stack_sampler.h" |
+#include "base/strings/string_number_conversions.h" |
+ |
+namespace base { |
+ |
+namespace { |
+ |
+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!
|
+ |
+// Copy of x86_64 thread context structure from x86_thread_state64_t type. |
+// Copied struct since fields can have different names on different versions of |
+// Darwin. |
+struct ThreadContext { |
+ uint64_t rax; |
+ uint64_t rbx; |
+ uint64_t rcx; |
+ uint64_t rdx; |
+ uint64_t rdi; |
+ uint64_t rsi; |
+ uint64_t rbp; |
+ uint64_t rsp; |
+ uint64_t r8; |
+ uint64_t r9; |
+ uint64_t r10; |
+ uint64_t r11; |
+ uint64_t r12; |
+ uint64_t r13; |
+ uint64_t r14; |
+ uint64_t r15; |
+ uint64_t rip; |
+ uint64_t rflags; |
+ uint64_t cs; |
+ uint64_t fs; |
+ uint64_t gs; |
+}; |
+ |
+// Struct for stack walking (represents stack state on any function call that |
+// pushes a frame pointer). |
+struct StackFrame { |
+ // Pointer to caller's frame (rbp). |
+ 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.
|
+ // Address in caller for callee to return to. |
+ uintptr_t return_addr; |
+}; |
+ |
+// Helper that swaps byte order in |x| if |big| (big-endian) flag is set. |
+uint32_t SwapIfBig32(uint32_t x, bool big) { |
+ if (big) |
+ return OSSwapBigToHostInt32(x); |
+ return x; |
+} |
+ |
+// Overwrites |dst| with StackFrame referenced in |src|. Returns true if stack |
+// read was successful. Failure implies address in |src| is corrupt. |
+bool SafeStackFrameRead(uintptr_t src, StackFrame* dst) { |
+ vm_size_t ignored_bytes_copied = 0; |
+ return vm_read_overwrite(mach_task_self(), static_cast<vm_address_t>(src), |
+ static_cast<vm_size_t>(sizeof(StackFrame)), |
+ reinterpret_cast<vm_address_t>(dst), |
+ &ignored_bytes_copied) == KERN_SUCCESS; |
+} |
+ |
+// Functions related to retrieving Mach-O Identifer |
+// |
+// 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.
|
+// breakpad/src/common/mac. Support for non-X86_64 architectures and MD5 IDs |
+// were removed to simplify the code. |
+// |
+// This walker only supports modules built for x86_64 architecture. More detail |
+// can be found in the OS X ABI Mach-O File Format Reference. |
+ |
+// Returns offset in bytes where the x86_64 header is located in binary |
+// loaded at |module_addr|. Returns 0 if |module_addr| is not a valid FAT |
+// Mach-O binary or has not been built for x86_64. |
+off_t GetMach64HeaderOffset(const void* module_addr) { |
+ 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.
|
+ if (hdr->magic != FAT_MAGIC && hdr->magic != FAT_CIGAM) |
+ return 0; |
+ bool swap = hdr->magic == FAT_CIGAM; |
+ |
+ // Search all FAT architectures for x86_64. |
+ const fat_arch* fat_arches = reinterpret_cast<const fat_arch*>( |
+ reinterpret_cast<const uint8_t*>(module_addr) + sizeof(hdr)); |
+ 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
|
+ const fat_arch& arch = fat_arches[i]; |
+ if (SwapIfBig32(arch.cputype, swap) == CPU_TYPE_X86_64) |
+ return SwapIfBig32(arch.offset, swap); |
+ } |
+ return 0; |
+} |
+ |
+// Returns true if Mach-O binary at |module_addr| was built specifically for |
+// x86_64 cpu architecture. |
+bool IsX64Header(const void* module_addr) { |
+ const mach_header_64* hdr = |
Robert Sesek
2015/09/15 20:53:00
naming: header
sydli
2015/09/15 21:49:42
Done.
|
+ reinterpret_cast<const mach_header_64*>(module_addr); |
+ if (hdr->magic != MH_MAGIC_64 && hdr->magic != MH_CIGAM_64) |
+ return false; |
+ 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),
|
+ 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.
|
+} |
+ |
+// Fills |id| with the UUID of the x86_64 Mach-O binary loaded at |module_addr|. |
+// |offset| is the offset in bytes into |module_addr| where the x86_64 header |
+// is located. |offset| is only relevant if binary is FAT and contains |
+// multiple architecture headers. Returns false if header is malformed or the |
+// header does not specify the UUID load command. |
+bool GetX64UUIDAt(const void* module_addr, unsigned char* id, off_t offset) { |
+ 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.
|
+ reinterpret_cast<const uint8_t*>(module_addr) + offset); |
+ if (hdr->magic != MH_MAGIC_64 && hdr->magic != MH_CIGAM_64) |
+ return false; |
+ bool swap = hdr->magic == MH_CIGAM_64; |
+ |
+ // Search all load commands for UUID command. |
+ offset += sizeof(mach_header_64); |
+ for (uint32_t i = 0; i < SwapIfBig32(hdr->ncmds, swap); ++i) { |
+ const load_command* cmd = reinterpret_cast<const load_command*>( |
+ reinterpret_cast<const uint8_t*>(module_addr) + offset); |
+ if (SwapIfBig32(cmd->cmd, swap) == LC_UUID) { |
+ const uuid_command* uuid_cmd = reinterpret_cast<const uuid_command*>(cmd); |
+ 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.
|
+ "UUID field of UUID command should be 16 bytes."); |
+ memcpy(id, &uuid_cmd->uuid, kUUIDLengthBytes); |
+ return true; |
+ } |
+ offset += SwapIfBig32(cmd->cmdsize, swap); |
+ } |
+ return false; |
+} |
+ |
+// Fills |id| with Mach-O UUID retrieved from Mach-O binary loaded at |
+// |module_addr|. This function returns false if the binary was not built for |
+// X86_64 or if UUID cannot be found. |
+bool GetUUID(const void* module_addr, unsigned char* id) { |
+ off_t offset = 0; |
+ // If module is not x86_64 exclusive, it could be a module that supports |
+ // multiple architectures. In this case, the appropriate header will be at |
+ // some non-zero offset. |
+ if (!IsX64Header(module_addr) && |
+ !(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.
|
+ return false; |
+ return GetX64UUIDAt(module_addr, id, offset); |
+} |
+ |
+// Returns hex encoding of a 16-byte ID for binary loaded at |module_addr|. |
+// Returns empty string if UUID cannot be found at |module_addr|. |
+std::string GetUniqueId(const void* module_addr) { |
+ unsigned char id[kUUIDLengthBytes]; |
+ if (!GetUUID(module_addr, id)) |
+ return ""; |
+ return HexEncode(id, kUUIDLengthBytes); |
+} |
+ |
+// Functions related to grabbing a stack trace --------------------------------- |
+ |
+// Fills |state| with |target_thread|'s context. |
+bool GetThreadContext(thread_act_t target_thread, ThreadContext* state) { |
+ mach_msg_type_number_t count = |
+ static_cast<mach_msg_type_number_t>(MACHINE_THREAD_STATE_COUNT); |
+ return thread_get_state(target_thread, x86_THREAD_STATE64, |
+ reinterpret_cast<thread_state_t>(state), |
+ &count) == KERN_SUCCESS; |
+} |
+ |
+// Walks |thread_handle|'s stack and fills |instruction_pointers|. |
+// Returns number of frames in stack, unless there's a corrupt frame pointer |
+// (likely if module compiled with -fno_omit_frame_pointer), in which this |
+// function will return the number of frames up until the frame with a corrupt |
+// frame pointer. This procedure occurs while thread is suspended, so it should |
+// take as little time as possible. |
+int RecordStack(mach_port_t thread_handle, |
+ int max_stack_size, |
+ uintptr_t instruction_pointers[]) { |
+ ThreadContext state; |
+ if (!GetThreadContext(thread_handle, &state)) |
+ return 0; |
+ |
+ StackFrame frame; |
+ frame.prev = state.rbp; |
+ int i = 0; |
+ for (; i < max_stack_size; i++) { |
+ // Three cases for end-of-stack condition: |
+ // 1) A frame pointer was corrupt. |
+ // 2) The next recovered rsp is not lower than the previously recovered |
+ // one, indicating the stack is not growing down. |
+ // 3) Return (instruction) address is 0. |
+ uintptr_t old_rsp = frame.prev; |
+ if (!SafeStackFrameRead(frame.prev, &frame) || frame.prev < old_rsp || |
+ !frame.return_addr) { |
+ return i; |
+ } |
+ instruction_pointers[i] = frame.return_addr; |
+ } |
+ return i; |
+} |
+ |
+// NativeStackSamplerMac ------------------------------------------------------ |
+ |
+class NativeStackSamplerMac : public NativeStackSampler { |
+ public: |
+ explicit NativeStackSamplerMac(pid_t thread_handle); |
+ ~NativeStackSamplerMac() override; |
+ |
+ // StackSamplingProfiler::NativeStackSampler: |
+ void ProfileRecordingStarting( |
+ std::vector<StackSamplingProfiler::Module>* modules) override; |
+ void RecordStackSample(StackSamplingProfiler::Sample* sample) override; |
+ void ProfileRecordingStopped() override; |
+ |
+ private: |
+ // Gets the index for the Module containing |instruction_pointer| in |
+ // |modules|, adding it if it's not already present. Returns |
+ // StackSamplingProfiler::Frame::kUnknownModuleIndex if no Module can be |
+ // determined for |module|. |
+ 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
|
+ std::vector<StackSamplingProfiler::Module>* modules); |
+ |
+ // Copies the stack information represented by |instruction_pointers| into |
+ // |sample| and |modules|. |
+ void CopyToSample(const uintptr_t instruction_pointers[], |
+ int stack_depth, |
+ StackSamplingProfiler::Sample* sample, |
+ std::vector<StackSamplingProfiler::Module>* modules); |
+ |
+ // Weak reference: Mach handle for thread being profiled. |
+ mach_port_t thread_handle_; |
+ |
+ // Weak. Points to the modules associated with the profile being recorded |
+ // between ProfileRecordingStarting() and ProfileRecordingStopped(). |
+ std::vector<StackSamplingProfiler::Module>* current_modules_; |
+ |
+ // Maps a module's base address to the corresponding Module's index within |
+ // current_modules_. |
+ std::map<const void*, size_t> profile_module_index_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(NativeStackSamplerMac); |
+}; |
+ |
+// The PlatformThreadId of the given thread is actually a typedef of |
+// mach_port_t. |
+// (base/threading/platform_thread_posix.cc:128) |
+NativeStackSamplerMac::NativeStackSamplerMac(pid_t thread_handle) |
+ : thread_handle_(static_cast<mach_port_t>(thread_handle)), |
+ current_modules_(nullptr) {} |
+ |
+NativeStackSamplerMac::~NativeStackSamplerMac() {} |
+ |
+void NativeStackSamplerMac::ProfileRecordingStarting( |
+ std::vector<StackSamplingProfiler::Module>* modules) { |
+ current_modules_ = modules; |
+ profile_module_index_.clear(); |
+} |
+ |
+void NativeStackSamplerMac::RecordStackSample( |
+ StackSamplingProfiler::Sample* sample) { |
+ DCHECK(current_modules_); |
+ |
+ const int max_stack_size = 64; |
+ uintptr_t instruction_pointers[max_stack_size] = {0}; |
+ |
+ thread_suspend(thread_handle_); |
+ int stack_depth = |
+ RecordStack(thread_handle_, max_stack_size, instruction_pointers); |
+ thread_resume(thread_handle_); |
+ CopyToSample(instruction_pointers, stack_depth, sample, current_modules_); |
+} |
+ |
+void NativeStackSamplerMac::ProfileRecordingStopped() { |
+ current_modules_ = nullptr; |
+} |
+ |
+size_t NativeStackSamplerMac::GetModuleIndex( |
+ const uintptr_t instruction_pointer, |
+ std::vector<StackSamplingProfiler::Module>* modules) { |
+ Dl_info inf; |
+ if (!dladdr(reinterpret_cast<const void*>(instruction_pointer), &inf)) |
+ return StackSamplingProfiler::Frame::kUnknownModuleIndex; |
+ |
+ auto module_index = profile_module_index_.find(inf.dli_fbase); |
+ if (module_index == profile_module_index_.end()) { |
+ StackSamplingProfiler::Module module(inf.dli_fbase, |
+ GetUniqueId(inf.dli_fbase), |
+ base::FilePath(inf.dli_fname)); |
+ modules->push_back(module); |
+ module_index = profile_module_index_.insert( |
+ std::make_pair(inf.dli_fbase, modules->size() - 1)).first; |
+ } |
+ return module_index->second; |
+} |
+ |
+void NativeStackSamplerMac::CopyToSample( |
+ const uintptr_t instruction_pointers[], |
+ int stack_depth, |
+ StackSamplingProfiler::Sample* sample, |
+ std::vector<StackSamplingProfiler::Module>* modules) { |
+ sample->clear(); |
+ sample->reserve(stack_depth); |
+ |
+ for (int i = 0; i < stack_depth; i++) { |
+ sample->push_back(StackSamplingProfiler::Frame( |
+ reinterpret_cast<const void*>(instruction_pointers[i]), |
+ GetModuleIndex(instruction_pointers[i], modules))); |
+ } |
+} |
+ |
+} // namespace |
+ |
+scoped_ptr<NativeStackSampler> NativeStackSampler::Create( |
+ PlatformThreadId thread_id) { |
+#if defined(__i386__) |
+ return nullptr; |
+#else |
Mike Wittman
2015/09/15 20:16:49
nit: #endif here
sydli
2015/09/15 20:37:40
Done.
|
+ return scoped_ptr<NativeStackSampler>(new NativeStackSamplerMac(thread_id)); |
+#endif |
+} |
+ |
+} // namespace base |