microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
dwarf_abi.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Jarosław Pelczar <jarek@jpelczar.com>
2//
3// SPDX-License-Identifier: BSD-2-Clause
4
5#pragma once
6
10#include "dwarf_registers.hpp"
11#include "gdb_registers.hpp"
12#include "register_context.hpp"
13#include <cstddef>
14#include <cstdint>
15#include <type_traits>
16#include <utility>
17
18namespace microfmt {
19
20// ============================================================================
21// ARM / Thumb (32-bit EABI)
22// ============================================================================
23
29 using register_type = uint32_t;
35 static constexpr size_t pointer_size = 4;
36
38 static constexpr uint32_t sp_reg = dwarf::arm32::sp; // R13
40 static constexpr uint32_t lr_reg = dwarf::arm32::lr; // R14
42 static constexpr uint32_t ra_reg = lr_reg;
44 static constexpr uint32_t fp_reg = dwarf::arm32::fp; // R11 (Traditional ARM)
45
46 // Frame pointer layout offsets relative to current FP, for @ref
47 // fp_unwinder_tag's naive FP-chain walking (as opposed to the
48 // EXIDX-bytecode-driven `arm_exidx_unwinder_tag`, which needs no static
49 // offset assumption at all).
50 //
51 // These describe GCC's *ARM (A32)* `-O0` prologue convention only:
52 // `push {..., fp, lr}` (any earlier callee-saved scratch registers, e.g.
53 // r4, may also be pushed) followed by `add fp, sp, <#pushed-bytes-1..2
54 // registers>`, which always lands `fp` exactly on the saved-LR word
55 // (`fp+0`), with the saved caller FP immediately below it (`fp-4`) --
56 // verified against real arm-linux-gnueabi-gcc output; this holds
57 // regardless of how many extra registers are pushed alongside {fp, lr}
58 // since fp/lr are always the last two (adjacent, ascending-register-number
59 // order) entries of the push list.
60 //
61 // Thumb/Thumb-2 mode (R7 frame pointer) does NOT follow a fixed offset:
62 // GCC emits `mov r7, sp` (or `add r7, sp, #0`) *after* allocating the
63 // local-variable area when any extra scratch register is pushed alongside
64 // {r7, lr}, so r7 ends up pointing at the locals base rather than at the
65 // {r7, lr} pair, and the true offset then depends on the (per-function,
66 // frame-size-dependent) local allocation size. There is no single constant
67 // that describes this, so `fp_unwinder_tag<arm_abi_traits>` is reliable
68 // only while executing in ARM (A32) state; treat Thumb-state fallback
69 // results as unreliable (prefer `arm_exidx_unwinder_tag`, which decodes
70 // the real bytecode/CFI instead of guessing a fixed layout).
71 static constexpr ptrdiff_t fp_slot_offset = -4; // Saved FP is at [FP - 4]
72 static constexpr ptrdiff_t ra_slot_offset = 0; // Saved LR is at [FP + 0]
73
79 [[nodiscard]] static constexpr bool is_return_address_register(uint32_t reg) noexcept { return reg == lr_reg; }
85 [[nodiscard]] static constexpr bool is_frame_pointer_register(uint32_t reg) noexcept {
86 return reg == fp_reg || reg == 7; // Support both R11 and R7 (Thumb frame pointer)
87 }
88
89 [[nodiscard]] static constexpr uintptr_t normalize_pc(uintptr_t raw_ra) noexcept {
90 // Clear the Thumb-2 mode bit (bit 0)
91 return raw_ra & ~static_cast<uintptr_t>(1);
92 }
93
109 [[nodiscard]] static uint32_t resolve_fp_reg(register_context_ref reg_ctx) noexcept {
110 uint32_t cpsr = 0;
111 if (reg_ctx && reg_ctx.read(dwarf::arm32::cpsr, cpsr)) {
112 constexpr uint32_t thumb_bit = 0x20U; // CPSR.T
113 return (cpsr & thumb_bit) != 0U ? 7U : fp_reg;
114 }
115 return fp_reg;
116 }
117
147 [[nodiscard]] static bool resolve_frame_slot_offsets(register_context_ref reg_ctx, ptrdiff_t &out_fp_slot_offset,
148 ptrdiff_t &out_ra_slot_offset) noexcept {
149 uint32_t cpsr = 0;
150 if (reg_ctx && reg_ctx.read(dwarf::arm32::cpsr, cpsr)) {
151 constexpr uint32_t thumb_bit = 0x20U; // CPSR.T
152 if ((cpsr & thumb_bit) != 0U)
153 return false;
154 }
155 out_fp_slot_offset = fp_slot_offset;
156 out_ra_slot_offset = ra_slot_offset;
157 return true;
158 }
159};
160
161// ============================================================================
162// AArch64 (64-bit ARM)
163// ============================================================================
164
170 using register_type = uint64_t;
176 static constexpr size_t pointer_size = 8;
177
179 static constexpr uint32_t sp_reg = dwarf::aarch64::sp; // X31 / SP
181 static constexpr uint32_t ra_reg = dwarf::aarch64::lr; // X30 (LR)
183 static constexpr uint32_t fp_reg = dwarf::aarch64::fp; // X29 (FP)
184
185 static constexpr ptrdiff_t fp_slot_offset = 0; // Saved FP is at [FP + 0]
186 static constexpr ptrdiff_t ra_slot_offset = 8; // Saved LR is at [FP + 8]
187
193 [[nodiscard]] static constexpr bool is_return_address_register(uint32_t reg) noexcept { return reg == ra_reg; }
199 [[nodiscard]] static constexpr bool is_frame_pointer_register(uint32_t reg) noexcept { return reg == fp_reg; }
200
201 [[nodiscard]] static constexpr uintptr_t normalize_pc(uintptr_t raw_ra) noexcept {
202 if constexpr (sizeof(uintptr_t) < sizeof(uint64_t))
203 return raw_ra & ~static_cast<uintptr_t>(1);
204
205 const uint64_t pc = static_cast<uint64_t>(raw_ra) & ~UINT64_C(1);
206
207 // Check if it's a kernel-space address (higher-half check: top bit set)
208 const bool is_kernel = (pc & (UINT64_C(1) << 63)) != 0;
209
210 if (is_kernel) {
211 return static_cast<uintptr_t>((pc & UINT64_C(0x0000FFFFFFFFFFFF)) | UINT64_C(0xFFFF000000000000));
212 } else {
213 // User-space PAC masking
214 return static_cast<uintptr_t>(pc & UINT64_C(0x0000FFFFFFFFFFFF));
215 }
216 }
217};
218
219// ============================================================================
220// RISC-V 32-bit (RV32)
221// ============================================================================
222
228 using register_type = uint32_t;
234 static constexpr size_t pointer_size = 4;
235
237 static constexpr uint32_t sp_reg = dwarf::riscv::sp; // x2
239 static constexpr uint32_t ra_reg = dwarf::riscv::ra; // x1
241 static constexpr uint32_t fp_reg = dwarf::riscv::fp; // x8 (s0/fp)
242
243 // Standard RISC-V convention (saves s0/fp and ra in the frame):
244 // [FP + 0] -> saved frame pointer (s0 / x8)
245 // [FP + 4] -> saved return address (ra / x1)
246 static constexpr ptrdiff_t fp_slot_offset = 0;
247 static constexpr ptrdiff_t ra_slot_offset = 4;
248
254 [[nodiscard]] static constexpr bool is_return_address_register(uint32_t reg) noexcept { return reg == ra_reg; }
260 [[nodiscard]] static constexpr bool is_frame_pointer_register(uint32_t reg) noexcept { return reg == fp_reg; }
261
262 [[nodiscard]] static constexpr uintptr_t normalize_pc(uintptr_t raw_ra) noexcept { return raw_ra; }
263};
264
265// ============================================================================
266// RISC-V 64-bit (RV64)
267// ============================================================================
268
274 using register_type = uint64_t;
280 static constexpr size_t pointer_size = 8;
281
283 static constexpr uint32_t sp_reg = dwarf::riscv::sp; // x2
285 static constexpr uint32_t ra_reg = dwarf::riscv::ra; // x1
287 static constexpr uint32_t fp_reg = dwarf::riscv::fp; // x8 (s0/fp)
288
289 static constexpr ptrdiff_t fp_slot_offset = 0; // Saved FP (s0) is at [FP + 0]
290 static constexpr ptrdiff_t ra_slot_offset = 8; // Saved RA is at [FP + 8]
291
297 [[nodiscard]] static constexpr bool is_return_address_register(uint32_t reg) noexcept { return reg == ra_reg; }
303 [[nodiscard]] static constexpr bool is_frame_pointer_register(uint32_t reg) noexcept { return reg == fp_reg; }
304
305 [[nodiscard]] static constexpr uintptr_t normalize_pc(uintptr_t raw_ra) noexcept { return raw_ra; }
306};
307
308// ============================================================================
309// x86 (32-bit IA-32)
310// ============================================================================
311
317 using register_type = uint32_t;
323 static constexpr size_t pointer_size = 4;
324
326 static constexpr uint32_t sp_reg = dwarf::x86::sp; // ESP (Register 4)
328 static constexpr uint32_t ra_reg = dwarf::x86::pc; // EIP (Register 8)
330 static constexpr uint32_t fp_reg = dwarf::x86::fp; // EBP (Register 5)
331
332 // On 32-bit x86:
333 // [EBP + 0] -> stores the saved previous EBP (Frame Pointer)
334 // [EBP + 4] -> stores the return address (EIP)
335 static constexpr ptrdiff_t fp_slot_offset = 0;
336 static constexpr ptrdiff_t ra_slot_offset = 4;
337
343 [[nodiscard]] static constexpr bool is_return_address_register(uint32_t reg) noexcept { return reg == ra_reg; }
349 [[nodiscard]] static constexpr bool is_frame_pointer_register(uint32_t reg) noexcept { return reg == fp_reg; }
350
351 [[nodiscard]] static constexpr uintptr_t normalize_pc(uintptr_t raw_ra) noexcept { return raw_ra; }
352};
353
354// ============================================================================
355// x86-64 (AMD64 / x86_64)
356// ============================================================================
357
363 using register_type = uint64_t;
369 static constexpr size_t pointer_size = 8;
370
372 static constexpr uint32_t sp_reg = dwarf::x86_64::sp; // RSP (Register 7)
374 static constexpr uint32_t ra_reg = dwarf::x86_64::pc; // RIP (Register 16)
376 static constexpr uint32_t fp_reg = dwarf::x86_64::fp; // RBP (Register 6)
377
378 static constexpr ptrdiff_t fp_slot_offset = 0; // Saved RBP is at [RBP + 0]
379 static constexpr ptrdiff_t ra_slot_offset = 8; // Saved RIP is at [RBP + 8]
380
386 [[nodiscard]] static constexpr bool is_return_address_register(uint32_t reg) noexcept { return reg == ra_reg; }
392 [[nodiscard]] static constexpr bool is_frame_pointer_register(uint32_t reg) noexcept { return reg == fp_reg; }
393
394 [[nodiscard]] static constexpr uintptr_t normalize_pc(uintptr_t raw_ra) noexcept { return raw_ra; }
395};
396
397namespace detail {
398
403template <typename AbiTraits, typename = void> struct has_resolve_fp_reg : std::false_type {};
404
405template <typename AbiTraits>
406struct has_resolve_fp_reg<AbiTraits,
407 std::void_t<decltype(AbiTraits::resolve_fp_reg(std::declval<register_context_ref>()))>>
408 : std::true_type {};
409
415template <typename AbiTraits, typename = void> struct has_resolve_frame_slot_offsets : std::false_type {};
416
417template <typename AbiTraits>
419 AbiTraits, std::void_t<decltype(AbiTraits::resolve_frame_slot_offsets(
420 std::declval<register_context_ref>(), std::declval<ptrdiff_t &>(), std::declval<ptrdiff_t &>()))>>
421 : std::true_type {};
422
423} // namespace detail
424
438template <typename AbiTraits> [[nodiscard]] uint32_t resolve_fp_register(register_context_ref reg_ctx) noexcept {
440 return AbiTraits::resolve_fp_reg(reg_ctx);
441 } else {
442 return AbiTraits::fp_reg;
443 }
444}
445
465template <typename AbiTraits>
466[[nodiscard]] bool resolve_frame_slot_offsets(register_context_ref reg_ctx, ptrdiff_t &out_fp_slot_offset,
467 ptrdiff_t &out_ra_slot_offset) noexcept {
469 return AbiTraits::resolve_frame_slot_offsets(reg_ctx, out_fp_slot_offset, out_ra_slot_offset);
470 } else {
471 out_fp_slot_offset = AbiTraits::fp_slot_offset;
472 out_ra_slot_offset = AbiTraits::ra_slot_offset;
473 return true;
474 }
475}
476
477template <typename Traits> struct validate_abi_traits {
478 static_assert(sizeof(typename Traits::register_type) == Traits::pointer_size,
479 "AbiTraits error: register_type size must exactly match pointer_size!");
480
481 static_assert(Traits::pointer_size == 4 || Traits::pointer_size == 8,
482 "AbiTraits error: pointer_size must be either 4 (32-bit) or 8 (64-bit)!");
483
484 static constexpr bool value = true;
485};
486
487// Enforce validation checks for all built-in traits at compile time
494
495} // namespace microfmt
Zero-allocation, type-erased handle for inspecting and modifying CPU registers from a frame context.
Definition register_context.hpp:118
#define MICROFMT_API_CLASS
Definition compat.hpp:181
Definition assert.hpp:19
uint32_t resolve_fp_register(register_context_ref reg_ctx) noexcept
Resolves the frame-pointer register to use for AbiTraits.
Definition dwarf_abi.hpp:438
bool resolve_frame_slot_offsets(register_context_ref reg_ctx, ptrdiff_t &out_fp_slot_offset, ptrdiff_t &out_ra_slot_offset) noexcept
Resolves the FP-relative saved-FP/saved-RA slot offsets to use for AbiTraits's naive FP-chain walking...
Definition dwarf_abi.hpp:466
DWARF unwind ABI traits for AArch64 (64-bit ARM).
Definition dwarf_abi.hpp:168
static constexpr uintptr_t normalize_pc(uintptr_t raw_ra) noexcept
Definition dwarf_abi.hpp:201
uint64_t register_type
Register value width.
Definition dwarf_abi.hpp:170
static constexpr bool is_frame_pointer_register(uint32_t reg) noexcept
Reports whether a register is a frame pointer.
Definition dwarf_abi.hpp:199
static constexpr bool is_return_address_register(uint32_t reg) noexcept
Reports whether a register holds the return address.
Definition dwarf_abi.hpp:193
DWARF unwind ABI traits for ARM / Thumb (32-bit EABI).
Definition dwarf_abi.hpp:27
static uint32_t resolve_fp_reg(register_context_ref reg_ctx) noexcept
Resolves which register actually holds the frame pointer for the current instruction set state.
Definition dwarf_abi.hpp:109
static constexpr bool is_frame_pointer_register(uint32_t reg) noexcept
Reports whether a register is a frame pointer.
Definition dwarf_abi.hpp:85
static constexpr bool is_return_address_register(uint32_t reg) noexcept
Reports whether a register holds the return address.
Definition dwarf_abi.hpp:79
uint32_t register_type
Register value width.
Definition dwarf_abi.hpp:29
static bool resolve_frame_slot_offsets(register_context_ref reg_ctx, ptrdiff_t &out_fp_slot_offset, ptrdiff_t &out_ra_slot_offset) noexcept
Resolves the FP-relative saved-FP/saved-RA slot offsets for the current instruction-set state,...
Definition dwarf_abi.hpp:147
static constexpr uintptr_t normalize_pc(uintptr_t raw_ra) noexcept
Definition dwarf_abi.hpp:89
Definition dwarf_abi.hpp:403
Definition dwarf_registers.hpp:384
Definition dwarf_registers.hpp:612
Definition dwarf_registers.hpp:825
Definition dwarf_registers.hpp:211
Definition dwarf_registers.hpp:121
Base template for architecture-specific GDB register traits.
Definition gdb_registers.hpp:110
DWARF unwind ABI traits for RISC-V 32-bit (RV32).
Definition dwarf_abi.hpp:226
static constexpr uintptr_t normalize_pc(uintptr_t raw_ra) noexcept
Definition dwarf_abi.hpp:262
uint32_t register_type
Register value width.
Definition dwarf_abi.hpp:228
static constexpr bool is_return_address_register(uint32_t reg) noexcept
Reports whether a register holds the return address.
Definition dwarf_abi.hpp:254
static constexpr bool is_frame_pointer_register(uint32_t reg) noexcept
Reports whether a register is a frame pointer.
Definition dwarf_abi.hpp:260
DWARF unwind ABI traits for RISC-V 64-bit (RV64).
Definition dwarf_abi.hpp:272
uint64_t register_type
Register value width.
Definition dwarf_abi.hpp:274
static constexpr bool is_frame_pointer_register(uint32_t reg) noexcept
Reports whether a register is a frame pointer.
Definition dwarf_abi.hpp:303
static constexpr bool is_return_address_register(uint32_t reg) noexcept
Reports whether a register holds the return address.
Definition dwarf_abi.hpp:297
static constexpr uintptr_t normalize_pc(uintptr_t raw_ra) noexcept
Definition dwarf_abi.hpp:305
Definition dwarf_abi.hpp:477
static constexpr bool value
Definition dwarf_abi.hpp:484
DWARF unwind ABI traits for x86-64 (AMD64 / x86_64).
Definition dwarf_abi.hpp:361
static constexpr bool is_frame_pointer_register(uint32_t reg) noexcept
Reports whether a register is a frame pointer.
Definition dwarf_abi.hpp:392
static constexpr bool is_return_address_register(uint32_t reg) noexcept
Reports whether a register holds the return address.
Definition dwarf_abi.hpp:386
uint64_t register_type
Register value width.
Definition dwarf_abi.hpp:363
static constexpr uintptr_t normalize_pc(uintptr_t raw_ra) noexcept
Definition dwarf_abi.hpp:394
DWARF unwind ABI traits for x86 (32-bit IA-32).
Definition dwarf_abi.hpp:315
static constexpr uintptr_t normalize_pc(uintptr_t raw_ra) noexcept
Definition dwarf_abi.hpp:351
static constexpr bool is_frame_pointer_register(uint32_t reg) noexcept
Reports whether a register is a frame pointer.
Definition dwarf_abi.hpp:349
uint32_t register_type
Register value width.
Definition dwarf_abi.hpp:317
static constexpr bool is_return_address_register(uint32_t reg) noexcept
Reports whether a register holds the return address.
Definition dwarf_abi.hpp:343