| 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..d660ef3dc64ffafcaf54ec07c8cbc91d86d12734
|
| --- /dev/null
|
| +++ b/base/profiler/native_stack_sampler_mac.cc
|
| @@ -0,0 +1,338 @@
|
| +// 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));
|
| + uint32_t n_arches = OSSwapBigToHostInt32(header->nfat_arch);
|
| + for (uint32_t i = 0; i < n_arches; ++i) {
|
| + 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;
|
| + // 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);
|
| + static_assert(sizeof(uuid_cmd->uuid) == sizeof(uuid_t),
|
| + "UUID field of UUID command should be 16 bytes.");
|
| + 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;
|
| +}
|
| +
|
| +// Forcibly binds the system calls we use during stackwalking to prevent
|
| +// deadlocking on dyld, including thread_get_state and vm_read_overwrite.
|
| +// Returns base address of loaded module.
|
| +void* BindSystemCalls() {
|
| + return dlopen("/usr/lib/system/libsystem_kernel.dylib", RTLD_NOW);
|
| +}
|
| +
|
| +// 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;
|
| + uintptr_t instruction_pointers[max_stack_size] = {0};
|
| +
|
| + void* syscall_handle = BindSystemCalls();
|
| + if (syscall_handle) {
|
| + thread_suspend(thread_handle_);
|
| + int stack_depth =
|
| + RecordStack(thread_handle_, max_stack_size, instruction_pointers);
|
| + thread_resume(thread_handle_);
|
| + dlclose(syscall_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
|
|
|