microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
arm_exidx_unwinder.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 "arm_exidx_decoder.hpp"
11#include "arm_exidx_search.hpp"
12#include "arm_extab_stream.hpp"
13#include "dwarf_abi.hpp"
14#include "elf_enumerator.hpp"
15#include "frame_pointer.hpp"
16#include <cstdint>
17
18namespace microfmt {
19
20// ============================================================================
21// ARM EXIDX Table Entry Format
22// ============================================================================
23
29 uint32_t prel31_addr_offset; // Relative offset to function start
31 uint32_t unwind_data; // Inline bytecode or pointer to .ARM.extab
32};
33
34// ============================================================================
35// EXIDX Context Interface
36// ============================================================================
37
50
60template <typename AbiTraits = arm_abi_traits> struct arm_exidx_unwinder_tag {};
61
62// ============================================================================
63// EXIDX Unwinder Traits Implementation
64// ============================================================================
65
69template <typename AbiTraits> struct frame_unwinder_traits<arm_exidx_unwinder_tag<AbiTraits>> {
72
89 static bool step(value_ref<const context_type> context, register_context_ref reg_ctx, uintptr_t current_pc,
90 uintptr_t &next_fp, uintptr_t &next_pc) noexcept {
91 if (!reg_ctx)
92 return false;
93
94 if (!context->elf_img_storage || !context->enumerator)
95 return false;
96
97 // Read the current SP from register context; the untouched hardware LR
98 // is kept as the fallback return address for frames whose bytecode
99 // never explicitly restores it (see below).
100 typename AbiTraits::register_type current_sp = 0;
101 typename AbiTraits::register_type return_lr = 0;
102 if (!reg_ctx.read_raw(AbiTraits::sp_reg, &current_sp, AbiTraits::pointer_size))
103 return false;
104 if (!reg_ctx.read_raw(AbiTraits::ra_reg, &return_lr, AbiTraits::pointer_size))
105 return false;
106
107 uintptr_t fault_pc = AbiTraits::normalize_pc(current_pc);
108 uintptr_t virtual_sp = static_cast<uintptr_t>(current_sp);
109 bool unwind_applied = false;
110
111 elf_image_info &img = *context->elf_img_storage;
112 img = {};
113
114 // Query which loaded ELF module owns the faulting program counter
115 if (context->enumerator.find_by_pc(fault_pc, img) && img.has_exidx()) {
116 uintptr_t exidx_start = img.exidx_start;
117 uintptr_t exidx_end = img.exidx_end;
118 size_t num_entries = (exidx_end - exidx_start) / 8;
119
120 size_t low = 0;
121 size_t high = num_entries;
122 size_t match_index = num_entries;
123
124 // Perform binary search within this specific module's EXIDX section
125 while (low < high) {
126 size_t mid = low + (high - low) / 2;
127 uintptr_t entry_addr = exidx_start + (mid * 8);
128 uint32_t prel31 = 0;
129 if (!context->space.read_bytes(entry_addr, &prel31, 4))
130 break;
131
132 uintptr_t fn_addr = exidx_table_searcher::decode_prel31(entry_addr, prel31);
133 if (fn_addr <= fault_pc) {
134 match_index = mid;
135 low = mid + 1;
136 } else {
137 high = mid;
138 }
139 }
140
141 if (match_index != num_entries) {
142 uintptr_t word2_address = exidx_start + (match_index * 8) + 4;
143 uint32_t raw_unwind_data = 0;
144
145 if (context->space.read_bytes(word2_address, &raw_unwind_data, 4)) {
146 if (raw_unwind_data == 0x1)
147 return false;
148
149 // Per the ARM EHABI (#6.2 Generic Model / #6.3 Compact Model): bit
150 // 31 SET means the unwind opcodes are packed inline in this very
151 // word (compact model, personality index in bits 27:24); bit 31
152 // CLEAR means this word is instead a PREL31-encoded pointer to an
153 // out-of-line entry in `.ARM.extab` (generic model).
154 if ((raw_unwind_data & 0x80000000U) != 0U) {
155 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
156
157 unwind_applied = arm_exidx_bytecode_decoder::execute_bytecode(context->space, raw_unwind_data, virtual_sp,
158 fault_pc, reg_ctx);
159
160 RELOCO_END_UNSAFE_BUFFER_USAGE;
161 } else {
162 uintptr_t extab_addr = exidx_table_searcher::decode_prel31(word2_address, raw_unwind_data);
163 unwind_applied = extab_stream_executor::execute(context->space, extab_addr, virtual_sp, reg_ctx);
164 }
165 }
166 }
167 }
168
169 if (!unwind_applied)
170 return false;
171
172 const auto updated_sp = static_cast<typename AbiTraits::register_type>(virtual_sp);
173 if (!reg_ctx.write_raw(AbiTraits::sp_reg, &updated_sp, AbiTraits::pointer_size))
174 return false;
175
176 // The frame-pointer register itself depends on the live ARM/Thumb
177 // instruction-set state (R11 vs. R7); consult AbiTraits rather than
178 // assuming R11, so Thumb-compiled frames report the correct value.
179 const uint32_t fp_reg = resolve_fp_register<AbiTraits>(reg_ctx);
180 typename AbiTraits::register_type updated_fp = 0;
181 if (!reg_ctx.read_raw(fp_reg, &updated_fp, AbiTraits::pointer_size)) {
182 updated_fp = updated_sp;
183 }
184
185 typename AbiTraits::register_type updated_lr = 0;
186 if (!reg_ctx.read_raw(AbiTraits::ra_reg, &updated_lr, AbiTraits::pointer_size))
187 return false;
188
189 next_fp = static_cast<uintptr_t>(updated_fp);
190 next_pc = AbiTraits::normalize_pc(static_cast<uintptr_t>(updated_lr));
191 return true;
192 }
193};
194
234
235} // namespace microfmt
ARM EXIDX inline unwind bytecode decoder.
Binary search over sorted `.ARM.exidx` unwind tables.
Sequential multi-word `.ARM.extab` bytecode stream executor.
Type-erased, two-word handle to a remote address space.
Definition address_space.hpp:204
static RELOCO_UNSAFE_BUFFER_USAGE bool execute_bytecode(address_space_ref space, uint32_t unwind_word, uintptr_t &io_sp, uintptr_t &, register_context_ref reg_ctx) noexcept
Executes up to 3 inline unwind bytes from an exidx/extab word.
Definition arm_exidx_decoder.hpp:34
Type-erased, two-word handle to an ELF image enumerator.
Definition elf_enumerator.hpp:104
static constexpr uintptr_t decode_prel31(uintptr_t entry_addr, uint32_t prel31) noexcept
Decodes a 31-bit program-relative (PREL31) offset.
Definition arm_exidx_search.hpp:26
static bool execute(address_space_ref space, uintptr_t extab_addr, uintptr_t &io_sp, register_context_ref reg_ctx) noexcept
Runs the extab bytecode stream, updating SP and writing restored registers.
Definition arm_extab_stream.hpp:162
Type-erased, two-word handle to a frame unwinder.
Definition frame_pointer.hpp:68
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
Per-architecture ABI traits describing registers used during DWARF unwinding.
ELF image introspection, type-erased image enumeration, and fixed-capacity registries.
Type-erased frame unwinder handles, frame cursor iteration, and backtrace views.
Definition assert.hpp:19
One .ARM.exidx table entry.
Definition arm_exidx_unwinder.hpp:27
uint32_t unwind_data
Inline bytecode or pointer to .ARM.extab.
Definition arm_exidx_unwinder.hpp:31
uint32_t prel31_addr_offset
Relative (PREL31) offset to the function start.
Definition arm_exidx_unwinder.hpp:29
Immutable context describing the EXIDX unwinder back to the type-erased handle.
Definition arm_exidx_unwinder.hpp:42
address_space_ref space
Address space to unwind in.
Definition arm_exidx_unwinder.hpp:44
elf_image_enumerator_ref enumerator
ELF image enumerator used to bind PCs to modules.
Definition arm_exidx_unwinder.hpp:46
Self-contained holder owning the EXIDX scratch storage.
Definition arm_exidx_unwinder.hpp:203
arm_exidx_unwinder_holder(address_space_ref space, elf_image_enumerator_ref enumerator) noexcept
Constructs a holder bound to an address space and enumerator.
Definition arm_exidx_unwinder.hpp:214
frame_unwinder_ref make_ref() &noexcept
Builds a type-erased unwinder handle bound to this holder.
Definition arm_exidx_unwinder.hpp:230
arm_exidx_unwinder_holder & operator=(const arm_exidx_unwinder_holder &)=delete
Copy-assignment is disabled to ensure pointer stability.
arm_exidx_unwinder_holder(const arm_exidx_unwinder_holder &)=delete
Copying is disabled to ensure pointer stability.
elf_image_info img_storage
Storage for the located ELF image.
Definition arm_exidx_unwinder.hpp:205
arm_exidx_unwinder_holder & operator=(arm_exidx_unwinder_holder &&)=delete
Move-assignment is disabled to ensure pointer stability.
arm_exidx_unwinder_holder(arm_exidx_unwinder_holder &&)=delete
Moving is disabled to ensure pointer stability.
arm_exidx_unwinder_context ctx
Unwinder context referencing the owned storage.
Definition arm_exidx_unwinder.hpp:207
Tag selecting the EXIDX unwinder in the traits customization point.
Definition arm_exidx_unwinder.hpp:60
Describes a loaded ELF image and the locations of its unwind tables.
Definition elf_enumerator.hpp:30
uintptr_t exidx_end
End of the .ARM.exidx section.
Definition elf_enumerator.hpp:51
uintptr_t exidx_start
Start of the .ARM.exidx section.
Definition elf_enumerator.hpp:47
constexpr bool has_exidx() const noexcept
Reports whether a usable .ARM.exidx section is present.
Definition elf_enumerator.hpp:83
static bool step(value_ref< const context_type > context, register_context_ref reg_ctx, uintptr_t current_pc, uintptr_t &next_fp, uintptr_t &next_pc) noexcept
Unwinds one frame using the module's .ARM.exidx tables and register_context_ref.
Definition arm_exidx_unwinder.hpp:89
Static customization point describing a frame unwinder backend.
Definition frame_pointer.hpp:56