microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
hybrid_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 "exception_frame.hpp"
11#include "frame_pointer.hpp"
12#include <type_traits>
13#include <utility>
14
15namespace microfmt {
16
17template <typename Tag> struct exception_matcher_traits;
18
19// ============================================================================
20// Unified Unwind Frame Type
21// ============================================================================
22
27enum class frame_kind : uint8_t { standard = 0, trap_transition };
28
36 uint32_t frame_index{0};
40 uintptr_t fp{0};
44 uintptr_t pc{0};
48 frame_kind kind{frame_kind::standard};
53 trap_context trap{}; // Valid when kind == frame_kind::trap_transition
54};
55
56// ============================================================================
57// Exception Matcher Hook
58// ============================================================================
59
64public:
68 struct vtable {
73 bool (*match_trap_frame)(const void *ctx, uintptr_t fp, uintptr_t pc, uintptr_t &out_trap_frame_addr) noexcept;
74 };
75
79 constexpr exception_matcher_ref() noexcept = default;
80
87 template <typename Tag, typename Context, typename Traits = exception_matcher_traits<Tag>,
88 std::enable_if_t<std::is_convertible_v<const Context *, const typename Traits::context_type *>, int> = 0>
89 constexpr exception_matcher_ref(Tag, const Context &ctx RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
90 : ctx_(&ctx), vtbl_(&s_vtbl<Tag>) {}
91
92 template <typename Tag, typename Context, std::enable_if_t<!std::is_lvalue_reference_v<Context>, int> = 0>
93 constexpr exception_matcher_ref(Tag, Context &&) = delete;
94
95 template <typename Tag, typename Context, typename Traits = exception_matcher_traits<Tag>>
96 [[nodiscard]] static constexpr exception_matcher_ref make(const Context &ctx RELOCO_LIFETIMEBOUND) noexcept {
97 return exception_matcher_ref(Tag{}, ctx);
98 }
99
100 template <typename Tag, typename Context, std::enable_if_t<!std::is_lvalue_reference_v<Context>, int> = 0>
101 static exception_matcher_ref make(Context &&) = delete;
102
110 [[nodiscard]] bool match_trap_frame(uintptr_t fp, uintptr_t pc, uintptr_t &out_trap_frame_addr) const noexcept {
111 if (!vtbl_)
112 return false;
113 return vtbl_->match_trap_frame(ctx_.get(), fp, pc, out_trap_frame_addr);
114 }
115
120 [[nodiscard]] constexpr explicit operator bool() const noexcept { return vtbl_ != nullptr; }
121
122private:
123 template <typename Tag>
124 static constexpr vtable s_vtbl{[](const void *context, uintptr_t fp, uintptr_t pc, uintptr_t &out_addr) noexcept {
125 using context_type = typename exception_matcher_traits<Tag>::context_type;
126 const auto &typed_context = *static_cast<const context_type *>(context);
127 return exception_matcher_traits<Tag>::match_trap_frame(value_ref<const context_type>(typed_context), fp, pc,
128 out_addr);
129 }};
130
131 value_ptr<const void> ctx_{};
132 const vtable *vtbl_{nullptr};
133};
134
138template <typename Tag> class RELOCO_OWNER exception_matcher {
139public:
141 using context_type = typename traits_type::context_type;
142
143 constexpr explicit exception_matcher(context_type context) noexcept : context_(std::move(context)) {}
144
145 [[nodiscard]] constexpr value_ref<context_type> context() & noexcept RELOCO_LIFETIMEBOUND {
146 return value_ref<context_type>(context_);
147 }
148
149 [[nodiscard]] constexpr value_ref<const context_type> context() const & noexcept RELOCO_LIFETIMEBOUND {
150 return value_ref<const context_type>(context_);
151 }
152
153 [[nodiscard]] constexpr exception_matcher_ref ref() const & noexcept RELOCO_LIFETIMEBOUND {
154 return exception_matcher_ref(Tag{}, context_);
155 }
156
157 [[nodiscard]] constexpr operator exception_matcher_ref() const & noexcept RELOCO_LIFETIMEBOUND { return ref(); }
158
159 value_ref<context_type> context() && = delete;
160 value_ref<const context_type> context() const && = delete;
161 exception_matcher_ref ref() const && = delete;
162 operator exception_matcher_ref() const && = delete;
163
164private:
165 context_type context_;
166};
167
168// ============================================================================
169// Hybrid Stack Unwinder (FP + Exception Frames)
170// ============================================================================
171
177public:
188 exception_matcher_ref matcher, register_context_ref reg_ctx, uintptr_t initial_fp,
189 uintptr_t initial_pc) noexcept
190 : fp_unwinder_(fp_unwinder), trap_decoder_(trap_decoder), matcher_(matcher), reg_ctx_(reg_ctx),
191 current_{0, initial_fp, initial_pc, frame_kind::standard, {}}, is_valid_(initial_fp != 0 || initial_pc != 0) {}
192
197 [[nodiscard]] constexpr const hybrid_frame &operator*() const noexcept { return current_; }
202 [[nodiscard]] constexpr const hybrid_frame *operator->() const noexcept { return &current_; }
203
208 [[nodiscard]] constexpr bool has_value() const noexcept { return is_valid_; }
213 [[nodiscard]] constexpr explicit operator bool() const noexcept { return is_valid_; }
214
223 bool next() noexcept {
224 if (!is_valid_)
225 return false;
226
227 // Check if the current frame is an exception trampoline
228 uintptr_t trap_frame_addr = 0;
229 if (matcher_ && matcher_.match_trap_frame(current_.fp, current_.pc, trap_frame_addr)) {
230 current_.trap = {};
231 if (trap_decoder_ && trap_decoder_.decode(trap_frame_addr, current_.trap)) {
232 // Synthesize a trap transition frame
233 current_.frame_index++;
234 current_.kind = frame_kind::trap_transition;
235 current_.fp = current_.trap.fp;
236 current_.pc = current_.trap.pc;
237 return true;
238 }
239 }
240
241 // Normal FP unwind step
242 uintptr_t next_fp = 0;
243 uintptr_t next_pc = 0;
244 if (!fp_unwinder_.step(reg_ctx_, current_.pc, next_fp, next_pc)) {
245 is_valid_ = false;
246 return false;
247 }
248
249 current_.frame_index++;
250 current_.kind = frame_kind::standard;
251 current_.fp = next_fp;
252 current_.pc = next_pc;
253 return true;
254 }
255
261 next();
262 return *this;
263 }
264
272 template <typename Visitor> void for_each_frame(Visitor &&visitor, uint32_t max_depth = 64) noexcept {
273 while (is_valid_ && current_.frame_index < max_depth) {
274 if (!visitor(current_)) {
275 break;
276 }
277 next();
278 }
279 }
280
281private:
283 frame_unwinder_ref fp_unwinder_{};
285 exception_frame_ref trap_decoder_{};
287 exception_matcher_ref matcher_{};
289 register_context_ref reg_ctx_{};
291 hybrid_frame current_{};
293 bool is_valid_{false};
294};
295
296// ============================================================================
297// Formattable Hybrid Backtrace View
298// ============================================================================
299
304public:
312 constexpr explicit hybrid_backtrace_view(hybrid_stack_unwinder &unwinder RELOCO_LIFETIMEBOUND,
313 symbol_resolver_ref resolver,
314 symbol_resolution_context &symbol_context RELOCO_LIFETIMEBOUND,
315 uint32_t max_depth = 32) noexcept
316 : unwinder_(&unwinder), resolver_(resolver), symbol_context_(&symbol_context), max_depth_(max_depth) {}
317
322 [[nodiscard]] constexpr hybrid_stack_unwinder &unwinder() const noexcept RELOCO_LIFETIMEBOUND { return *unwinder_; }
327 [[nodiscard]] constexpr symbol_resolver_ref resolver() const noexcept { return resolver_; }
328 [[nodiscard]] constexpr symbol_resolution_context &symbol_context() const noexcept RELOCO_LIFETIMEBOUND {
329 return *symbol_context_;
330 }
335 [[nodiscard]] constexpr uint32_t max_depth() const noexcept { return max_depth_; }
336
337private:
339 value_ptr<hybrid_stack_unwinder> unwinder_;
341 symbol_resolver_ref resolver_{};
343 value_ptr<symbol_resolution_context> symbol_context_{};
345 uint32_t max_depth_{32};
346};
347
355template <> struct formatter<hybrid_backtrace_view> {
359 char mode{'\0'};
360
365 constexpr void parse(format_parse_context &ctx) noexcept {
366 auto spec = ctx.spec();
367 if (!spec.empty() && spec.front() == '#') {
368 mode = '#';
369 }
370 }
371
377 void format(const hybrid_backtrace_view &view, const sink &out) const noexcept {
378 hybrid_stack_unwinder cursor = view.unwinder();
379
380 bool first = true;
381 cursor.for_each_frame(
382 [&](const hybrid_frame &frame) noexcept -> bool {
383 if (!first) {
384 out.write("\n");
385 }
386 first = false;
387
388 if (frame.kind == frame_kind::trap_transition) {
389 out.write(" --- [Exception Boundary: ");
390 if (frame.trap.is_user_mode) {
391 out.write("User -> Kernel");
392 } else {
393 out.write("Nested Fault");
394 }
395 microfmt::format_to(out, " (vec {:#x})] ---\n", frame.trap.vector_or_reason);
396 }
397
398 microfmt::format_to(out, " #{:<2} fp={:#x} pc=", frame.frame_index, frame.fp);
399
400 remote_fn_ptr fn_sym(frame.pc, view.resolver(), view.symbol_context());
401 if (mode == '#') {
402 microfmt::format_to(out, "{:#}", fn_sym);
403 } else {
404 microfmt::format_to(out, "{}", fn_sym);
405 }
406 return true;
407 },
408 view.max_depth());
409 }
410};
411
412} // namespace microfmt
Type-erased, two-word handle decoding exception/trap frames.
Definition exception_frame.hpp:79
Type-erased hook detecting exception trampolines during unwinding.
Definition hybrid_unwinder.hpp:63
static constexpr exception_matcher_ref make(const Context &ctx RELOCO_LIFETIMEBOUND) noexcept
Definition hybrid_unwinder.hpp:96
constexpr exception_matcher_ref(Tag, Context &&)=delete
static exception_matcher_ref make(Context &&)=delete
bool match_trap_frame(uintptr_t fp, uintptr_t pc, uintptr_t &out_trap_frame_addr) const noexcept
Tests whether (fp, pc) is an exception trampoline.
Definition hybrid_unwinder.hpp:110
constexpr exception_matcher_ref() noexcept=default
Constructs an empty (invalid) matcher.
Typed owner for an exception-matcher traits specialization.
Definition hybrid_unwinder.hpp:138
constexpr value_ref< const context_type > context() const &noexcept RELOCO_LIFETIMEBOUND
Definition hybrid_unwinder.hpp:149
constexpr exception_matcher_ref ref() const &noexcept RELOCO_LIFETIMEBOUND
Definition hybrid_unwinder.hpp:153
value_ref< context_type > context() &&=delete
constexpr value_ref< context_type > context() &noexcept RELOCO_LIFETIMEBOUND
Definition hybrid_unwinder.hpp:145
value_ref< const context_type > context() const &&=delete
typename traits_type::context_type context_type
Definition hybrid_unwinder.hpp:141
constexpr exception_matcher(context_type context) noexcept
Definition hybrid_unwinder.hpp:143
Definition microfmt.hpp:881
Type-erased, two-word handle to a frame unwinder.
Definition frame_pointer.hpp:68
Formattable view rendering a hybrid backtrace.
Definition hybrid_unwinder.hpp:303
constexpr symbol_resolver_ref resolver() const noexcept
Returns the symbol resolver handle.
Definition hybrid_unwinder.hpp:327
constexpr hybrid_stack_unwinder & unwinder() const noexcept RELOCO_LIFETIMEBOUND
Returns the underlying unwinder.
Definition hybrid_unwinder.hpp:322
constexpr symbol_resolution_context & symbol_context() const noexcept RELOCO_LIFETIMEBOUND
Definition hybrid_unwinder.hpp:328
constexpr hybrid_backtrace_view(hybrid_stack_unwinder &unwinder RELOCO_LIFETIMEBOUND, symbol_resolver_ref resolver, symbol_resolution_context &symbol_context RELOCO_LIFETIMEBOUND, uint32_t max_depth=32) noexcept
Constructs a hybrid backtrace view.
Definition hybrid_unwinder.hpp:312
constexpr uint32_t max_depth() const noexcept
Returns the maximum frame depth.
Definition hybrid_unwinder.hpp:335
Forward-only cursor combining frame-pointer stepping with exception/trap transitions.
Definition hybrid_unwinder.hpp:176
constexpr hybrid_stack_unwinder(frame_unwinder_ref fp_unwinder, exception_frame_ref trap_decoder, exception_matcher_ref matcher, register_context_ref reg_ctx, uintptr_t initial_fp, uintptr_t initial_pc) noexcept
Constructs a hybrid unwinder.
Definition hybrid_unwinder.hpp:187
constexpr bool has_value() const noexcept
Reports whether the cursor is positioned on a frame.
Definition hybrid_unwinder.hpp:208
hybrid_stack_unwinder & operator++() noexcept
Pre-increment advancing to the next frame.
Definition hybrid_unwinder.hpp:260
void for_each_frame(Visitor &&visitor, uint32_t max_depth=64) noexcept
Visitor-style traversal over the hybrid frame chain.
Definition hybrid_unwinder.hpp:272
bool next() noexcept
Advances to the next frame.
Definition hybrid_unwinder.hpp:223
constexpr const hybrid_frame & operator*() const noexcept
Returns the current frame record.
Definition hybrid_unwinder.hpp:197
constexpr const hybrid_frame * operator->() const noexcept
Returns the current frame record.
Definition hybrid_unwinder.hpp:202
Zero-allocation, type-erased handle for inspecting and modifying CPU registers from a frame context.
Definition register_context.hpp:118
Symbolic view of a remote function / code pointer.
Definition remote_diagnostics.hpp:82
Type-erased, two-word handle to a symbol resolver.
Definition symbol_resolver.hpp:132
#define MICROFMT_API_CLASS
Definition compat.hpp:181
Trap/exception frame decoding, iteration, and formattable trap summaries.
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
frame_kind
Classifies a hybrid_frame as either a normal stack frame or a trap transition.
Definition hybrid_unwinder.hpp:27
Virtual table of matcher operations.
Definition hybrid_unwinder.hpp:68
Definition hybrid_unwinder.hpp:17
void format(const hybrid_backtrace_view &view, const sink &out) const noexcept
Renders the hybrid backtrace.
Definition hybrid_unwinder.hpp:377
constexpr void parse(format_parse_context &ctx) noexcept
Parses the leading # mode flag.
Definition hybrid_unwinder.hpp:365
Definition microfmt.hpp:985
One frame record produced during hybrid unwinding.
Definition hybrid_unwinder.hpp:32
uintptr_t fp
Frame/saved-stack pointer of the frame.
Definition hybrid_unwinder.hpp:40
trap_context trap
Trap context, valid when kind is frame_kind::trap_transition.
Definition hybrid_unwinder.hpp:53
uintptr_t pc
Program counter (return address) of the frame.
Definition hybrid_unwinder.hpp:44
frame_kind kind
Frame classification.
Definition hybrid_unwinder.hpp:48
uint32_t frame_index
Zero-based frame index (depth).
Definition hybrid_unwinder.hpp:36
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
uint64_t vector_or_reason
Vector number, trap error code, or exception syndrome register.
Definition exception_frame.hpp:55