microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
exception_frame.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 "frame_pointer.hpp"
12#include "symbol_resolver.hpp"
13#include <cstddef>
14#include <cstdint>
15#include <string_view>
16#include <type_traits>
17
18namespace microfmt {
19
20// ============================================================================
21// Normalized Trap Context Snapshot
22// ============================================================================
23
31 uint32_t trap_level{0}; // 0 = primary/top fault, 1 = nested exception, etc.
35 uintptr_t trap_frame_addr{0}; // Address of the raw architecture pt_regs/frame
39 uintptr_t pc{0}; // Faulting instruction pointer (RIP/PC/ELR)
43 uintptr_t sp{0}; // Faulting stack pointer (RSP/SP)
47 uintptr_t fp{0}; // Faulting frame pointer (RBP/FP/x29)
51 uintptr_t lr{0}; // Link register (AArch64/ARM32) or 0 (x86)
55 uint64_t vector_or_reason{0}; // Vector number, trap error code, or ESR
59 bool is_user_mode{false}; // True if origin was user mode
60};
61
62// ============================================================================
63// Customization Point (exception_frame_traits)
64// ============================================================================
65
70template <typename ArchTag> struct exception_frame_traits;
71
72// ============================================================================
73// Type-Erased Exception Frame Accessor (2 Words)
74// ============================================================================
75
79class RELOCO_POINTER exception_frame_ref {
80public:
84 struct vtable {
88 bool (*decode)(const void *ctx, uintptr_t trap_frame_addr, trap_context &out_trap) noexcept;
93 bool (*next_trap_frame)(const void *ctx, uintptr_t current_trap_frame_addr,
94 uintptr_t &next_trap_frame_addr) noexcept;
99 microfmt::string_view (*describe_reason)(const void *ctx, uint64_t vector_or_reason) noexcept;
100 };
101
105 constexpr exception_frame_ref() noexcept = default;
106
112 template <typename ArchTag, typename Traits = exception_frame_traits<ArchTag>,
113 std::enable_if_t<std::is_void_v<typename Traits::context_type>, int> = 0>
114 constexpr explicit exception_frame_ref(ArchTag) noexcept : ctx_(nullptr), vtbl_(&s_vtbl<ArchTag>) {}
115
124 template <typename ArchTag, typename Context, typename Traits = exception_frame_traits<ArchTag>,
125 std::enable_if_t<!std::is_void_v<typename Traits::context_type> &&
126 std::is_convertible_v<const Context *, const typename Traits::context_type *>,
127 int> = 0>
128 constexpr exception_frame_ref(ArchTag,
129 const Context &ctx RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
130 : ctx_(&ctx), vtbl_(&s_vtbl<ArchTag>) {}
131
132 template <typename ArchTag, typename Context, std::enable_if_t<!std::is_lvalue_reference_v<Context>, int> = 0>
133 constexpr exception_frame_ref(ArchTag, Context &&) = delete;
134
141 template <typename ArchTag, typename Traits = exception_frame_traits<ArchTag>,
142 std::enable_if_t<std::is_void_v<typename Traits::context_type>, int> = 0>
143 [[nodiscard]] static constexpr exception_frame_ref make() noexcept {
144 return exception_frame_ref(ArchTag{});
145 }
146
155 template <typename ArchTag, typename Context, typename Traits = exception_frame_traits<ArchTag>,
156 std::enable_if_t<!std::is_void_v<typename Traits::context_type>, int> = 0>
157 [[nodiscard]] static constexpr exception_frame_ref make(const Context &ctx RELOCO_LIFETIMEBOUND) noexcept {
158 return exception_frame_ref(ArchTag{}, ctx);
159 }
160
161 template <typename ArchTag, typename Context, std::enable_if_t<!std::is_lvalue_reference_v<Context>, int> = 0>
162 static exception_frame_ref make(Context &&) = delete;
163
171 [[nodiscard]] bool decode(uintptr_t trap_frame_addr, trap_context &out_trap) const noexcept {
172 if (!vtbl_)
173 return false;
174 return vtbl_->decode(ctx_.get(), trap_frame_addr, out_trap);
175 }
176
183 [[nodiscard]] bool next_trap_frame(uintptr_t current_trap_frame_addr,
184 uintptr_t &next_trap_frame_addr) const noexcept {
185 if (!vtbl_)
186 return false;
187 return vtbl_->next_trap_frame(ctx_.get(), current_trap_frame_addr, next_trap_frame_addr);
188 }
189
195 [[nodiscard]] microfmt::string_view describe_reason(uint64_t vector_or_reason) const noexcept {
196 if (!vtbl_ || !vtbl_->describe_reason)
197 return {};
198 return vtbl_->describe_reason(ctx_.get(), vector_or_reason);
199 }
200
205 [[nodiscard]] constexpr explicit operator bool() const noexcept { return vtbl_ != nullptr; }
206
207private:
208 template <typename ArchTag>
209 static bool decode_entry(const void *context, uintptr_t address, trap_context &trap) noexcept {
210 using context_type = typename exception_frame_traits<ArchTag>::context_type;
211 if constexpr (std::is_void_v<context_type>) {
212 return exception_frame_traits<ArchTag>::decode(address, trap);
213 } else {
214 const auto &typed_context = *static_cast<const context_type *>(context);
215 return exception_frame_traits<ArchTag>::decode(value_ref<const context_type>(typed_context), address, trap);
216 }
217 }
218
219 template <typename ArchTag>
220 static bool next_entry(const void *context, uintptr_t address, uintptr_t &next_address) noexcept {
221 using context_type = typename exception_frame_traits<ArchTag>::context_type;
222 if constexpr (std::is_void_v<context_type>) {
223 return exception_frame_traits<ArchTag>::next_trap_frame(address, next_address);
224 } else {
225 const auto &typed_context = *static_cast<const context_type *>(context);
226 return exception_frame_traits<ArchTag>::next_trap_frame(value_ref<const context_type>(typed_context), address,
227 next_address);
228 }
229 }
230
231 template <typename ArchTag>
232 static microfmt::string_view describe_entry(const void *context, uint64_t reason) noexcept {
233 using context_type = typename exception_frame_traits<ArchTag>::context_type;
234 if constexpr (std::is_void_v<context_type>) {
235 return microfmt::string_view(exception_frame_traits<ArchTag>::describe_reason(reason));
236 } else {
237 const auto &typed_context = *static_cast<const context_type *>(context);
238 return microfmt::string_view(
239 exception_frame_traits<ArchTag>::describe_reason(value_ref<const context_type>(typed_context), reason));
240 }
241 }
242
243 template <typename ArchTag>
244 static constexpr vtable s_vtbl{&decode_entry<ArchTag>, &next_entry<ArchTag>, &describe_entry<ArchTag>};
245
246 value_ptr<const void> ctx_{};
247 const vtable *vtbl_{nullptr};
248};
249
250template <typename Tag, bool Stateless = std::is_void_v<typename exception_frame_traits<Tag>::context_type>>
252
253template <typename Tag> class RELOCO_OWNER exception_frame<Tag, false> {
254public:
256 using context_type = typename traits_type::context_type;
257
258 constexpr explicit exception_frame(context_type context) noexcept : context_(std::move(context)) {}
259
260 [[nodiscard]] constexpr value_ref<context_type> context() & noexcept RELOCO_LIFETIMEBOUND {
261 return value_ref<context_type>(context_);
262 }
263
264 [[nodiscard]] constexpr value_ref<const context_type> context() const & noexcept RELOCO_LIFETIMEBOUND {
265 return value_ref<const context_type>(context_);
266 }
267
268 [[nodiscard]] constexpr exception_frame_ref ref() const & noexcept RELOCO_LIFETIMEBOUND {
269 return exception_frame_ref(Tag{}, context_);
270 }
271
272 value_ref<context_type> context() && = delete;
273 value_ref<const context_type> context() const && = delete;
274 exception_frame_ref ref() const && = delete;
275
276private:
277 context_type context_;
278};
279
280template <typename Tag> class exception_frame<Tag, true> {
281public:
282 [[nodiscard]] static constexpr exception_frame_ref ref() noexcept { return exception_frame_ref(Tag{}); }
283};
284
285// ============================================================================
286// Type-Erased Exception Frame Iterator
287// ============================================================================
288
293public:
297 constexpr exception_frame_iterator() noexcept = default;
298
304 constexpr exception_frame_iterator(exception_frame_ref decoder, uintptr_t initial_trap_frame_addr) noexcept
305 : decoder_(decoder), raw_frame_addr_(initial_trap_frame_addr), is_valid_(initial_trap_frame_addr != 0) {
306 if (is_valid_) {
307 load_current();
308 }
309 }
310
315 [[nodiscard]] constexpr const trap_context &operator*() const noexcept { return current_; }
320 [[nodiscard]] constexpr const trap_context *operator->() const noexcept { return &current_; }
321
326 [[nodiscard]] constexpr bool has_value() const noexcept { return is_valid_; }
331 [[nodiscard]] constexpr explicit operator bool() const noexcept { return is_valid_; }
332
337 bool next() noexcept {
338 if (!is_valid_)
339 return false;
340
341 uintptr_t next_raw_addr = 0;
342 if (!decoder_.next_trap_frame(raw_frame_addr_, next_raw_addr) || next_raw_addr == 0) {
343 is_valid_ = false;
344 return false;
345 }
346
347 raw_frame_addr_ = next_raw_addr;
348 current_level_++;
349 return load_current();
350 }
351
357 next();
358 return *this;
359 }
360
368 template <typename Visitor> void for_each_trap(Visitor &&visitor, uint32_t max_traps = 8) noexcept {
369 while (is_valid_ && current_.trap_level < max_traps) {
370 if (!visitor(current_)) {
371 break;
372 }
373 next();
374 }
375 }
376
377private:
378 bool load_current() noexcept {
379 if (!decoder_.decode(raw_frame_addr_, current_)) {
380 is_valid_ = false;
381 return false;
382 }
383 current_.trap_level = current_level_;
384 current_.trap_frame_addr = raw_frame_addr_;
385 return true;
386 }
387
389 exception_frame_ref decoder_{};
391 uintptr_t raw_frame_addr_{};
393 trap_context current_{};
395 uint32_t current_level_{0};
397 bool is_valid_{false};
398};
399
400// ============================================================================
401// Formattable Exception & Trap Summary View
402// ============================================================================
403
408public:
417 symbol_resolution_context &symbol_context RELOCO_LIFETIMEBOUND) noexcept
418 : trap_(trap), decoder_(decoder), resolver_(resolver), symbol_context_(&symbol_context) {}
419
424 [[nodiscard]] constexpr const trap_context &trap() const noexcept RELOCO_LIFETIMEBOUND { return trap_; }
429 [[nodiscard]] constexpr exception_frame_ref decoder() const noexcept { return decoder_; }
434 [[nodiscard]] constexpr symbol_resolver_ref resolver() const noexcept { return resolver_; }
435 [[nodiscard]] constexpr symbol_resolution_context &symbol_context() const noexcept RELOCO_LIFETIMEBOUND {
436 return *symbol_context_;
437 }
438
439private:
441 trap_context trap_{};
443 exception_frame_ref decoder_{};
445 symbol_resolver_ref resolver_{};
447 value_ptr<symbol_resolution_context> symbol_context_{};
448};
449
457template <> struct formatter<remote_trap_view> {
461 char mode{'\0'};
462
467 constexpr void parse(format_parse_context &ctx) noexcept {
468 auto spec = ctx.spec();
469 if (!spec.empty() && spec.front() == '#') {
470 mode = '#';
471 }
472 }
473
479 void format(const remote_trap_view &view, const sink &out) const noexcept {
480 const auto &trap = view.trap();
481 microfmt::string_view desc = view.decoder().describe_reason(trap.vector_or_reason);
482
483 microfmt::format_to(out, "[Trap Level {} @ {:#x}] Mode: {}\n", trap.trap_level, trap.trap_frame_addr,
484 trap.is_user_mode ? "User" : "Kernel");
485
486 if (!desc.empty()) {
487 microfmt::format_to(out, " Reason : {} ({:#x})\n", desc, trap.vector_or_reason);
488 } else if (trap.vector_or_reason != 0) {
489 microfmt::format_to(out, " Vector : {:#x}\n", trap.vector_or_reason);
490 }
491
492 microfmt::format_to(out, " PC : ");
493 remote_fn_ptr pc_sym(trap.pc, view.resolver(), view.symbol_context());
494 if (mode == '#') {
495 microfmt::format_to(out, "{:#}\n", pc_sym);
496 } else {
497 microfmt::format_to(out, "{}\n", pc_sym);
498 }
499
500 microfmt::format_to(out, " SP : {:#x}\n", trap.sp);
501 microfmt::format_to(out, " FP : {:#x}", trap.fp);
502 if (trap.lr != 0) {
503 microfmt::format_to(out, "\n LR : ");
504 remote_fn_ptr lr_sym(trap.lr, view.resolver(), view.symbol_context());
505 microfmt::format_to(out, "{}", lr_sym);
506 }
507 }
508};
509
510} // namespace microfmt
Type-erased remote address-space access and remote string/object views.
constexpr value_ref< const context_type > context() const &noexcept RELOCO_LIFETIMEBOUND
Definition exception_frame.hpp:264
constexpr value_ref< context_type > context() &noexcept RELOCO_LIFETIMEBOUND
Definition exception_frame.hpp:260
constexpr exception_frame(context_type context) noexcept
Definition exception_frame.hpp:258
value_ref< context_type > context() &&=delete
typename traits_type::context_type context_type
Definition exception_frame.hpp:256
constexpr exception_frame_ref ref() const &noexcept RELOCO_LIFETIMEBOUND
Definition exception_frame.hpp:268
value_ref< const context_type > context() const &&=delete
static constexpr exception_frame_ref ref() noexcept
Definition exception_frame.hpp:282
Forward-only cursor iterating over a chain of trap frames.
Definition exception_frame.hpp:292
constexpr bool has_value() const noexcept
Reports whether the iterator is positioned on a trap frame.
Definition exception_frame.hpp:326
constexpr exception_frame_iterator() noexcept=default
Constructs an empty (invalid) iterator.
constexpr const trap_context * operator->() const noexcept
Returns the current decoded trap context.
Definition exception_frame.hpp:320
bool next() noexcept
Advances to the next (outer/nested) trap frame.
Definition exception_frame.hpp:337
exception_frame_iterator & operator++() noexcept
Pre-increment advancing to the next trap frame.
Definition exception_frame.hpp:356
constexpr const trap_context & operator*() const noexcept
Returns the current decoded trap context.
Definition exception_frame.hpp:315
void for_each_trap(Visitor &&visitor, uint32_t max_traps=8) noexcept
Visitor-style traversal over the trap chain.
Definition exception_frame.hpp:368
Type-erased, two-word handle decoding exception/trap frames.
Definition exception_frame.hpp:79
bool decode(uintptr_t trap_frame_addr, trap_context &out_trap) const noexcept
Decodes raw trap frame memory into a uniform trap_context.
Definition exception_frame.hpp:171
microfmt::string_view describe_reason(uint64_t vector_or_reason) const noexcept
Returns a textual description of a vector/reason code.
Definition exception_frame.hpp:195
constexpr exception_frame_ref(ArchTag, const Context &ctx RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
Constructs a handle for a stateful architecture tag.
Definition exception_frame.hpp:128
constexpr exception_frame_ref() noexcept=default
Constructs an empty (invalid) handle.
static exception_frame_ref make(Context &&)=delete
static constexpr exception_frame_ref make(const Context &ctx RELOCO_LIFETIMEBOUND) noexcept
Creates a handle for a stateful architecture tag.
Definition exception_frame.hpp:157
bool next_trap_frame(uintptr_t current_trap_frame_addr, uintptr_t &next_trap_frame_addr) const noexcept
Follows links to outer/nested trap frames.
Definition exception_frame.hpp:183
static constexpr exception_frame_ref make() noexcept
Creates a handle for a stateless architecture tag.
Definition exception_frame.hpp:143
constexpr exception_frame_ref(ArchTag, Context &&)=delete
Definition exception_frame.hpp:251
Definition microfmt.hpp:881
Symbolic view of a remote function / code pointer.
Definition remote_diagnostics.hpp:82
Formattable summary view of a trap context.
Definition exception_frame.hpp:407
constexpr const trap_context & trap() const noexcept RELOCO_LIFETIMEBOUND
Returns the wrapped trap context.
Definition exception_frame.hpp:424
constexpr remote_trap_view(const trap_context &trap, exception_frame_ref decoder, symbol_resolver_ref resolver, symbol_resolution_context &symbol_context RELOCO_LIFETIMEBOUND) noexcept
Constructs a trap summary view.
Definition exception_frame.hpp:416
constexpr symbol_resolver_ref resolver() const noexcept
Returns the symbol resolver handle.
Definition exception_frame.hpp:434
constexpr exception_frame_ref decoder() const noexcept
Returns the trap-frame decoder handle.
Definition exception_frame.hpp:429
constexpr symbol_resolution_context & symbol_context() const noexcept RELOCO_LIFETIMEBOUND
Definition exception_frame.hpp:435
Type-erased, two-word handle to a symbol resolver.
Definition symbol_resolver.hpp:132
#define MICROFMT_API_CLASS
Definition compat.hpp:181
Type-erased frame unwinder handles, frame cursor iteration, and backtrace views.
Definition assert.hpp:19
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
RELOCO_CONSTEXPR20 void format_to(const sink &out, microfmt::string_view fmt, const Args &...args) noexcept
Definition microfmt.hpp:1766
Virtual table of trap-frame operations.
Definition exception_frame.hpp:84
Static customization point for decoding architecture trap frames.
Definition exception_frame.hpp:70
constexpr void parse(format_parse_context &ctx) noexcept
Parses the leading # mode flag.
Definition exception_frame.hpp:467
void format(const remote_trap_view &view, const sink &out) const noexcept
Renders the trap summary.
Definition exception_frame.hpp:479
Definition microfmt.hpp:985
Caller-owned temporary storage for one symbol resolution.
Definition symbol_resolver.hpp:100
Normalized, architecture-independent snapshot of a trap context.
Definition exception_frame.hpp:27
Type-erased symbol resolution for remote addresses and formattable symbol views.