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

Unified 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: 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 side-by-side diff with in-line comments
Download patch
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..99a313b3728100f512f1e34875b5e43ab99bbdb2
--- /dev/null
+++ b/base/profiler/native_stack_sampler_mac.cc
@@ -0,0 +1,322 @@
+// 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 <stdlib.h>
+
+#include <map>
+
+#include "base/logging.h"
Mike Wittman 2015/09/15 00:34:54 unused?
sydli 2015/09/15 01:12:20 Removed all of these.
+#include "base/md5.h"
Mike Wittman 2015/09/15 00:34:54 unused?
+#include "base/profiler/native_stack_sampler.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/time/time.h"
Mike Wittman 2015/09/15 00:34:54 unused?
+
+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 prev;
+ // Address in caller for callee to return to.
+ 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(uintptr_t src, StackFrame* dst) {
+ vm_size_t ignored_bytes_copied = 0;
+ return vm_read_overwrite(mach_task_self(), (vm_address_t)src,
Mike Wittman 2015/09/15 00:34:54 use C++-style casts
sydli 2015/09/15 01:12:19 Done.
+ (vm_size_t)sizeof(StackFrame), (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
+// 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, reinterpret_cast<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());
+
+ return (hdr.cputype == CPU_TYPE_X86_64);
Mike Wittman 2015/09/15 00:34:54 nit: no parens
sydli 2015/09/15 01:12:19 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. See spec here:
+// https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/MachORuntime/
+// 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, reinterpret_cast<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());
+
+ // Search all load commands for UUID command.
+ offset += sizeof(hdr);
+ load_command cmd;
+ for (uint32_t i = 0; i < hdr.ncmds; ++i) {
+ const void* command_loc =
+ reinterpret_cast<const char*>(module_addr) + offset;
+ memcpy(&cmd, command_loc, sizeof(cmd));
+
+ if (swap)
+ swap_load_command(&cmd, NXHostByteOrder());
+ if (cmd.cmd == LC_UUID) {
+ uuid_command uuid_cmd;
+ memcpy(&uuid_cmd, command_loc, sizeof(uuid_cmd));
+ if (swap)
+ swap_uuid_command(&uuid_cmd, NXHostByteOrder());
+
+ static_assert(sizeof(uuid_cmd.uuid) == 16,
+ "UUID field of UUID command should be 16 bytes.");
+ memcpy(id, &uuid_cmd.uuid, 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) &&
Mike Wittman 2015/09/15 00:34:54 shouldn't this be || ?
sydli 2015/09/15 01:12:19 No, if it's not an x86_64-exclusive module it coul
+ !(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) {
Mike Wittman 2015/09/15 00:34:54 can we call this GetThreadContext?
sydli 2015/09/15 01:12:19 Done.
+ 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 instruction_pointers_size,
Mike Wittman 2015/09/15 00:34:54 can we call this max_stack_size, as in the Windows
sydli 2015/09/15 01:12:19 Changed back to max_stack_size for consistency wit
+ uintptr_t instruction_pointers[]) {
+ ThreadContext state;
+ if (!GetThreadState(thread_handle, &state))
+ return 0;
+
+ StackFrame frame;
+ frame.prev = state.rbp;
+ int i = 0;
+ for (; i < instruction_pointers_size; i++) {
+ // Three cases for end-of-stack condition:
+ // 1) We tried to jump off the stack.
+ // 2) We jumped to a lower address.
+ // 3) We reached a return (instruction) address of 0.
+ uintptr_t old_esp = frame.prev;
+ if (!SafeJump(frame.prev, &frame) || frame.prev < old_esp ||
+ !frame.return_addr) {
+ return i;
+ }
+ instruction_pointers[i] = 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 uintptr_t instruction_pointer) {
+ char filepath[PATH_MAX + 1];
+ Dl_info inf;
+ dladdr(reinterpret_cast<const void*>(instruction_pointer), &inf);
+ auto module_index = module_to_index->find(inf.dli_fbase);
+ if (module_index == 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);
+
+ module_index = module_to_index->
+ insert(std::make_pair(inf.dli_fbase, modules->size() - 1)).first;
+ }
+ sample->push_back(StackSamplingProfiler::Frame(
+ reinterpret_cast<const void*>(instruction_pointer),
+ module_index->second));
+}
+
+// Fills |sample| with Frames and Modules referenced by |instruction_pointers|.
+void FillSample(const uintptr_t instruction_pointers[],
+ std::vector<StackSamplingProfiler::Module>* modules,
+ int stack_depth,
+ StackSamplingProfiler::Sample* sample) {
+ std::map<void*, size_t> module_index;
Mike Wittman 2015/09/15 00:34:54 This needs to be a member variable, as in the Wind
sydli 2015/09/15 01:12:19 Sure.
+ 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;
+
+ static const int kMaxStackSize = 64;
Mike Wittman 2015/09/15 00:34:54 this can be a local variable in RecordStackSample(
sydli 2015/09/15 01:12:19 Done.
+
+ 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)) {}
Mike Wittman 2015/09/15 00:34:54 current_modules_(nullptr)
sydli 2015/09/15 01:12:20 Done.
+
+NativeStackSamplerMac::~NativeStackSamplerMac() {}
+
+void NativeStackSamplerMac::ProfileRecordingStarting(
+ std::vector<StackSamplingProfiler::Module>* modules) {
+ current_modules_ = modules;
+}
+
+void NativeStackSamplerMac::RecordStackSample(
+ StackSamplingProfiler::Sample* sample) {
+ DCHECK(current_modules_);
+ uintptr_t instruction_pointers[kMaxStackSize] = {0};
+
+ thread_suspend(thread_handle_);
+ int stack_depth =
+ RecordStack(thread_handle_, kMaxStackSize, 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)
Mike Wittman 2015/09/15 00:34:54 you can remove the "defined(OS_MACOSX)", and exclu
sydli 2015/09/15 01:12:19 Done.
+ return scoped_ptr<NativeStackSampler>(new NativeStackSamplerMac(thread_id));
+#endif
+ return scoped_ptr<NativeStackSampler>();
+}
+
+} // namespace base

Powered by Google App Engine
This is Rietveld 408576698