Index: base/profiler/native_stack_sampler_posix.cc |
diff --git a/base/profiler/native_stack_sampler_posix.cc b/base/profiler/native_stack_sampler_posix.cc |
index bce37e10c3af76d27be11cb97b2ea5b343ef020d..736270ff57fdf6a8381f45c27e2e8b0a9069961c 100644 |
--- a/base/profiler/native_stack_sampler_posix.cc |
+++ b/base/profiler/native_stack_sampler_posix.cc |
@@ -2,12 +2,324 @@ |
// 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/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. |
+ |
+// Converts |identifier| to a hexstring and writes to |id_dst|. |
+void ConvertIdentifierToString(const unsigned char identifier[16], |
Robert Sesek
2015/09/14 18:55:54
Can you use HexEncode() from base/strings/string_n
|
+ std::string* id_dst) { |
+ char buffer[33]; |
+ int buffer_idx = 0; |
+ for (int idx = 0; idx < 16; ++idx) { |
+ int hi = (identifier[idx] >> 4) & 0x0F; |
+ int lo = (identifier[idx]) & 0x0F; |
+ |
+ buffer[buffer_idx++] = |
+ static_cast<char>((hi >= 10) ? ('A' + hi - 10) : ('0' + hi)); |
+ buffer[buffer_idx++] = |
+ static_cast<char>((lo >= 10) ? ('A' + lo - 10) : ('0' + lo)); |
+ } |
+ |
+ // NULL terminate |
+ buffer[32] = 0; |
+ *id_dst = std::string(buffer); |
+} |
+ |
+// Returns offset in bytes where the x86_64 file header is located in |fd|. |
+// Returns 0 if any read fails, or |fd| is not a valid FAT Mach-O file or has |
+// not been built for x86_64. |
+off_t GetMach64HeaderOffset(int fd) { |
+ fat_header hdr; |
+ if (!pread(fd, &hdr, sizeof(hdr), 0) || |
+ (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) { |
+ if (!pread(fd, &arch, sizeof(arch), offset)) |
+ return 0; |
+ |
+ 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 file referenced by |fd| is built specifically for |
+// x86_64 architectures. Also returns false if read fails. |
+bool IsX64Header(int fd) { |
+ mach_header_64 hdr; |
+ if (!pread(fd, &hdr, sizeof(hdr), 0) || |
+ (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 file specified by |fd|. |offset| is |
+// the offset in bytes into |fd|, where the x86_64 header is located. Returns |
+// false if header is malformed, a read fails, or the header does not specify |
+// the UUID load command. |
+bool GetX64UUIDAt(int fd, unsigned char id[16], off_t offset) { |
+ mach_header_64 hdr; |
+ if (!pread(fd, &hdr, sizeof(hdr), offset) || |
+ (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; |
+ if (!pread(fd, &cmd, sizeof(cmd), offset)) |
+ return false; |
+ |
+ if (swap) |
+ swap_load_command(&cmd, NXHostByteOrder()); |
+ if (cmd.cmd == LC_UUID) { |
+ uuid_command uuid_cmd; |
+ if (!pread(fd, &uuid_cmd, sizeof(uuid_cmd), offset)) |
+ return false; |
+ 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 file specified by |fd|. |
+// This function returns false if |fd| has not been built for X86_64 or if UUID |
+// cannot be found. |
+bool GetUUID(int fd, unsigned char id[16]) { |
+ off_t offset = 0; |
+ if (!IsX64Header(fd) && !(offset = GetMach64HeaderOffset(fd))) |
+ return false; |
+ return GetX64UUIDAt(fd, id, offset); |
+} |
+ |
+// Obtains an ID of binary specified by |path| and loads it into |id_dst|. |
+// Returns false and sets |id_dst| to empty string if GetUUID fails. |
+bool GetUniqueId(const char* path, std::string* id_dst) { |
+ unsigned char id[16]; |
+ int fd = open(path, O_RDONLY); |
+ if (!GetUUID(fd, id)) { |
+ close(fd); |
+ *id_dst = ""; |
+ return false; |
+ } |
+ close(fd); |
+ ConvertIdentifierToString(id, id_dst); |
+ return true; |
+} |
+ |
+// 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); |
+ std::string unique_id; |
+ GetUniqueId(inf.dli_fname, &unique_id); |
Robert Sesek
2015/09/14 18:55:54
Touching disk here is going to be a huge hit on so
|
+ StackSamplingProfiler::Module module(inf.dli_fbase, unique_id, |
+ 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 { |
Robert Sesek
2015/09/14 18:55:54
This should move into native_stack_sampler_mac.cc
|
+ 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 type mach_port_t. |
+// (See base/threading/platform_thread_posix.cc:128) |
Robert Sesek
2015/09/14 18:55:54
This seems like a bug. May want to file one for th
|
+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) |
+ return scoped_ptr<NativeStackSampler>(new NativeStackSamplerMac(thread_id)); |
+#endif |
return scoped_ptr<NativeStackSampler>(); |
} |