microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
frame_pointer.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 "register_context.hpp"
13#include "symbol_resolver.hpp"
14#include <cstddef>
15#include <cstdint>
16#include <type_traits>
17
18namespace microfmt {
19
20// ============================================================================
21// Stack Frame Record
22// ============================================================================
23
31 uint32_t frame_index{0};
35 uintptr_t fp{0};
39 uintptr_t pc{0};
40
45 [[nodiscard]] constexpr bool is_null() const noexcept { return fp == 0 && pc == 0; }
46};
47
48// ============================================================================
49// Customization Traits Point
50// ============================================================================
51
56template <typename ArchTag> struct frame_unwinder_traits;
57
58// ============================================================================
59// Type-Erased Unwinder Handle (2 Words)
60// ============================================================================
61
68class RELOCO_POINTER frame_unwinder_ref {
69public:
73 struct vtable {
77 bool (*step)(const void *ctx, register_context_ref reg_ctx, uintptr_t current_pc, uintptr_t &next_fp,
78 uintptr_t &next_pc) noexcept;
79 };
80
84 constexpr frame_unwinder_ref() noexcept = default;
85
89 template <typename ArchTag, typename Traits = frame_unwinder_traits<ArchTag>,
90 std::enable_if_t<std::is_void_v<typename Traits::context_type>, int> = 0>
91 constexpr explicit frame_unwinder_ref(ArchTag) noexcept : ctx_(nullptr), vtbl_(&s_vtbl<ArchTag>) {}
92
96 template <typename ArchTag, typename Context, typename Traits = frame_unwinder_traits<ArchTag>,
97 std::enable_if_t<!std::is_void_v<typename Traits::context_type> &&
98 std::is_convertible_v<const Context *, const typename Traits::context_type *>,
99 int> = 0>
100 constexpr frame_unwinder_ref(ArchTag,
101 const Context &ctx RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
102 : ctx_(&ctx), vtbl_(&s_vtbl<ArchTag>) {}
103
104 template <typename ArchTag, typename Context, std::enable_if_t<!std::is_lvalue_reference_v<Context>, int> = 0>
105 constexpr frame_unwinder_ref(ArchTag, Context &&) = delete;
106
107 template <typename ArchTag, typename Traits = frame_unwinder_traits<ArchTag>,
108 std::enable_if_t<std::is_void_v<typename Traits::context_type>, int> = 0>
109 [[nodiscard]] static constexpr frame_unwinder_ref make() noexcept {
110 return frame_unwinder_ref(ArchTag{});
111 }
112
113 template <typename ArchTag, typename Context, typename Traits = frame_unwinder_traits<ArchTag>,
114 std::enable_if_t<!std::is_void_v<typename Traits::context_type>, int> = 0>
115 [[nodiscard]] static constexpr frame_unwinder_ref make(const Context &ctx RELOCO_LIFETIMEBOUND) noexcept {
116 return frame_unwinder_ref(ArchTag{}, ctx);
117 }
118
119 template <typename ArchTag, typename Context, std::enable_if_t<!std::is_lvalue_reference_v<Context>, int> = 0>
120 static frame_unwinder_ref make(Context &&) = delete;
121
135 [[nodiscard]] bool step(register_context_ref reg_ctx, uintptr_t current_pc, uintptr_t &next_fp,
136 uintptr_t &next_pc) const noexcept {
137 if (!vtbl_)
138 return false;
139 return vtbl_->step(ctx_.get(), reg_ctx, current_pc, next_fp, next_pc);
140 }
141
142 [[nodiscard]] constexpr explicit operator bool() const noexcept { return vtbl_ != nullptr; }
143
144private:
145 template <typename ArchTag>
146 static bool step_entry(const void *context, register_context_ref registers, uintptr_t current_pc, uintptr_t &next_fp,
147 uintptr_t &next_pc) noexcept {
148 using context_type = typename frame_unwinder_traits<ArchTag>::context_type;
149 if constexpr (std::is_void_v<context_type>) {
150 return frame_unwinder_traits<ArchTag>::step(registers, current_pc, next_fp, next_pc);
151 } else {
152 const auto &typed_context = *static_cast<const context_type *>(context);
153 return frame_unwinder_traits<ArchTag>::step(value_ref<const context_type>(typed_context), registers, current_pc,
154 next_fp, next_pc);
155 }
156 }
157
158 template <typename ArchTag> static constexpr vtable s_vtbl{&step_entry<ArchTag>};
159
160 value_ptr<const void> ctx_{};
161 const vtable *vtbl_{nullptr};
162};
163
164template <typename Tag, bool Stateless = std::is_void_v<typename frame_unwinder_traits<Tag>::context_type>>
166
167template <typename Tag> class RELOCO_OWNER frame_unwinder<Tag, false> {
168public:
170 using context_type = typename traits_type::context_type;
171
172 constexpr explicit frame_unwinder(context_type context) noexcept : context_(std::move(context)) {}
173
174 [[nodiscard]] constexpr value_ref<context_type> context() & noexcept RELOCO_LIFETIMEBOUND {
175 return value_ref<context_type>(context_);
176 }
177
178 [[nodiscard]] constexpr value_ref<const context_type> context() const & noexcept RELOCO_LIFETIMEBOUND {
179 return value_ref<const context_type>(context_);
180 }
181
182 [[nodiscard]] constexpr frame_unwinder_ref ref() const & noexcept RELOCO_LIFETIMEBOUND {
183 return frame_unwinder_ref(Tag{}, context_);
184 }
185
186 value_ref<context_type> context() && = delete;
187 value_ref<const context_type> context() const && = delete;
188 frame_unwinder_ref ref() const && = delete;
189
190private:
191 context_type context_;
192};
193
194template <typename Tag> class frame_unwinder<Tag, true> {
195public:
196 [[nodiscard]] static constexpr frame_unwinder_ref ref() noexcept { return frame_unwinder_ref(Tag{}); }
197};
198
199// ============================================================================
200// Type-Erased Frame Pointer Iterator / Cursor
201// ============================================================================
202
208public:
209 constexpr frame_pointer_iterator() noexcept = default;
210
223 constexpr frame_pointer_iterator(frame_unwinder_ref unwinder, register_context_ref reg_ctx, uintptr_t initial_fp,
224 uintptr_t initial_pc) noexcept
225 : unwinder_(unwinder), reg_ctx_(reg_ctx), frame_{0, initial_fp, initial_pc},
226 is_valid_(initial_fp != 0 && initial_pc != 0) {}
227
228 [[nodiscard]] constexpr const stack_frame &operator*() const noexcept RELOCO_LIFETIMEBOUND { return frame_; }
229 [[nodiscard]] constexpr const stack_frame *operator->() const noexcept RELOCO_LIFETIMEBOUND { return &frame_; }
230
231 [[nodiscard]] constexpr bool has_value() const noexcept { return is_valid_; }
232 [[nodiscard]] constexpr explicit operator bool() const noexcept { return is_valid_; }
233
238 bool next() noexcept {
239 if (!is_valid_)
240 return false;
241
242 uintptr_t next_fp = 0;
243 uintptr_t next_pc = 0;
244
245 if (!unwinder_.step(reg_ctx_, frame_.pc, next_fp, next_pc)) {
246 is_valid_ = false;
247 return false;
248 }
249
250 frame_.frame_index++;
251 frame_.fp = next_fp;
252 frame_.pc = next_pc;
253 return true;
254 }
255
257 next();
258 return *this;
259 }
260
261 template <typename Visitor> void for_each_frame(Visitor &&visitor, uint32_t max_depth = 64) noexcept {
262 while (is_valid_ && frame_.frame_index < max_depth) {
263 if (!visitor(frame_)) {
264 break;
265 }
266 next();
267 }
268 }
269
270private:
271 frame_unwinder_ref unwinder_{};
272 register_context_ref reg_ctx_{};
273 stack_frame frame_{};
274 bool is_valid_{false};
275};
276
277// ============================================================================
278// Backtrace View Formatter
279// ============================================================================
280
285public:
294 symbol_resolution_context &symbol_context RELOCO_LIFETIMEBOUND,
295 uint32_t max_depth = 16) noexcept
296 : iter_(iter), resolver_(resolver), symbol_context_(&symbol_context), max_depth_(max_depth) {}
297
302 [[nodiscard]] constexpr frame_pointer_iterator iterator() const noexcept { return iter_; }
307 [[nodiscard]] constexpr symbol_resolver_ref resolver() const noexcept { return resolver_; }
308 [[nodiscard]] constexpr symbol_resolution_context &symbol_context() const noexcept RELOCO_LIFETIMEBOUND {
309 return *symbol_context_;
310 }
315 [[nodiscard]] constexpr uint32_t max_depth() const noexcept { return max_depth_; }
316
317private:
321 symbol_resolver_ref resolver_{};
323 value_ptr<symbol_resolution_context> symbol_context_{};
325 uint32_t max_depth_{16};
326};
327
334template <> struct formatter<remote_backtrace_view> {
338 char mode{'\0'};
339
344 constexpr void parse(format_parse_context &ctx) noexcept {
345 auto spec = ctx.spec();
346 if (!spec.empty() && spec.front() == '#') {
347 mode = '#';
348 }
349 }
350
356 void format(const remote_backtrace_view &view, const sink &out) const noexcept {
357 frame_pointer_iterator cursor = view.iterator();
358
359 bool first = true;
360 cursor.for_each_frame(
361 [&](const stack_frame &frame) noexcept -> bool {
362 if (!first) {
363 out.write("\n");
364 }
365 first = false;
366
367 microfmt::format_to(out, " #{:<2} fp={:#x} pc=", frame.frame_index, frame.fp);
368
369 remote_fn_ptr fn_sym(frame.pc, view.resolver(), view.symbol_context());
370 if (mode == '#') {
371 microfmt::format_to(out, "{:#}", fn_sym);
372 } else {
373 microfmt::format_to(out, "{}", fn_sym);
374 }
375 return true;
376 },
377 view.max_depth());
378 }
379};
380
381} // namespace microfmt
Type-erased remote address-space access and remote string/object views.
Definition microfmt.hpp:881
Forward-only cursor iterating over a stack walk backed by register context.
Definition frame_pointer.hpp:207
constexpr bool has_value() const noexcept
Definition frame_pointer.hpp:231
void for_each_frame(Visitor &&visitor, uint32_t max_depth=64) noexcept
Definition frame_pointer.hpp:261
constexpr const stack_frame * operator->() const noexcept RELOCO_LIFETIMEBOUND
Definition frame_pointer.hpp:229
frame_pointer_iterator & operator++() noexcept
Definition frame_pointer.hpp:256
bool next() noexcept
Steps to the next (caller) frame using the unwinder and register context.
Definition frame_pointer.hpp:238
constexpr const stack_frame & operator*() const noexcept RELOCO_LIFETIMEBOUND
Definition frame_pointer.hpp:228
constexpr frame_pointer_iterator() noexcept=default
constexpr frame_unwinder_ref ref() const &noexcept RELOCO_LIFETIMEBOUND
Definition frame_pointer.hpp:182
value_ref< context_type > context() &&=delete
constexpr frame_unwinder(context_type context) noexcept
Definition frame_pointer.hpp:172
constexpr value_ref< context_type > context() &noexcept RELOCO_LIFETIMEBOUND
Definition frame_pointer.hpp:174
typename traits_type::context_type context_type
Definition frame_pointer.hpp:170
value_ref< const context_type > context() const &&=delete
constexpr value_ref< const context_type > context() const &noexcept RELOCO_LIFETIMEBOUND
Definition frame_pointer.hpp:178
static constexpr frame_unwinder_ref ref() noexcept
Definition frame_pointer.hpp:196
Type-erased, two-word handle to a frame unwinder.
Definition frame_pointer.hpp:68
constexpr frame_unwinder_ref(ArchTag, const Context &ctx RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
Constructs a handle for a stateful unwinder tag.
Definition frame_pointer.hpp:100
static constexpr frame_unwinder_ref make() noexcept
Definition frame_pointer.hpp:109
bool step(register_context_ref reg_ctx, uintptr_t current_pc, uintptr_t &next_fp, uintptr_t &next_pc) const noexcept
Advances from the current frame to its caller using register_context_ref.
Definition frame_pointer.hpp:135
static constexpr frame_unwinder_ref make(const Context &ctx RELOCO_LIFETIMEBOUND) noexcept
Definition frame_pointer.hpp:115
constexpr frame_unwinder_ref() noexcept=default
Constructs an empty (invalid) handle.
static frame_unwinder_ref make(Context &&)=delete
constexpr frame_unwinder_ref(ArchTag, Context &&)=delete
Definition frame_pointer.hpp:165
Zero-allocation, type-erased handle for inspecting and modifying CPU registers from a frame context.
Definition register_context.hpp:118
Formattable view rendering a remote backtrace.
Definition frame_pointer.hpp:284
constexpr frame_pointer_iterator iterator() const noexcept
Returns the underlying frame cursor.
Definition frame_pointer.hpp:302
constexpr symbol_resolver_ref resolver() const noexcept
Returns the symbol resolver handle.
Definition frame_pointer.hpp:307
constexpr uint32_t max_depth() const noexcept
Returns the maximum frame depth.
Definition frame_pointer.hpp:315
constexpr symbol_resolution_context & symbol_context() const noexcept RELOCO_LIFETIMEBOUND
Definition frame_pointer.hpp:308
constexpr remote_backtrace_view(frame_pointer_iterator iter, symbol_resolver_ref resolver, symbol_resolution_context &symbol_context RELOCO_LIFETIMEBOUND, uint32_t max_depth=16) noexcept
Constructs a backtrace view.
Definition frame_pointer.hpp:293
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
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
Remote fault formatting and diagnostics-aware remote pointer/struct views.
constexpr void parse(format_parse_context &ctx) noexcept
Parses the leading # mode flag.
Definition frame_pointer.hpp:344
void format(const remote_backtrace_view &view, const sink &out) const noexcept
Renders the backtrace.
Definition frame_pointer.hpp:356
Definition microfmt.hpp:985
Virtual table of unwinder operations.
Definition frame_pointer.hpp:73
Static customization point describing a frame unwinder backend.
Definition frame_pointer.hpp:56
One frame record produced during stack unwinding.
Definition frame_pointer.hpp:27
uint32_t frame_index
Zero-based frame index (depth).
Definition frame_pointer.hpp:31
uintptr_t pc
Program counter (return address) of the frame.
Definition frame_pointer.hpp:39
uintptr_t fp
Frame/saved-stack pointer of the frame.
Definition frame_pointer.hpp:35
constexpr bool is_null() const noexcept
Reports whether the record is an empty sentinel.
Definition frame_pointer.hpp:45
Caller-owned temporary storage for one symbol resolution.
Definition symbol_resolver.hpp:100
Type-erased symbol resolution for remote addresses and formattable symbol views.