From b6cc95c38e651025323f7b8d949eafaaf8616f28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Holz?= Date: Thu, 9 May 2024 22:55:54 +0200 Subject: [PATCH] AK: Add a function for frame pointer-based stack unwinding Instead of duplicating stack unwinding code everywhere, introduce a new AK helper to unwind the stack in a generic way. --- AK/StackUnwinder.h | 69 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 AK/StackUnwinder.h diff --git a/AK/StackUnwinder.h b/AK/StackUnwinder.h new file mode 100644 index 00000000000..d03d8e5c9a6 --- /dev/null +++ b/AK/StackUnwinder.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024, Sönke Holz + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace AK { + +struct StackFrame { + FlatPtr return_address; + FlatPtr previous_frame_pointer; +}; + +// This function only returns errors if on_stack_frame returns an error. +// It doesn't return an error on failed memory reads, since the last frame record sometimes contains invalid addresses when using frame pointer-based unwinding. +ErrorOr unwind_stack_from_frame_pointer(FlatPtr frame_pointer, CallableAs, FlatPtr> auto read_memory, CallableAs, StackFrame> auto on_stack_frame) +{ + // aarch64/x86_64 frame record layout: + // fp/rbp+8: return address + // fp/rbp+0: previous base/frame pointer + + // riscv64 frame record layout: + // fp-8: return address + // fp-16: previous frame pointer + +#if ARCH(AARCH64) || ARCH(X86_64) + static constexpr ptrdiff_t FRAME_POINTER_RETURN_ADDRESS_OFFSET = 8; + static constexpr ptrdiff_t FRAME_POINTER_PREVIOUS_FRAME_POINTER_OFFSET = 0; +#elif ARCH(RISCV64) + static constexpr ptrdiff_t FRAME_POINTER_RETURN_ADDRESS_OFFSET = -8; + static constexpr ptrdiff_t FRAME_POINTER_PREVIOUS_FRAME_POINTER_OFFSET = -16; +#else +# error Unknown architecture +#endif + + FlatPtr current_frame_pointer = frame_pointer; + + while (current_frame_pointer != 0) { + StackFrame stack_frame; + + auto maybe_return_address = read_memory(current_frame_pointer + FRAME_POINTER_RETURN_ADDRESS_OFFSET); + if (maybe_return_address.is_error()) + return {}; + stack_frame.return_address = maybe_return_address.value(); + + if (stack_frame.return_address == 0) + return {}; + + auto maybe_previous_frame_pointer = read_memory(current_frame_pointer + FRAME_POINTER_PREVIOUS_FRAME_POINTER_OFFSET); + if (maybe_previous_frame_pointer.is_error()) + return {}; + stack_frame.previous_frame_pointer = maybe_previous_frame_pointer.value(); + + if (TRY(on_stack_frame(stack_frame)) == IterationDecision::Break) + return {}; + + current_frame_pointer = maybe_previous_frame_pointer.value(); + } + + return {}; +} + +}