blob: 907a6a0a39f0d408b74250480bb74fe0278a670b [file] [log] [blame]
// Copyright 2017 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 "base/profiler/native_stack_sampler.h"
#include <libkern/OSByteOrder.h>
#include <libunwind.h>
#include <mach-o/compact_unwind_encoding.h>
#include <mach-o/getsect.h>
#include <mach-o/swap.h>
#include <mach/kern_return.h>
#include <mach/mach.h>
#include <mach/thread_act.h>
#include <mach/vm_map.h>
#include <pthread.h>
#include <sys/ptrace.h>
#include <sys/resource.h>
#include <sys/syslimits.h>
#include <algorithm>
#include <memory>
#include "base/logging.h"
#include "base/mac/mach_logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/sampling_heap_profiler/module_cache.h"
#include "base/strings/string_number_conversions.h"
extern "C" {
void _sigtramp(int, int, struct sigset*);
namespace base {
using Frame = StackSamplingProfiler::Frame;
using ProfileBuilder = StackSamplingProfiler::ProfileBuilder;
namespace {
// Stack walking --------------------------------------------------------------
// Fills |state| with |target_thread|'s context.
// Note that this is called while a thread is suspended. Make very very sure
// that no shared resources (e.g. memory allocators) are used for the duration
// of this function.
bool GetThreadState(thread_act_t target_thread, x86_thread_state64_t* state) {
auto count = static_cast<mach_msg_type_number_t>(x86_THREAD_STATE64_COUNT);
return thread_get_state(target_thread, x86_THREAD_STATE64,
&count) == KERN_SUCCESS;
// If the value at |pointer| points to the original stack, rewrites it to point
// to the corresponding location in the copied stack.
// Note that this is called while a thread is suspended. Make very very sure
// that no shared resources (e.g. memory allocators) are used for the duration
// of this function.
uintptr_t RewritePointerIfInOriginalStack(
const uintptr_t* original_stack_bottom,
const uintptr_t* original_stack_top,
uintptr_t* stack_copy_bottom,
uintptr_t pointer) {
auto original_stack_bottom_int =
auto original_stack_top_int = reinterpret_cast<uintptr_t>(original_stack_top);
auto stack_copy_bottom_int = reinterpret_cast<uintptr_t>(stack_copy_bottom);
if (pointer < original_stack_bottom_int || pointer >= original_stack_top_int)
return pointer;
return stack_copy_bottom_int + (pointer - original_stack_bottom_int);
// Copies the stack to a buffer while rewriting possible pointers to locations
// within the stack to point to the corresponding locations in the copy. This is
// necessary to handle stack frames with dynamic stack allocation, where a
// pointer to the beginning of the dynamic allocation area is stored on the
// stack and/or in a non-volatile register.
// Eager rewriting of anything that looks like a pointer to the stack, as done
// in this function, does not adversely affect the stack unwinding. The only
// other values on the stack the unwinding depends on are return addresses,
// which should not point within the stack memory. The rewriting is guaranteed
// to catch all pointers because the stacks are guaranteed by the ABI to be
// sizeof(void*) aligned.
// Note that this is called while a thread is suspended. Make very very sure
// that no shared resources (e.g. memory allocators) are used for the duration
// of this function.
void CopyStackAndRewritePointers(uintptr_t* stack_copy_bottom,
const uintptr_t* original_stack_bottom,
const uintptr_t* original_stack_top,
x86_thread_state64_t* thread_state)
NO_SANITIZE("address") {
size_t count = original_stack_top - original_stack_bottom;
for (size_t pos = 0; pos < count; ++pos) {
stack_copy_bottom[pos] = RewritePointerIfInOriginalStack(
original_stack_bottom, original_stack_top, stack_copy_bottom,
uint64_t* rewrite_registers[] = {&thread_state->__rbx, &thread_state->__rbp,
&thread_state->__rsp, &thread_state->__r12,
&thread_state->__r13, &thread_state->__r14,
for (auto* reg : rewrite_registers) {
*reg = RewritePointerIfInOriginalStack(
original_stack_bottom, original_stack_top, stack_copy_bottom, *reg);
// Extracts the "frame offset" for a given frame from the compact unwind info.
// A frame offset indicates the location of saved non-volatile registers in
// relation to the frame pointer. See |mach-o/compact_unwind_encoding.h| for
// details.
uint32_t GetFrameOffset(int compact_unwind_info) {
// The frame offset lives in bytes 16-23. This shifts it down by the number of
// leading zeroes in the mask, then masks with (1 << number of one bits in the
// mask) - 1, turning 0x00FF0000 into 0x000000FF. Adapted from |EXTRACT_BITS|
// in libunwind's CompactUnwinder.hpp.
return (
(compact_unwind_info >> __builtin_ctz(UNWIND_X86_64_RBP_FRAME_OFFSET)) &
(((1 << __builtin_popcount(UNWIND_X86_64_RBP_FRAME_OFFSET))) - 1));
// True if the unwind from |leaf_frame_module| may trigger a crash bug in
// unw_init_local. If so, the stack walk should be aborted at the leaf frame.
bool MayTriggerUnwInitLocalCrash(const ModuleCache::Module* leaf_frame_module) {
// The issue here is a bug in unw_init_local that, in some unwinds, results in
// attempts to access memory at the address immediately following the address
// range of the library. When the library is the last of the mapped libraries
// that address is in a different memory region. Starting with 10.13.4 beta
// releases it appears that this region is sometimes either unmapped or mapped
// without read access, resulting in crashes on the attempted access. It's not
// clear what circumstances result in this situation; attempts to reproduce on
// a 10.13.4 beta did not trigger the issue.
// The workaround is to check if the memory address that would be accessed is
// readable, and if not, abort the stack walk before calling unw_init_local.
// As of 2018/03/19 about 0.1% of non-idle stacks on the UI and GPU main
// threads have a leaf frame in the last library. Since the issue appears to
// only occur some of the time it's expected that the quantity of lost samples
// will be lower than 0.1%, possibly significantly lower.
// TODO(lgrey): Add references above to LLVM/Radar bugs on unw_init_local once
// filed.
uint64_t unused;
vm_size_t size = sizeof(unused);
return vm_read_overwrite(
leaf_frame_module->GetBaseAddress() + leaf_frame_module->GetSize(),
sizeof(unused), reinterpret_cast<vm_address_t>(&unused),
&size) != 0;
// Check if the cursor contains a valid-looking frame pointer for frame pointer
// unwinds. If the stack frame has a frame pointer, stepping the cursor will
// involve indexing memory access off of that pointer. In that case,
// sanity-check the frame pointer register to ensure it's within bounds.
// Additionally, the stack frame might be in a prologue or epilogue, which can
// cause a crash when the unwinder attempts to access non-volatile registers
// that have not yet been pushed, or have already been popped from the
// stack. libwunwind will try to restore those registers using an offset from
// the frame pointer. However, since we copy the stack from RSP up, any
// locations below the stack pointer are before the beginning of the stack
// buffer. Account for this by checking that the expected location is above the
// stack pointer, and rejecting the sample if it isn't.
bool HasValidRbp(unw_cursor_t* unwind_cursor, uintptr_t stack_top) {
unw_proc_info_t proc_info;
unw_get_proc_info(unwind_cursor, &proc_info);
if ((proc_info.format & UNWIND_X86_64_MODE_MASK) ==
unw_word_t rsp, rbp;
unw_get_reg(unwind_cursor, UNW_X86_64_RSP, &rsp);
unw_get_reg(unwind_cursor, UNW_X86_64_RBP, &rbp);
uint32_t offset = GetFrameOffset(proc_info.format) * sizeof(unw_word_t);
if (rbp < offset || (rbp - offset) < rsp || rbp > stack_top)
return false;
return true;
const ModuleCache::Module* GetLibSystemKernelModule(ModuleCache* module_cache) {
const ModuleCache::Module* module =
DCHECK_EQ(FilePath("libsystem_kernel.dylib"), module->GetDebugBasename());
return module;
void GetSigtrampRange(uintptr_t* start, uintptr_t* end) {
auto address = reinterpret_cast<uintptr_t>(&_sigtramp);
DCHECK(address != 0);
*start = address;
unw_context_t context;
unw_cursor_t cursor;
unw_proc_info_t info;
// Set the context's RIP to the beginning of sigtramp,
// +1 byte to work around a bug in 10.11 ([16] = address + 1;
unw_init_local(&cursor, &context);
unw_get_proc_info(&cursor, &info);
DCHECK_EQ(info.start_ip, address);
*end = info.end_ip;
// ScopedSuspendThread --------------------------------------------------------
// Suspends a thread for the lifetime of the object.
class ScopedSuspendThread {
explicit ScopedSuspendThread(mach_port_t thread_port)
: thread_port_(thread_suspend(thread_port) == KERN_SUCCESS
? thread_port
~ScopedSuspendThread() {
if (!was_successful())
kern_return_t kr = thread_resume(thread_port_);
MACH_CHECK(kr == KERN_SUCCESS, kr) << "thread_resume";
bool was_successful() const { return thread_port_ != MACH_PORT_NULL; }
mach_port_t thread_port_;
} // namespace
// NativeStackSamplerMac ------------------------------------------------------
class NativeStackSamplerMac : public NativeStackSampler {
NativeStackSamplerMac(mach_port_t thread_port,
ModuleCache* module_cache,
NativeStackSamplerTestDelegate* test_delegate);
~NativeStackSamplerMac() override;
// StackSamplingProfiler::NativeStackSampler:
void RecordStackFrames(StackBuffer* stack_buffer,
ProfileBuilder* profile_builder) override;
// Walks the stack represented by |thread_state|, calling back to the
// provided lambda for each frame.
std::vector<Frame> WalkStack(const x86_thread_state64_t& thread_state,
uintptr_t stack_top);
// Weak reference: Mach port for thread being profiled.
mach_port_t thread_port_;
// Maps a module's address range to the module.
ModuleCache* const module_cache_;
NativeStackSamplerTestDelegate* const test_delegate_;
// The stack base address corresponding to |thread_handle_|.
const void* const thread_stack_base_address_;
// Cached pointer to the libsystem_kernel module.
const ModuleCache::Module* const libsystem_kernel_module_;
// The address range of |_sigtramp|, the signal trampoline function.
uintptr_t sigtramp_start_;
uintptr_t sigtramp_end_;
mach_port_t thread_port,
ModuleCache* module_cache,
NativeStackSamplerTestDelegate* test_delegate)
: thread_port_(thread_port),
libsystem_kernel_module_(GetLibSystemKernelModule(module_cache)) {
GetSigtrampRange(&sigtramp_start_, &sigtramp_end_);
// This class suspends threads, and those threads might be suspended in dyld.
// Therefore, for all the system functions that might be linked in dynamically
// that are used while threads are suspended, make calls to them to make sure
// that they are linked up.
x86_thread_state64_t thread_state;
GetThreadState(thread_port_, &thread_state);
NativeStackSamplerMac::~NativeStackSamplerMac() {}
void NativeStackSamplerMac::RecordStackFrames(StackBuffer* stack_buffer,
ProfileBuilder* profile_builder) {
x86_thread_state64_t thread_state;
// Copy the stack.
uintptr_t new_stack_top = 0;
// IMPORTANT NOTE: Do not do ANYTHING in this in this scope that might
// allocate memory, including indirectly via use of DCHECK/CHECK or other
// logging statements. Otherwise this code can deadlock on heap locks in the
// default heap acquired by the target thread before it was suspended.
ScopedSuspendThread suspend_thread(thread_port_);
if (!suspend_thread.was_successful())
if (!GetThreadState(thread_port_, &thread_state))
auto stack_top = reinterpret_cast<uintptr_t>(thread_stack_base_address_);
uintptr_t stack_bottom = thread_state.__rsp;
if (stack_bottom >= stack_top)
uintptr_t stack_size = stack_top - stack_bottom;
if (stack_size > stack_buffer->size())
reinterpret_cast<uintptr_t*>(stack_top), &thread_state);
new_stack_top =
reinterpret_cast<uintptr_t>(stack_buffer->buffer()) + stack_size;
} // ScopedSuspendThread
if (test_delegate_)
// Walk the stack and record it.
profile_builder->OnSampleCompleted(WalkStack(thread_state, new_stack_top));
std::vector<Frame> NativeStackSamplerMac::WalkStack(
const x86_thread_state64_t& thread_state,
uintptr_t stack_top) {
std::vector<Frame> stack;
// Reserve enough memory for most stacks, to avoid repeated
// allocations. Approximately 99.9% of recorded stacks are 128 frames or
// fewer.
// There isn't an official way to create a unw_context other than to create it
// from the current state of the current thread's stack. Since we're walking a
// different thread's stack we must forge a context. The unw_context is just a
// copy of the 16 main registers followed by the instruction pointer, nothing
// more. Coincidentally, the first 17 items of the x86_thread_state64_t type
// are exactly those registers in exactly the same order, so just bulk copy
// them over.
unw_context_t unwind_context;
memcpy(&unwind_context, &thread_state, sizeof(uintptr_t) * 17);
// Avoid an out-of-bounds read bug in libunwind that can crash us in some
// circumstances. If we're subject to that case, just record the first frame
// and bail. See MayTriggerUnwInitLocalCrash for details.
const ModuleCache::Module* leaf_frame_module =
if (leaf_frame_module && MayTriggerUnwInitLocalCrash(leaf_frame_module)) {
return {Frame(thread_state.__rip, leaf_frame_module)};
unw_cursor_t unwind_cursor;
unw_init_local(&unwind_cursor, &unwind_context);
bool at_top_frame = true;
int step_result;
do {
unw_word_t instruction_pointer;
unw_get_reg(&unwind_cursor, UNW_REG_IP, &instruction_pointer);
// Ensure IP is in a module.
// Frameless unwinding (non-DWARF) works by fetching the function's stack
// size from the unwind encoding or stack, and adding it to the stack
// pointer to determine the function's return address.
// If we're in a function prologue or epilogue, the actual stack size may be
// smaller than it will be during the normal course of execution. When
// libunwind adds the expected stack size, it will look for the return
// address in the wrong place. This check should ensure that we bail before
// trying to deref a bad IP obtained this way in the previous frame.
const ModuleCache::Module* module =
if (!module)
// Record the frame.
stack.emplace_back(instruction_pointer, module);
// Don't continue if we're in sigtramp. Unwinding this from another thread
// is very fragile. It's a complex DWARF unwind that needs to restore the
// entire thread context which was saved by the kernel when the interrupt
// occurred.
if (instruction_pointer >= sigtramp_start_ &&
instruction_pointer < sigtramp_end_)
// Don't continue if rbp appears to be invalid (due to a previous bad
// unwind).
if (!HasValidRbp(&unwind_cursor, stack_top))
step_result = unw_step(&unwind_cursor);
if (step_result == 0 && at_top_frame) {
// libunwind is designed to be triggered by user code on their own thread,
// if it hits a library that has no unwind info for the function that is
// being executed, it just stops. This isn't a problem in the normal case,
// but in this case, it's quite possible that the stack being walked is
// stopped in a function that bridges to the kernel and thus is missing
// the unwind info.
// For now, just unwind the single case where the thread is stopped in a
// function in libsystem_kernel.
uint64_t& rsp =[7];
uint64_t& rip =[16];
if (module_cache_->GetModuleForAddress(rip) == libsystem_kernel_module_) {
rip = *reinterpret_cast<uint64_t*>(rsp);
rsp += 8;
// Reset the cursor.
unw_init_local(&unwind_cursor, &unwind_context);
// Mock a successful step_result.
step_result = 1;
at_top_frame = false;
} while (step_result > 0);
return stack;
// NativeStackSampler ---------------------------------------------------------
// static
std::unique_ptr<NativeStackSampler> NativeStackSampler::Create(
PlatformThreadId thread_id,
ModuleCache* module_cache,
NativeStackSamplerTestDelegate* test_delegate) {
return std::make_unique<NativeStackSamplerMac>(thread_id, module_cache,
// static
size_t NativeStackSampler::GetStackBufferSize() {
size_t stack_size = PlatformThread::GetDefaultThreadStackSize();
// If getrlimit somehow fails, return the default macOS main thread stack size
// of 8 MB (DFLSSIZ in <i386/vmparam.h>) with extra wiggle room.
return stack_size > 0 ? stack_size : 12 * 1024 * 1024;
} // namespace base