microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
dwarf_decoder.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 "address_space.hpp"
11#include "dwarf_abi.hpp"
12#include "dwarf_registers.hpp"
13#include "elf_enumerator.hpp"
14#include "frame_pointer.hpp"
15#include "register_context.hpp"
16#include <cstddef>
17#include <cstdint>
18#include <tuple>
19
20namespace microfmt {
21
28template <typename AbiTraits> class dwarf_cfi_decoder {
29public:
30 using register_type = typename AbiTraits::register_type;
31
32 static bool read_uleb128(address_space_ref space, uintptr_t &addr, uint64_t &out_val) noexcept {
33 uint64_t result = 0;
34 unsigned shift = 0;
35 uint8_t byte = 0;
36 for (int i = 0; i < 10; ++i) {
37 if (!space.read_bytes(addr++, &byte, 1))
38 return false;
39 result |= static_cast<uint64_t>(byte & 0x7F) << shift;
40 if (!(byte & 0x80)) {
41 out_val = result;
42 return true;
43 }
44 shift += 7;
45 }
46 return false;
47 }
48
49 static bool read_sleb128(address_space_ref space, uintptr_t &addr, int64_t &out_val) noexcept {
50 int64_t result = 0;
51 unsigned shift = 0;
52 uint8_t byte = 0;
53 for (int i = 0; i < 10; ++i) {
54 if (!space.read_bytes(addr++, &byte, 1))
55 return false;
56 result |= static_cast<int64_t>(byte & 0x7F) << shift;
57 shift += 7;
58 if (!(byte & 0x80)) {
59 if ((shift < 64) && (byte & 0x40)) {
60 result |= (~0ULL << shift);
61 }
62 out_val = result;
63 return true;
64 }
65 }
66 return false;
67 }
68
69 // ------------------------------------------------------------------------
70 // DWARF CFI Opcodes
71 // ------------------------------------------------------------------------
72 static constexpr uint8_t DW_CFA_advance_loc = 0x40;
73 static constexpr uint8_t DW_CFA_offset = 0x80;
74 static constexpr uint8_t DW_CFA_restore = 0xC0;
75 static constexpr uint8_t DW_CFA_nop = 0x00;
76 static constexpr uint8_t DW_CFA_register = 0x09; // Register-to-register rule
77 static constexpr uint8_t DW_CFA_advance_loc1 = 0x02;
78 static constexpr uint8_t DW_CFA_advance_loc2 = 0x03;
79 static constexpr uint8_t DW_CFA_offset_extended = 0x05;
80 static constexpr uint8_t DW_CFA_remember_state = 0x0A;
81 static constexpr uint8_t DW_CFA_restore_state = 0x0B;
82 static constexpr uint8_t DW_CFA_def_cfa = 0x0C;
83 static constexpr uint8_t DW_CFA_def_cfa_register = 0x0D;
84 static constexpr uint8_t DW_CFA_def_cfa_offset = 0x0E;
85 static constexpr uint8_t DW_CFA_offset_extended_sf = 0x11;
86 static constexpr uint8_t DW_CFA_def_cfa_sf = 0x12;
87 static constexpr uint8_t DW_CFA_def_cfa_offset_sf = 0x13;
88
94
95 struct reg_location {
97 int64_t value{0}; // Either offset from CFA or source register number
98 };
99
100 struct frame_state {
101 uint32_t cfa_reg{AbiTraits::sp_reg};
102 int64_t cfa_offset{0};
103
104 reg_location ra_loc{reg_rule_type::from_memory_cfa_offset, -static_cast<int64_t>(AbiTraits::pointer_size)};
105 reg_location fp_loc{reg_rule_type::from_memory_cfa_offset, -static_cast<int64_t>(AbiTraits::pointer_size * 2)};
106
107 uintptr_t current_loc{0};
108 };
109
110 template <size_t MaxStateStackDepth = 8> struct scratch_context {
112 frame_state state_stack[MaxStateStackDepth];
113 size_t stack_depth{0};
114
115 void reset(uintptr_t initial_loc) noexcept {
117 current_state.current_loc = initial_loc;
118 stack_depth = 0;
119 }
120 };
121
127 template <size_t MaxStateStackDepth>
128 static bool execute_fde(register_context_ref reg_ctx, uintptr_t fde_addr, uintptr_t target_pc, uintptr_t &out_fp,
129 uintptr_t &out_pc, scratch_context<MaxStateStackDepth> &scratch) noexcept {
130 if (!reg_ctx)
131 return false;
132 address_space_ref space = reg_ctx.space();
133
134 // Read FDE Header
135 uint32_t length = 0;
136 if (!space.read_bytes(fde_addr, &length, 4))
137 return false;
138 if (length == 0 || length == 0xFFFFFFFF)
139 return false;
140
141 uintptr_t entry_start = fde_addr + 4;
142 uintptr_t entry_end = entry_start + length;
143
144 uint32_t cie_pointer = 0;
145 if (!space.read_bytes(entry_start, &cie_pointer, 4))
146 return false;
147 entry_start += 4;
148
149 uint32_t initial_loc = 0;
150 uint32_t address_range = 0;
151 if (!space.read_bytes(entry_start, &initial_loc, 4))
152 return false;
153 entry_start += 4;
154 if (!space.read_bytes(entry_start, &address_range, 4))
155 return false;
156 entry_start += 4;
157
158 if (target_pc < initial_loc || target_pc >= (initial_loc + address_range)) {
159 return false;
160 }
161
162 scratch.reset(initial_loc);
163 auto &state = scratch.current_state;
164
165 // Execute CFI instructions up to target_pc
166 uintptr_t inst_ptr = entry_start;
167 while (inst_ptr < entry_end && state.current_loc <= target_pc) {
168 uint8_t op = 0;
169 if (!space.read_bytes(inst_ptr++, &op, 1))
170 break;
171
172 if ((op & 0xC0) == DW_CFA_advance_loc) {
173 state.current_loc += (op & 0x3F);
174 } else if ((op & 0xC0) == DW_CFA_offset) {
175 uint8_t reg = op & 0x3F;
176 uint64_t offset = 0;
177 if (!read_uleb128(space, inst_ptr, offset))
178 break;
179 int64_t mem_offset = -static_cast<int64_t>(offset * AbiTraits::pointer_size);
180 if (AbiTraits::is_return_address_register(reg)) {
181 state.ra_loc = {reg_rule_type::from_memory_cfa_offset, mem_offset};
182 } else if (AbiTraits::is_frame_pointer_register(reg)) {
183 state.fp_loc = {reg_rule_type::from_memory_cfa_offset, mem_offset};
184 }
185 } else {
186 switch (op) {
187 case DW_CFA_nop:
188 break;
189 case DW_CFA_register: {
190 uint64_t reg = 0;
191 uint64_t target_reg = 0;
192 if (!read_uleb128(space, inst_ptr, reg) || !read_uleb128(space, inst_ptr, target_reg))
193 return false;
194 if (AbiTraits::is_return_address_register(static_cast<uint32_t>(reg))) {
195 state.ra_loc = {reg_rule_type::from_register, static_cast<int64_t>(target_reg)};
196 } else if (AbiTraits::is_frame_pointer_register(static_cast<uint32_t>(reg))) {
197 state.fp_loc = {reg_rule_type::from_register, static_cast<int64_t>(target_reg)};
198 }
199 break;
200 }
201 case DW_CFA_advance_loc1: {
202 uint8_t delta = 0;
203 if (!space.read_bytes(inst_ptr++, &delta, 1))
204 return false;
205 state.current_loc += delta;
206 break;
207 }
208 case DW_CFA_advance_loc2: {
209 uint16_t delta = 0;
210 if (!space.read_bytes(inst_ptr, &delta, 2))
211 return false;
212 inst_ptr += 2;
213 state.current_loc += delta;
214 break;
215 }
217 uint64_t reg = 0;
218 uint64_t offset = 0;
219 if (!read_uleb128(space, inst_ptr, reg) || !read_uleb128(space, inst_ptr, offset))
220 return false;
221 int64_t mem_offset = -static_cast<int64_t>(offset * AbiTraits::pointer_size);
222 if (AbiTraits::is_return_address_register(static_cast<uint32_t>(reg))) {
223 state.ra_loc = {reg_rule_type::from_memory_cfa_offset, mem_offset};
224 } else if (AbiTraits::is_frame_pointer_register(static_cast<uint32_t>(reg))) {
225 state.fp_loc = {reg_rule_type::from_memory_cfa_offset, mem_offset};
226 }
227 break;
228 }
230 uint64_t reg = 0;
231 int64_t offset = 0;
232 if (!read_uleb128(space, inst_ptr, reg) || !read_sleb128(space, inst_ptr, offset))
233 return false;
234 if (AbiTraits::is_return_address_register(static_cast<uint32_t>(reg))) {
235 state.ra_loc = {reg_rule_type::from_memory_cfa_offset, offset};
236 } else if (AbiTraits::is_frame_pointer_register(static_cast<uint32_t>(reg))) {
237 state.fp_loc = {reg_rule_type::from_memory_cfa_offset, offset};
238 }
239 break;
240 }
242 if (scratch.stack_depth < MaxStateStackDepth) {
243 scratch.state_stack[scratch.stack_depth++] = state;
244 }
245 break;
247 if (scratch.stack_depth > 0) {
248 state = scratch.state_stack[--scratch.stack_depth];
249 }
250 break;
251 case DW_CFA_def_cfa: {
252 uint64_t reg = 0;
253 uint64_t offset = 0;
254 if (!read_uleb128(space, inst_ptr, reg) || !read_uleb128(space, inst_ptr, offset))
255 return false;
256 state.cfa_reg = static_cast<uint32_t>(reg);
257 state.cfa_offset = static_cast<int64_t>(offset);
258 break;
259 }
261 uint64_t reg = 0;
262 if (!read_uleb128(space, inst_ptr, reg))
263 return false;
264 state.cfa_reg = static_cast<uint32_t>(reg);
265 break;
266 }
268 uint64_t offset = 0;
269 if (!read_uleb128(space, inst_ptr, offset))
270 return false;
271 state.cfa_offset = static_cast<int64_t>(offset);
272 break;
273 }
275 int64_t offset = 0;
276 if (!read_sleb128(space, inst_ptr, offset))
277 return false;
278 state.cfa_offset = offset;
279 break;
280 }
281 default:
282 break;
283 }
284 }
285 }
286
287 // 1. SELECTIVE REGISTER ACCESS: Read the CFA base register from the
288 // register context
289 register_type cfa_base_val = 0;
290 if (!reg_ctx.read_raw(state.cfa_reg, &cfa_base_val, AbiTraits::pointer_size)) {
291 return false;
292 }
293 uintptr_t cfa = static_cast<uintptr_t>(cfa_base_val) + state.cfa_offset;
294
295 // 2. Resolve saved Frame Pointer (FP)
296 register_type saved_fp = 0;
297 if (state.fp_loc.type == reg_rule_type::from_memory_cfa_offset) {
298 uintptr_t fp_addr = cfa + state.fp_loc.value;
299 space.read_bytes(fp_addr, &saved_fp, AbiTraits::pointer_size);
300 } else if (state.fp_loc.type == reg_rule_type::from_register) {
301 // SELECTIVE REGISTER ACCESS: Query source register directly via register
302 // accessor
303 reg_ctx.read_raw(static_cast<uint32_t>(state.fp_loc.value), &saved_fp, AbiTraits::pointer_size);
304 }
305
306 // 3. Resolve saved Return Address (RA)
307 register_type saved_ra = 0;
308 if (state.ra_loc.type == reg_rule_type::from_memory_cfa_offset) {
309 uintptr_t ra_addr = cfa + state.ra_loc.value;
310 if (!space.read_bytes(ra_addr, &saved_ra, AbiTraits::pointer_size))
311 return false;
312 } else if (state.ra_loc.type == reg_rule_type::from_register) {
313 // SELECTIVE REGISTER ACCESS: Query source register directly via register
314 // accessor
315 if (!reg_ctx.read_raw(static_cast<uint32_t>(state.ra_loc.value), &saved_ra, AbiTraits::pointer_size))
316 return false;
317 }
318
319 out_fp = static_cast<uintptr_t>(saved_fp);
320 out_pc = AbiTraits::normalize_pc(static_cast<uintptr_t>(saved_ra));
321
322 // reg_ctx is always a mutable scratch snapshot (a dead-process register
323 // view or a local ucontext_t/GPR-array copy), never a live process to
324 // resume -- write the caller's recovered SP (CFA)/FP/RA back into it so a
325 // *subsequent* step (whether another DWARF FDE for the caller, or a
326 // different unwinder tier) sees the caller's live registers instead of
327 // silently re-reading this frame's now-stale values (which would
328 // otherwise loop forever re-resolving the same frame). Best-effort:
329 // read-only register contexts still let this single step succeed via
330 // out_fp/out_pc, they just can't chain further through this tier.
331 auto cfa_reg_val = static_cast<register_type>(cfa);
332 std::ignore = reg_ctx.write_raw(AbiTraits::sp_reg, &cfa_reg_val, AbiTraits::pointer_size);
333 std::ignore = reg_ctx.write_raw(AbiTraits::fp_reg, &saved_fp, AbiTraits::pointer_size);
334 std::ignore = reg_ctx.write_raw(AbiTraits::ra_reg, &saved_ra, AbiTraits::pointer_size);
335
336 return true;
337 }
338};
339
344template <typename AbiTraits, size_t MaxStateStackDepth = 8> struct dwarf_unwinder_context {
348 typename dwarf_cfi_decoder<AbiTraits>::template scratch_context<MaxStateStackDepth> *dwarf_scratch{nullptr};
349};
350
351template <typename AbiTraits, size_t MaxStateStackDepth = 8> struct dwarf_unwinder_tag {};
352
370
371} // namespace microfmt
372
377template <typename AbiTraits, size_t MaxStateStackDepth>
378struct microfmt::frame_unwinder_traits<microfmt::dwarf_unwinder_tag<AbiTraits, MaxStateStackDepth>> {
381
397 static bool step(value_ref<const context_type> context, register_context_ref reg_ctx, uintptr_t current_pc,
398 uintptr_t &next_fp, uintptr_t &next_pc) noexcept {
399 if (!reg_ctx)
400 return false;
401
402 if (!context->enumerator || !context->img_storage || !context->dwarf_scratch) {
403 return false;
404 }
405
406 uintptr_t fault_pc = AbiTraits::normalize_pc(current_pc);
407
408 microfmt::elf_image_info &img = *context->img_storage;
409 img = {};
410
411 if (!context->enumerator.find_by_pc(fault_pc, img) || !img.has_debug_frame()) {
412 return false;
413 }
414
415 uintptr_t curr = img.debug_frame_start;
416 while (curr < img.debug_frame_end) {
417 uint32_t len = 0;
418 if (!context->space.read_bytes(curr, &len, 4))
419 break;
420 if (len == 0)
421 break;
422
423 if (decoder_type::execute_fde(reg_ctx, curr, fault_pc, next_fp, next_pc, *context->dwarf_scratch)) {
424 return true;
425 }
426
427 curr += 4 + len;
428 }
429
430 return false;
431 }
432};
Type-erased remote address-space access and remote string/object views.
Type-erased, two-word handle to a remote address space.
Definition address_space.hpp:204
expected< void, address_space_error > read_bytes(uintptr_t addr, void *dest, size_t size) const noexcept
Reads raw bytes from the remote address space.
Definition address_space.hpp:294
Decoder executing DWARF CFI bytecode against an address space and selective register context.
Definition dwarf_decoder.hpp:28
static constexpr uint8_t DW_CFA_restore
Definition dwarf_decoder.hpp:74
typename AbiTraits::register_type register_type
Definition dwarf_decoder.hpp:30
static constexpr uint8_t DW_CFA_def_cfa_register
Definition dwarf_decoder.hpp:83
static constexpr uint8_t DW_CFA_def_cfa
Definition dwarf_decoder.hpp:82
static constexpr uint8_t DW_CFA_advance_loc2
Definition dwarf_decoder.hpp:78
static constexpr uint8_t DW_CFA_register
Definition dwarf_decoder.hpp:76
static bool execute_fde(register_context_ref reg_ctx, uintptr_t fde_addr, uintptr_t target_pc, uintptr_t &out_fp, uintptr_t &out_pc, scratch_context< MaxStateStackDepth > &scratch) noexcept
Executes FDE bytecode, selectively querying register_context_ref only when virtual registers are need...
Definition dwarf_decoder.hpp:128
static constexpr uint8_t DW_CFA_def_cfa_offset_sf
Definition dwarf_decoder.hpp:87
static constexpr uint8_t DW_CFA_offset_extended_sf
Definition dwarf_decoder.hpp:85
static constexpr uint8_t DW_CFA_offset_extended
Definition dwarf_decoder.hpp:79
static bool read_sleb128(address_space_ref space, uintptr_t &addr, int64_t &out_val) noexcept
Definition dwarf_decoder.hpp:49
static constexpr uint8_t DW_CFA_remember_state
Definition dwarf_decoder.hpp:80
reg_rule_type
Tracks location type for saved registers (Memory offset vs Live Register).
Definition dwarf_decoder.hpp:93
static constexpr uint8_t DW_CFA_def_cfa_offset
Definition dwarf_decoder.hpp:84
static constexpr uint8_t DW_CFA_restore_state
Definition dwarf_decoder.hpp:81
static constexpr uint8_t DW_CFA_def_cfa_sf
Definition dwarf_decoder.hpp:86
static constexpr uint8_t DW_CFA_offset
Definition dwarf_decoder.hpp:73
static constexpr uint8_t DW_CFA_advance_loc1
Definition dwarf_decoder.hpp:77
static bool read_uleb128(address_space_ref space, uintptr_t &addr, uint64_t &out_val) noexcept
Definition dwarf_decoder.hpp:32
static constexpr uint8_t DW_CFA_advance_loc
Definition dwarf_decoder.hpp:72
static constexpr uint8_t DW_CFA_nop
Definition dwarf_decoder.hpp:75
Type-erased, two-word handle to an ELF image enumerator.
Definition elf_enumerator.hpp:104
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
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
Definition dwarf_decoder.hpp:100
reg_location ra_loc
Definition dwarf_decoder.hpp:104
int64_t cfa_offset
Definition dwarf_decoder.hpp:102
uintptr_t current_loc
Definition dwarf_decoder.hpp:107
uint32_t cfa_reg
Definition dwarf_decoder.hpp:101
reg_location fp_loc
Definition dwarf_decoder.hpp:105
Definition dwarf_decoder.hpp:95
reg_rule_type type
Definition dwarf_decoder.hpp:96
int64_t value
Definition dwarf_decoder.hpp:97
Definition dwarf_decoder.hpp:110
frame_state state_stack[MaxStateStackDepth]
Definition dwarf_decoder.hpp:112
frame_state current_state
Definition dwarf_decoder.hpp:111
void reset(uintptr_t initial_loc) noexcept
Definition dwarf_decoder.hpp:115
size_t stack_depth
Definition dwarf_decoder.hpp:113
Immutable context describing a DWARF unwinder back to the type-erased handle.
Definition dwarf_decoder.hpp:344
dwarf_cfi_decoder< AbiTraits >::template scratch_context< MaxStateStackDepth > * dwarf_scratch
Definition dwarf_decoder.hpp:348
elf_image_info * img_storage
Definition dwarf_decoder.hpp:347
address_space_ref space
Definition dwarf_decoder.hpp:345
elf_image_enumerator_ref enumerator
Definition dwarf_decoder.hpp:346
Definition dwarf_decoder.hpp:353
dwarf_unwinder_holder(dwarf_unwinder_holder &&)=delete
dwarf_unwinder_holder(const dwarf_unwinder_holder &)=delete
dwarf_unwinder_holder & operator=(dwarf_unwinder_holder &&)=delete
elf_image_info img_storage
Definition dwarf_decoder.hpp:354
dwarf_unwinder_context< AbiTraits, MaxStateStackDepth > ctx
Definition dwarf_decoder.hpp:356
frame_unwinder_ref make_ref() &noexcept
Definition dwarf_decoder.hpp:366
dwarf_unwinder_holder & operator=(const dwarf_unwinder_holder &)=delete
dwarf_unwinder_holder(address_space_ref space, elf_image_enumerator_ref enumerator) noexcept
Definition dwarf_decoder.hpp:358
dwarf_cfi_decoder< AbiTraits >::template scratch_context< MaxStateStackDepth > dwarf_scratch
Definition dwarf_decoder.hpp:355
Definition dwarf_decoder.hpp:351
Describes a loaded ELF image and the locations of its unwind tables.
Definition elf_enumerator.hpp:30
uintptr_t debug_frame_end
End of the DWARF CFI unwind-record table. May be UINTPTR_MAX when the backend can locate the table's ...
Definition elf_enumerator.hpp:68
constexpr bool has_debug_frame() const noexcept
Reports whether a usable DWARF CFI unwind-record table (.debug_frame or .eh_frame,...
Definition elf_enumerator.hpp:90
uintptr_t debug_frame_start
Start of the DWARF CFI unwind-record table used by microfmt::dwarf_decoder for stack unwinding....
Definition elf_enumerator.hpp:61
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 DWARF CFI FDE and register_context_ref.
Definition dwarf_decoder.hpp:397
Static customization point describing a frame unwinder backend.
Definition frame_pointer.hpp:56