| 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..6c92bceb76fbbec5be96d24935229cc3a493cedd
|
| --- /dev/null
|
| +++ b/base/profiler/native_stack_sampler_mac.cc
|
| @@ -0,0 +1,298 @@
|
| +// 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 <mach/mach.h>
|
| +#include <mach/thread_status.h>
|
| +#include <mach-o/swap.h>
|
| +#include <stdio.h>
|
| +#include <stdlib.h>
|
| +
|
| +#include <map>
|
| +
|
| +#include "base/logging.h"
|
| +#include "base/md5.h"
|
| +#include "base/profiler/native_stack_sampler.h"
|
| +#include "base/strings/string_number_conversions.h"
|
| +#include "base/time/time.h"
|
| +
|
| +namespace base {
|
| +
|
| +namespace {
|
| +
|
| +// Copy of x86_64 thread context structure.
|
| +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.
|
| +struct StackFrame {
|
| + struct StackFrame* prev;
|
| + uintptr_t return_addr;
|
| +};
|
| +
|
| +// Overwrites |dst| with StackFrame referenced in |src|. Returns true if stack
|
| +// jump was successful. Failure implies address in |src| is corrupt.
|
| +bool SafeJump(const StackFrame* src, StackFrame* dst) {
|
| + vm_size_t bytesCopied = 0;
|
| + return vm_read_overwrite(mach_task_self(), (vm_address_t)src,
|
| + (vm_size_t)sizeof(StackFrame), (vm_address_t)dst,
|
| + &bytesCopied) == KERN_SUCCESS;
|
| +}
|
| +
|
| +// Functions related to retrieving Mach-O Identifer ----------------------------
|
| +// These functions were cannibalized from Mach-O Identifier procedures found in
|
| +// breakpad/src/common/mac. Support for non-X86_64 architectures and MD5 IDs
|
| +// were removed to simplify the code.
|
| +
|
| +// 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) {
|
| + fat_header hdr;
|
| + memcpy(&hdr, module_addr, sizeof(hdr));
|
| + if (hdr.magic != FAT_MAGIC && hdr.magic != FAT_CIGAM)
|
| + return 0;
|
| +
|
| + if (hdr.magic == FAT_CIGAM)
|
| + swap_fat_header(&hdr, NXHostByteOrder());
|
| +
|
| + // Search all fat architectures for x86_64.
|
| + off_t offset = sizeof(hdr);
|
| + fat_arch arch;
|
| + for (uint32_t i = 0; i < hdr.nfat_arch; ++i) {
|
| + memcpy(&arch, (const char*)module_addr + offset, sizeof(arch));
|
| +
|
| + if (NXHostByteOrder() != NX_BigEndian)
|
| + swap_fat_arch(&arch, 1, NXHostByteOrder());
|
| +
|
| + if (arch.cputype == CPU_TYPE_X86_64)
|
| + return arch.offset;
|
| + offset += sizeof(arch);
|
| + }
|
| + 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) {
|
| + mach_header_64 hdr;
|
| + memcpy(&hdr, module_addr, sizeof(hdr));
|
| + if (hdr.magic != MH_MAGIC_64 && hdr.magic != MH_CIGAM_64)
|
| + return false;
|
| + if (hdr.magic == MH_CIGAM_64)
|
| + swap_mach_header_64(&hdr, NXHostByteOrder());
|
| + if (hdr.cputype != CPU_TYPE_X86_64)
|
| + return false;
|
| + return true;
|
| +}
|
| +
|
| +// Fills |id| with UUID of 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. 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[16], off_t offset) {
|
| + mach_header_64 hdr;
|
| + if (!memcpy(&hdr, (const char*)module_addr + offset, sizeof(hdr)) ||
|
| + (hdr.magic != MH_MAGIC_64 && hdr.magic != MH_CIGAM_64))
|
| + return false;
|
| +
|
| + bool swap = hdr.magic == MH_CIGAM_64;
|
| + if (swap)
|
| + swap_mach_header_64(&hdr, NXHostByteOrder());
|
| +
|
| + // Look through load commands for UUID command.
|
| + offset += sizeof(hdr);
|
| + for (uint32_t i = 0; i < hdr.ncmds; ++i) {
|
| + load_command cmd;
|
| + memcpy(&cmd, (const char*)module_addr + offset, sizeof(cmd));
|
| +
|
| + if (swap)
|
| + swap_load_command(&cmd, NXHostByteOrder());
|
| + if (cmd.cmd == LC_UUID) {
|
| + uuid_command uuid_cmd;
|
| + memcpy(&uuid_cmd, (const char*)module_addr + offset, sizeof(uuid_cmd));
|
| + if (swap)
|
| + swap_uuid_command(&uuid_cmd, NXHostByteOrder());
|
| + memcpy(id, &uuid_cmd, sizeof(uuid_cmd));
|
| + return true;
|
| + }
|
| + offset += cmd.cmdsize;
|
| + }
|
| + 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[16]) {
|
| + off_t offset = 0;
|
| + 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[16];
|
| + if (!GetUUID(module_addr, id))
|
| + return "";
|
| + return HexEncode(id, 16);
|
| +}
|
| +
|
| +// Functions related to grabbing a stack trace --------------------------------
|
| +
|
| +// Fills |state| with |target_thread|'s context.
|
| +bool GetThreadState(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 ebp, in which
|
| +// this function will return the number of frames up until the frame with
|
| +// a corrupt ebp. 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,
|
| + const void* instruction_pointers[]) {
|
| + ThreadContext state;
|
| + if (!GetThreadState(thread_handle, &state))
|
| + return 0;
|
| +
|
| + StackFrame frame;
|
| + frame.prev = reinterpret_cast<StackFrame*>(state.rbp);
|
| + int i = 0;
|
| + for (; i < max_stack_size; i++) {
|
| + if (!SafeJump(frame.prev, &frame) || !frame.return_addr)
|
| + return i;
|
| + instruction_pointers[i] = reinterpret_cast<const void*>(frame.return_addr);
|
| + }
|
| + return i;
|
| +}
|
| +
|
| +// Adds library referenced by |instruction_pointer| to |sample| and |modules|.
|
| +// Records library's (1) filepath, (2) base address, and (3) unique ID.
|
| +void AddModule(StackSamplingProfiler::Sample* sample,
|
| + std::vector<StackSamplingProfiler::Module>* modules,
|
| + std::map<void*, size_t>* module_to_index,
|
| + const void* instruction_pointer) {
|
| + char filepath[PATH_MAX + 1];
|
| + Dl_info inf;
|
| + dladdr(instruction_pointer, &inf);
|
| + auto loc = module_to_index->find(inf.dli_fbase);
|
| + if (loc == module_to_index->end()) {
|
| + realpath(inf.dli_fname, filepath);
|
| + StackSamplingProfiler::Module module(
|
| + inf.dli_fbase, GetUniqueId(inf.dli_fbase), base::FilePath(filepath));
|
| + modules->push_back(module);
|
| +
|
| + loc = module_to_index->insert(
|
| + std::make_pair(inf.dli_fbase, modules->size() - 1))
|
| + .first;
|
| + }
|
| + sample->push_back(
|
| + StackSamplingProfiler::Frame(instruction_pointer, loc->second));
|
| +}
|
| +
|
| +// Fills |sample| with Frames and Modules referenced by |instruction_pointers|.
|
| +void FillSample(const void* instruction_pointers[],
|
| + std::vector<StackSamplingProfiler::Module>* modules,
|
| + int stack_depth,
|
| + StackSamplingProfiler::Sample* sample) {
|
| + std::map<void*, size_t> module_index;
|
| + for (int i = 0; i < stack_depth; i++) {
|
| + AddModule(sample, modules, &module_index, instruction_pointers[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:
|
| + 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_;
|
| +
|
| + 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)) {}
|
| +
|
| +NativeStackSamplerMac::~NativeStackSamplerMac() {}
|
| +
|
| +void NativeStackSamplerMac::ProfileRecordingStarting(
|
| + std::vector<StackSamplingProfiler::Module>* modules) {
|
| + current_modules_ = modules;
|
| +}
|
| +
|
| +void NativeStackSamplerMac::RecordStackSample(
|
| + StackSamplingProfiler::Sample* sample) {
|
| + DCHECK(current_modules_);
|
| + const int max_stack_size = 64;
|
| + const void* 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_);
|
| + FillSample(instruction_pointers, current_modules_, stack_depth, sample);
|
| +}
|
| +
|
| +void NativeStackSamplerMac::ProfileRecordingStopped() {
|
| + current_modules_ = nullptr;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +scoped_ptr<NativeStackSampler> NativeStackSampler::Create(
|
| + PlatformThreadId thread_id) {
|
| +#if defined(OS_MACOSX) && !defined(OS_NACL)
|
| + return scoped_ptr<NativeStackSampler>(new NativeStackSamplerMac(thread_id));
|
| +#endif
|
| + return scoped_ptr<NativeStackSampler>();
|
| +}
|
| +
|
| +} // namespace base
|
|
|