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

Unified Diff: base/profiler/native_stack_sampler_posix.cc

Issue 1342453003: Stack sampling profiler to profile on Mac. TEST-- not for commit. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: BUILD.gn update 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
« no previous file with comments | « base/base.gypi ('k') | base/profiler/stack_sampling_profiler_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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>();
}
« no previous file with comments | « base/base.gypi ('k') | base/profiler/stack_sampling_profiler_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698