108 lines
3.9 KiB
C++
108 lines
3.9 KiB
C++
|
|
// Copyright 2021 The Chromium Authors
|
||
|
|
// Use of this source code is governed by a BSD-style license that can be
|
||
|
|
// found in the LICENSE file.
|
||
|
|
|
||
|
|
#include "base/profiler/frame_pointer_unwinder.h"
|
||
|
|
|
||
|
|
#include "base/check_op.h"
|
||
|
|
#include "base/notreached.h"
|
||
|
|
#include "base/numerics/clamped_math.h"
|
||
|
|
#include "base/profiler/module_cache.h"
|
||
|
|
#include "build/build_config.h"
|
||
|
|
|
||
|
|
#if BUILDFLAG(IS_APPLE)
|
||
|
|
#include <pthread/stack_np.h>
|
||
|
|
#endif
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
// Given a frame pointer, returns the frame pointer of the calling stack
|
||
|
|
// frame and places the return address of the calling stack frame into
|
||
|
|
// `return_address`. Shim around `pthread_stack_frame_decode_np` where
|
||
|
|
// available since it handles pointer authentication on supported platforms.
|
||
|
|
// NB: The caller *must* ensure that there are 2+ uintptr_t's worth of memory at
|
||
|
|
// `frame_pointer`.
|
||
|
|
uintptr_t DecodeFrame(uintptr_t frame_pointer, uintptr_t* return_address) {
|
||
|
|
#if BUILDFLAG(IS_APPLE)
|
||
|
|
if (__builtin_available(macOS 10.14, iOS 12, *))
|
||
|
|
return pthread_stack_frame_decode_np(frame_pointer, return_address);
|
||
|
|
#endif
|
||
|
|
const uintptr_t* fp = reinterpret_cast<uintptr_t*>(frame_pointer);
|
||
|
|
uintptr_t next_frame = *fp;
|
||
|
|
*return_address = *(fp + 1);
|
||
|
|
return next_frame;
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
namespace base {
|
||
|
|
|
||
|
|
FramePointerUnwinder::FramePointerUnwinder() = default;
|
||
|
|
|
||
|
|
bool FramePointerUnwinder::CanUnwindFrom(const Frame& current_frame) const {
|
||
|
|
return current_frame.module && current_frame.module->IsNative();
|
||
|
|
}
|
||
|
|
|
||
|
|
UnwindResult FramePointerUnwinder::TryUnwind(RegisterContext* thread_context,
|
||
|
|
uintptr_t stack_top,
|
||
|
|
std::vector<Frame>* stack) {
|
||
|
|
// We expect the frame corresponding to the |thread_context| register state to
|
||
|
|
// exist within |stack|.
|
||
|
|
DCHECK_GT(stack->size(), 0u);
|
||
|
|
#if defined(ARCH_CPU_ARM64)
|
||
|
|
constexpr uintptr_t align_mask = 0x1;
|
||
|
|
#elif defined(ARCH_CPU_X86_64)
|
||
|
|
constexpr uintptr_t align_mask = 0xf;
|
||
|
|
#endif
|
||
|
|
|
||
|
|
uintptr_t next_frame = RegisterContextFramePointer(thread_context);
|
||
|
|
uintptr_t frame_lower_bound = RegisterContextStackPointer(thread_context);
|
||
|
|
const auto is_fp_valid = [&](uintptr_t fp) {
|
||
|
|
// Ensure there's space on the stack to read two values: the caller's
|
||
|
|
// frame pointer and the return address.
|
||
|
|
return next_frame >= frame_lower_bound &&
|
||
|
|
ClampAdd(next_frame, sizeof(uintptr_t) * 2) <= stack_top &&
|
||
|
|
(next_frame & align_mask) == 0;
|
||
|
|
};
|
||
|
|
if (!is_fp_valid(next_frame))
|
||
|
|
return UnwindResult::kAborted;
|
||
|
|
|
||
|
|
for (;;) {
|
||
|
|
if (!stack->back().module) {
|
||
|
|
return UnwindResult::kAborted;
|
||
|
|
}
|
||
|
|
if (!stack->back().module->IsNative()) {
|
||
|
|
// This is a non-native module associated with the auxiliary unwinder
|
||
|
|
// (e.g. corresponding to a frame in V8 generated code). Report as
|
||
|
|
// UNRECOGNIZED_FRAME to allow that unwinder to unwind the frame.
|
||
|
|
return UnwindResult::kUnrecognizedFrame;
|
||
|
|
}
|
||
|
|
uintptr_t retaddr;
|
||
|
|
uintptr_t frame = next_frame;
|
||
|
|
next_frame = DecodeFrame(frame, &retaddr);
|
||
|
|
frame_lower_bound = frame + 1;
|
||
|
|
// If `next_frame` is 0, we've hit the root and `retaddr` isn't useful.
|
||
|
|
// Bail without recording the frame.
|
||
|
|
if (next_frame == 0)
|
||
|
|
return UnwindResult::kCompleted;
|
||
|
|
const ModuleCache::Module* module =
|
||
|
|
module_cache()->GetModuleForAddress(retaddr);
|
||
|
|
// V8 doesn't conform to the x86_64 ABI re: stack alignment. For V8 frames,
|
||
|
|
// let the V8 unwinder determine whether the FP is valid or not.
|
||
|
|
bool is_non_native_module = module && !module->IsNative();
|
||
|
|
// If the FP doesn't look correct, don't record this frame.
|
||
|
|
if (!is_non_native_module && !is_fp_valid(next_frame))
|
||
|
|
return UnwindResult::kAborted;
|
||
|
|
|
||
|
|
RegisterContextFramePointer(thread_context) = next_frame;
|
||
|
|
RegisterContextInstructionPointer(thread_context) = retaddr;
|
||
|
|
RegisterContextStackPointer(thread_context) = frame + sizeof(uintptr_t) * 2;
|
||
|
|
stack->emplace_back(retaddr, module);
|
||
|
|
}
|
||
|
|
|
||
|
|
NOTREACHED();
|
||
|
|
return UnwindResult::kCompleted;
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace base
|