Chromium Code Reviews| 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..b196c09436de9923d239466b222eef06909b3de0 |
| --- /dev/null |
| +++ b/base/profiler/native_stack_sampler_mac.cc |
| @@ -0,0 +1,324 @@ |
| +// 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> |
| +#include <libkern/OSByteOrder.h> |
| +#include <mach/mach.h> |
| +#include <mach/thread_status.h> |
| +#include <mach-o/swap.h> |
| +#include <stdlib.h> |
| +#include <uuid/uuid.h> |
| + |
| +#include <map> |
| + |
| +#include "base/profiler/native_stack_sampler.h" |
| +#include "base/strings/string_number_conversions.h" |
| + |
| +namespace base { |
| + |
| +namespace { |
| + |
| +// 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 base_pointer; |
| + // Address in caller for callee to return to. |
| + uintptr_t return_address; |
| +}; |
| + |
| +// Helper that swaps byte order in |x| if |swap| flag is set. |
| +uint32_t SwapIfBig32(uint32_t x, bool swap) { |
| + if (swap) |
| + 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 |
| +// 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* header = reinterpret_cast<const fat_header*>(module_addr); |
| + if (header->magic != FAT_MAGIC && header->magic != FAT_CIGAM) |
| + return 0; |
| + |
| + // 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(header)); |
| + for (uint32_t i = 0; i < OSSwapBigToHostInt32(header->nfat_arch); ++i) { |
|
robliao
2015/09/16 01:41:45
Cache the result of OSSwapBigToHostInt32(header->n
sydli
2015/09/18 20:29:20
Done.
|
| + const fat_arch& arch = fat_arches[i]; |
| + if (OSSwapBigToHostInt32(arch.cputype) == CPU_TYPE_X86_64) |
| + return OSSwapBigToHostInt32(arch.offset); |
| + } |
| + 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* header = |
| + reinterpret_cast<const mach_header_64*>(module_addr); |
| + if (header->magic != MH_MAGIC_64 && header->magic != MH_CIGAM_64) |
| + return false; |
| + bool swap = header->magic == MH_CIGAM_64; |
| + return SwapIfBig32(header->cputype, swap) == CPU_TYPE_X86_64; |
| +} |
| + |
| +// 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* header = reinterpret_cast<const mach_header_64*>( |
| + reinterpret_cast<const uint8_t*>(module_addr) + offset); |
| + if (header->magic != MH_MAGIC_64 && header->magic != MH_CIGAM_64) |
| + return false; |
| + bool swap = header->magic == MH_CIGAM_64; |
|
robliao
2015/09/16 01:41:45
Nit: Group statement with the next set of statemen
sydli
2015/09/18 20:29:20
Done.
|
| + |
| + // Search all load commands for UUID command. |
| + offset += sizeof(mach_header_64); |
| + for (uint32_t i = 0; i < SwapIfBig32(header->ncmds, swap); ++i) { |
| + const load_command* current_cmd = reinterpret_cast<const load_command*>( |
| + reinterpret_cast<const uint8_t*>(module_addr) + offset); |
| + |
| + if (SwapIfBig32(current_cmd->cmd, swap) == LC_UUID) { |
| + const uuid_command* uuid_cmd = |
| + reinterpret_cast<const uuid_command*>(current_cmd); |
| + memcpy(id, &uuid_cmd->uuid, sizeof(uuid_t)); |
| + return true; |
| + } |
| + offset += SwapIfBig32(current_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))) { |
| + 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[sizeof(uuid_t)]; |
| + if (!GetUUID(module_addr, id)) |
| + return ""; |
| + return HexEncode(id, sizeof(uuid_t)); |
| +} |
| + |
| +// 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.base_pointer = 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.base_pointer; |
| + if (!SafeStackFrameRead(frame.base_pointer, &frame) || |
| + frame.base_pointer < old_rsp || !frame.return_address) { |
| + return i; |
| + } |
| + instruction_pointers[i] = frame.return_address; |
| + } |
| + 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, |
| + 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; |
|
robliao
2015/09/16 01:41:45
Optional: It would be nice to unify this value som
|
| + uintptr_t instruction_pointers[max_stack_size] = {0}; |
| + |
| + thread_suspend(thread_handle_); |
|
Robert Sesek
2015/09/17 22:06:56
In the email thread, Mark mentioned that this coul
sydli
2015/09/18 20:29:20
Now forcibly binds library used during stack walk.
|
| + 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; |
| +#endif |
| + return scoped_ptr<NativeStackSampler>(new NativeStackSamplerMac(thread_id)); |
| +} |
| + |
| +} // namespace base |