Chromium Code Reviews| 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..19d53b525b9458e7085480910530fe78c1d72011 |
| --- /dev/null |
| +++ b/base/profiler/native_stack_sampler_mac.cc |
| @@ -0,0 +1,313 @@ |
| +// 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 { |
| + |
|
robliao
2015/09/14 21:58:17
Nit: Remove extra line.
sydli
2015/09/14 23:00:13
Done.
|
| + |
| +// 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). |
| + struct StackFrame* 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(const StackFrame* src, StackFrame* dst) { |
|
robliao
2015/09/14 21:58:17
Can src be a const StackFrame& ?
sydli
2015/09/14 23:00:13
Don't think so; I pass in frame.prev (which is a S
|
| + vm_size_t bytesCopied = 0; |
|
robliao
2015/09/14 21:58:16
ignored_bytes_copied
sydli
2015/09/14 23:00:13
Done.
|
| + 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 ---------------------------- |
|
robliao
2015/09/14 21:58:17
Remove trailing -'s
sydli
2015/09/14 23:00:13
Done.
|
| +// |
| +// 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)); |
|
robliao
2015/09/14 21:58:17
Safety: Where do we get the assurance that module_
sydli
2015/09/14 23:00:13
This is an assumption we make since |module_addr|
robliao
2015/09/14 23:28:05
The sections don't appear to have size info, so I
|
| + 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)); |
|
robliao
2015/09/14 21:58:17
Would casting module_addr+offset to a fat_arch[] w
sydli
2015/09/14 23:00:13
module_addr has to be a pointer of a byte-wide typ
robliao
2015/09/14 23:28:05
const char* is likely what you want since you're o
|
| + |
| + 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) |
|
robliao
2015/09/14 21:58:16
return hdr.cputype == CPU_TYPE_X86_64;
sydli
2015/09/14 23:00:13
Done.
|
| + return false; |
| + return true; |
| +} |
| + |
| +// Fills |id| with UUID of x86_64 Mach-O binary loaded at |module_addr|. |
|
robliao
2015/09/14 21:58:17
Nit: {the} UUID of {the} x86_64
sydli
2015/09/14 23:00:13
Done.
|
| +// |offset| is the offset in bytes into |module_addr| where the x86_64 header |
|
robliao
2015/09/14 21:58:17
It might be instructive to do some ASCII Art towar
sydli
2015/09/14 23:00:13
This is only accurate for FAT Mach-O headers. Upda
robliao
2015/09/14 23:28:05
The link is good, but the diagram is more helpful
sydli
2015/09/15 00:20:13
Ok.
|
| +// is located. |offset| should be non-zero 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[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; |
|
robliao
2015/09/14 21:58:17
When a conditional spans multiple lines, use brace
sydli
2015/09/14 23:00:13
Done.
robliao
2015/09/14 23:28:05
I assume this is the one you forgot?
sydli
2015/09/15 00:20:13
Yep! Added it in new patchset.
|
| + |
| + 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); |
| + for (uint32_t i = 0; i < hdr.ncmds; ++i) { |
| + load_command cmd; |
|
robliao
2015/09/14 21:58:17
Move cmd outside the loop to avoid constructing th
sydli
2015/09/14 23:00:13
Done.
|
| + memcpy(&cmd, (const char*)module_addr + offset, sizeof(cmd)); |
|
robliao
2015/09/14 21:58:16
Could represent module_addr + offset as a temp val
sydli
2015/09/14 23:00:13
Done.
|
| + |
| + 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)); |
|
robliao
2015/09/14 21:58:17
Safety: This copy probably should be no longer tha
sydli
2015/09/14 23:00:13
Done.
|
| + 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 |
|
robliao
2015/09/14 21:58:17
corrupt ebp or missing ebp or both?
sydli
2015/09/14 23:00:13
A corrupt base pointer implies that it is missing
|
| +// 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, |
|
robliao
2015/09/14 21:58:17
Maybe instruction_pointers_size to make it obvious
sydli
2015/09/14 23:00:14
Done.
|
| + const void* instruction_pointers[]) { |
|
robliao
2015/09/14 21:58:17
Should this be const uintptr_t?
sydli
2015/09/14 23:00:13
Done. Changed where appropriate.
|
| + 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++) { |
| + // 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. |
| + StackFrame* old_esp = frame.prev; |
| + if (!SafeJump(frame.prev, &frame) || frame.prev < old_esp || |
| + !frame.return_addr) |
| + return i; |
|
robliao
2015/09/14 21:58:17
When a conditional spans multiple lines, add brace
sydli
2015/09/14 23:00:13
Done.
|
| + 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); |
|
robliao
2015/09/14 21:58:16
What is loc?
sydli
2015/09/14 23:00:13
Pair result from module -> index map. Updated name
|
| + 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( |
|
robliao
2015/09/14 21:58:17
Linebreak after the -> and reflow.
sydli
2015/09/14 23:00:13
Done.
|
| + 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; |
|
robliao
2015/09/14 21:58:17
MAX_STACK_SIZE. Consider moving this to the top of
sydli
2015/09/14 23:00:13
Done. Made this a static const in NativeStackSampl
|
| + 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 |