blob: 9874899536e0635e01486fc59303010233f4d78b [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#ifndef ZIRCON_KERNEL_LIB_ARCH_INCLUDE_LIB_ARCH_BACKTRACE_H_
#define ZIRCON_KERNEL_LIB_ARCH_INCLUDE_LIB_ARCH_BACKTRACE_H_
#include <lib/arch/internal/arch-backtrace.h>
#include <lib/stdcompat/span.h>
#include <zircon/compiler.h>
#include <cstdint>
#include <iterator>
#include <type_traits>
#include <utility>
namespace arch {
// The FramePointerBacktrace and ShadowCallStackBacktrace classes (see below)
// provide container-like APIs for safely traversing a backtrace from either
// source using forward iteration.
//
// This stores a backtrace from either of those in a fixed-sized buffer, and
// returns the number of frames stored there. The optional third argument can
// be `__builtin_return_address(0)` in the function where the backtrace was
// collected. This will be included as the innermost frame if the backtrace
// doesn't already record it there, such as when the collecting function has
// no frame pointer itself.
template <class Backtrace>
inline size_t StoreBacktrace(Backtrace&& bt, cpp20::span<uintptr_t> pcs, void* raptr = nullptr) {
if (pcs.empty()) [[unlikely]] {
return 0;
}
// Prepend the immediate return address if it's not the innermost caller
// already, in case the capturing function doesn't have a frame pointer or
// shadow-call-stack spill itself. (The latter should technically be
// impossible, since the arch::GetShadowCallStackPointer() call always
// requires a spill of the return-address register.)
uintptr_t ra = reinterpret_cast<uintptr_t>(raptr);
if (raptr && bt.empty()) {
pcs.front() = ra;
return 1;
}
size_t i = 0;
for (uintptr_t pc : bt) {
if (raptr && i == 0 && pc != ra) {
pcs[i++] = ra;
}
if (i == pcs.size()) {
break;
}
pcs[i++] = pc;
}
return i;
}
// Each frame records its caller's FP and PC (return address). A call pushes
// the PC and the prologue then pushes the caller's FP (x86), or the prologue
// pushes the return-address register and PC together (other CPUs). Since the
// stack grows down, the PC is always just after the FP in memory. It then
// sets the FP to point at (or above) the FP, PC pair just pushed. On x86 it's
// unavoidable that the FP is two words below the CFA (SP at call site), since
// the call itself puts the PC there; the FP points directly to the FP, PC pair
// describing the caller. On ARM, the compiler will often place the FP, PC
// pair at the bottom of the new frame instead of the top; the FP points
// directly to the FP, PC pair describing the caller, but there's no guarantee
// where the FP is in relation to the CFA. On RISC-V, the FP is set to the CFA
// (SP at call site / entry); so it points *just past* the FP, PC pair
// describing the caller, but it's also guaranteed to be the CFA.
struct CallFrame {
const CallFrame* fp = nullptr;
uintptr_t pc = 0;
};
// This is parameterized by the type of some object that's callable as
// `bool(const CallFrame*)` to determine whether it's safe to dereference a
// known-aligned pointer that's expected to be on the stack. The IsOnStack
// object must be default-constructible, copyable, and copy-assignable. But a
// non-default value can be passed to BackTrace().
template <typename IsOnStack>
class FramePointerBacktrace {
public:
static_assert(std::is_copy_constructible_v<IsOnStack>);
static_assert(std::is_copy_assignable_v<IsOnStack>);
// A FramePointerBacktrace is a forward iterator object that also acts as
// its own container object. So in a range-based for loop it yields a list
// of uintptr_t PC values.
using iterator = FramePointerBacktrace;
using const_iterator = iterator;
using value_type = uintptr_t;
using difference_type = ptrdiff_t;
using reference = const value_type&;
using pointer = const value_type*;
using iterator_category = std::forward_iterator_tag;
FramePointerBacktrace() = default;
FramePointerBacktrace(const FramePointerBacktrace&) = default;
FramePointerBacktrace& operator=(const FramePointerBacktrace&) = default;
// The caller evaluates the default argument to supply its own backtrace:
// `for (uintptr_t pc : FramePointerBacktrace::BackTrace()) { ... }` or
// `vector<uintptr_t>(FramePointerBacktrace::BackTrace(), FramePointerBacktrace::end())`.
// That way the immediate caller itself is not included in the backtrace.
static FramePointerBacktrace BackTrace(
const CallFrame* fp = static_cast<const CallFrame*>(__builtin_frame_address(0)),
IsOnStack is_on_stack = {}) {
FramePointerBacktrace bt;
bt.is_on_stack_ = std::move(is_on_stack);
// frame_ is a copy of the CallFrame describing the caller's caller. If
// this object instead held only a CallFrame* pointer, that would be a
// pointer into the caller's frame and so would not be valid after the
// caller returns. But since the backtrace intentionally omits the
// immediate caller, it's reasonable to expect that some callers might be
// returning the backtrace object. The increment operator just does a
// copy with arch adjustment and safety checks (alignment and on-stack).
// If the safety checks fail, the object winds up in empty / end() state.
bt.frame_.fp = fp;
++bt;
return bt;
}
static FramePointerBacktrace BackTrace(uintptr_t pc) {
return BackTrace(reinterpret_cast<const CallFrame*>(pc));
}
// Container interface.
bool empty() const { return *this == end(); }
iterator begin() const { return *this; }
static iterator end() { return {}; }
// Iterator interface.
bool operator==(const FramePointerBacktrace& other) const {
return frame_.fp == other.frame_.fp && frame_.pc == other.frame_.pc;
}
bool operator!=(const FramePointerBacktrace& other) const { return !(*this == other); }
FramePointerBacktrace& operator++() { // prefix
if (frame_.fp && IsAligned(frame_.fp)) {
const CallFrame* fp = frame_.fp + arch::internal::kArchFpOffset;
if (is_on_stack_(fp)) [[likely]] {
frame_ = *fp;
return *this;
}
}
frame_ = {};
return *this;
}
FramePointerBacktrace operator++(int) { // postfix
auto old = *this;
++*this;
return old;
}
value_type operator*() const { return frame_.pc; }
private:
static bool IsAligned(const CallFrame* fp) {
return reinterpret_cast<uintptr_t>(fp) % alignof(CallFrame) == 0;
}
CallFrame frame_;
__NO_UNIQUE_ADDRESS IsOnStack is_on_stack_{};
};
// Note there is no BackTrace() method here as in FramePointerBacktrace.
// arch::GetShadowCallStackPointer() can be used to get the upper bound of the
// currently-used shadow-call-stack, but finding the lower bound depends on the
// runtime environment's exact details. So both fetching the base address and
// checking whether the current shadow-call-stack pointer falls within the
// allocated bounds must be dealt with before passing the constructor a valid
// span of the in-use part of the shadow-call-stack.
class ShadowCallStackBacktrace {
public:
ShadowCallStackBacktrace() = default;
ShadowCallStackBacktrace(const ShadowCallStackBacktrace&) = default;
explicit ShadowCallStackBacktrace(cpp20::span<const uintptr_t> stack) : stack_(stack) {}
ShadowCallStackBacktrace(cpp20::span<const uintptr_t> stack, uintptr_t sp) : stack_(stack) {
ShrinkToSp(sp);
}
ShadowCallStackBacktrace& operator=(const ShadowCallStackBacktrace&) = default;
// The shadow call stack grows up, so iterating over frames from innermost to
// outermost has to go last to first.
bool empty() const { return stack_.empty(); }
auto begin() const { return stack_.rbegin(); }
auto end() const { return stack_.rend(); }
bool IsValidSp(uintptr_t sp) const {
return IsValidSp(sp, reinterpret_cast<uintptr_t>(stack_.data()), stack_.size_bytes());
}
static bool IsValidSp(uintptr_t sp, uintptr_t base, size_t size) {
return sp >= base && sp - base <= size && sp % sizeof(uintptr_t) == 0;
}
void ShrinkToSp(uintptr_t sp) {
if (IsValidSp(sp)) {
stack_ = stack_.subspan(0, reinterpret_cast<const uintptr_t*>(sp) - stack_.data());
} else {
stack_ = {};
}
// An outermost return address of zero can be used as a marker not to look
// further back.
while (!stack_.empty() && stack_.front() == 0) {
stack_ = stack_.subspan(1);
}
}
private:
cpp20::span<const uintptr_t> stack_;
};
#if __has_feature(shadow_call_stack)
// This has to be defined separately in assembly (not inline asm), even though
// it just fetches a register.
extern "C" uintptr_t GetShadowCallStackPointer();
#else
constexpr uintptr_t GetShadowCallStackPointer() { return 0; }
#endif
} // namespace arch
#endif // ZIRCON_KERNEL_LIB_ARCH_INCLUDE_LIB_ARCH_BACKTRACE_H_