microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
memory_scanner.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
12#include "../formatters/hexdump.hpp"
13#include "../microfmt.hpp"
14#include "address_space.hpp"
15#include "dwarf_abi.hpp"
16#include "memory_classifier.hpp"
17#include "register_context.hpp"
18#include "symbol_resolver.hpp"
19#include <cstdint>
20#include <type_traits>
21#include <utility>
22
23namespace microfmt {
24
25template <typename Tag> struct address_source_traits;
26
30class RELOCO_POINTER address_source_ref {
31public:
32 constexpr address_source_ref() noexcept = default;
33
34 template <typename Tag, typename Context, typename Traits = address_source_traits<Tag>,
35 std::enable_if_t<std::is_convertible_v<const Context *, const typename Traits::context_type *>, int> = 0>
36 constexpr address_source_ref(Tag, const Context &ctx RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
37 : ctx_(&ctx), next_fn_(&next_entry<Tag>) {}
38
39 template <typename Tag, typename Context, std::enable_if_t<!std::is_lvalue_reference_v<Context>, int> = 0>
40 constexpr address_source_ref(Tag, Context &&) = delete;
41
42 template <typename Tag, typename Context, typename Traits = address_source_traits<Tag>>
43 [[nodiscard]] static constexpr address_source_ref make(const Context &ctx RELOCO_LIFETIMEBOUND) noexcept {
44 return address_source_ref(Tag{}, ctx);
45 }
46
47 template <typename Tag, typename Context, std::enable_if_t<!std::is_lvalue_reference_v<Context>, int> = 0>
48 static address_source_ref make(Context &&) = delete;
49
50 [[nodiscard]] bool next(uintptr_t &out_addr) const noexcept {
51 if (next_fn_ == nullptr || !ctx_)
52 return false;
53 return next_fn_(ctx_.get(), out_addr);
54 }
55
56 [[nodiscard]] constexpr explicit operator bool() const noexcept { return next_fn_ != nullptr; }
57
58private:
59 template <typename Tag> static bool next_entry(const void *context, uintptr_t &out_addr) noexcept {
60 using context_type = typename address_source_traits<Tag>::context_type;
61 const auto &typed_context = *static_cast<const context_type *>(context);
62 return address_source_traits<Tag>::next(value_ref<const context_type>(typed_context), out_addr);
63 }
64
65 value_ptr<const void> ctx_{};
66 bool (*next_fn_)(const void *ctx, uintptr_t &out_addr) noexcept {nullptr};
67};
68
69template <typename Tag> class RELOCO_OWNER address_source {
70public:
72 using context_type = typename traits_type::context_type;
73
74 constexpr explicit address_source(context_type context) noexcept : context_(std::move(context)) {}
75
76 [[nodiscard]] constexpr value_ref<context_type> context() & noexcept RELOCO_LIFETIMEBOUND {
77 return value_ref<context_type>(context_);
78 }
79
80 [[nodiscard]] constexpr value_ref<const context_type> context() const & noexcept RELOCO_LIFETIMEBOUND {
81 return value_ref<const context_type>(context_);
82 }
83
84 [[nodiscard]] constexpr address_source_ref ref() const & noexcept RELOCO_LIFETIMEBOUND {
85 return address_source_ref(Tag{}, context_);
86 }
87
88 value_ref<context_type> context() && = delete;
89 value_ref<const context_type> context() const && = delete;
90 address_source_ref ref() const && = delete;
91
92private:
93 context_type context_;
94};
95
104 size_t dump_bytes{80};
108 bool demangle_symbols{true};
109};
110
121 span<char> symbol_scratch{};
122 memory_region_info region_info{};
124 resolved_symbol_info resolved_symbol{};
125 hexdump_view dump_view{};
126 // NOLINTNEXTLINE(reloco-no-c-array): fixed-size buffer/table for span APIs
127 uint8_t dump_line_buffer[16]{};
128 address_space_ref reader_space{};
129};
130
136public:
142 template <typename AbiTraits>
144 span<const uintptr_t> raw_addresses, memory_scanner_context &context,
145 const sink &out) noexcept {
146 size_t index = 0;
147
148 // If a register context is provided, scan the architecture's ordered
149 // pointer-bearing GPR candidates.
150 if (reg_ctx) {
151 for (const auto &reg_desc : AbiTraits::register_traits::address_registers()) {
152 typename AbiTraits::register_type reg_val = 0;
153 if (reg_ctx.read_raw(reg_desc.index, &reg_val, sizeof(reg_val)) && reg_val != 0) {
154 microfmt::format_to(out, "[{}] -> ", reg_desc.name);
155 process_address(space, classifier, index++, static_cast<uintptr_t>(reg_val), context, out);
156 }
157 }
158 }
159
160 // Scan explicit raw address span
161 for (uintptr_t addr : raw_addresses) {
162 process_address(space, classifier, index++, addr, context, out);
163 }
164 }
165
171 memory_scanner_context &context, const sink &out) noexcept {
172 if (!source)
173 return;
174
175 size_t index = 0;
176 uintptr_t addr = 0;
177 while (source.next(addr)) {
178 process_address(space, classifier, index++, addr, context, out);
179 }
180 }
181
182private:
183 static void process_address(address_space_ref space, memory_classifier_ref classifier, size_t index, uintptr_t addr,
184 memory_scanner_context &context, const sink &out) noexcept {
185 context.region_info = {};
186 const bool classified = classifier && classifier.classify_address(addr, context.region_info);
187 const auto &info = context.region_info;
188
189 microfmt::format_to(out, " #{:<2} addr={:#018x} | type=", index, addr);
190
191 if (classified) {
192 print_region_type(out, info.type);
193 microfmt::format_to(out, " [{:#x} - {:#x}]", info.start_address, info.end_address);
194 } else {
195 out.write("unknown/unmapped");
196 }
197
198 if (classified && context.options.symbol_resolver && is_symbolic_region(info.type)) {
199 context.resolved_symbol = {};
200 if (context.options.symbol_resolver.resolve(addr, context.symbol_scratch, context.raw_symbol,
201 context.resolved_symbol) &&
202 context.resolved_symbol.has_symbol()) {
203 const auto &symbol = context.resolved_symbol;
204 out.write(" | symbol=");
205 if (context.options.demangle_symbols) {
206 microfmt::format_to(out, "{}", as_demangled(symbol.symbol_name));
207 } else {
208 out.write(symbol.symbol_name);
209 }
210 if (symbol.offset_from_symbol != 0) {
211 microfmt::format_to(out, "+{:#x}", symbol.offset_from_symbol);
212 }
213 }
214 }
215
216 // Determine whether dereferencing (memory reading/dumping) is permitted for
217 // this type
218 bool allow_deref = classified && info.readable;
219 if (allow_deref) {
220 switch (info.type) {
221 case memory_region_type::kernel_code:
222 case memory_region_type::direct_map:
223 case memory_region_type::user_code:
224 case memory_region_type::device_mmio:
225 case memory_region_type::guard_page:
226 case memory_region_type::ns_map:
227 case memory_region_type::ns_device_mmio:
228 allow_deref = false; // Do not dereference code, direct maps, MMIO,
229 // guard pages, or NS maps
230 break;
231 case memory_region_type::unknown:
232 case memory_region_type::kernel_data:
233 case memory_region_type::kernel_stack:
234 case memory_region_type::process_stack:
235 case memory_region_type::struct_pages:
236 case memory_region_type::kernel_heap:
237 case memory_region_type::user_data:
238 default:
239 break;
240 }
241 }
242
243 out.write("\n");
244
245 if (!space || context.options.dump_bytes == 0 || !allow_deref)
246 return;
247
248 size_t bytes_to_dump = (context.options.dump_bytes > 80) ? 80 : context.options.dump_bytes;
249 if (classified && info.end_address > addr) {
250 const size_t bytes_in_region = static_cast<size_t>(info.end_address - addr);
251 if (bytes_to_dump > bytes_in_region)
252 bytes_to_dump = bytes_in_region;
253 }
254
255 const auto reader = [](void *reader_context, uintptr_t source_address, uint8_t *buffer,
256 size_t size) noexcept -> size_t {
257 if (reader_context == nullptr)
258 return 0;
259 const auto &target_space = *static_cast<const address_space_ref *>(reader_context);
260 return target_space.read_bytes(source_address, buffer, size) ? size : 0;
261 };
262 context.reader_space = space;
263 context.dump_view = hexdump_checked(addr, bytes_to_dump, reader, &context.reader_space, 16, true);
264 format_hexdump(context.dump_view, {context.dump_line_buffer, sizeof(context.dump_line_buffer)}, out);
265 }
266
267 [[nodiscard]] static constexpr bool is_symbolic_region(memory_region_type type) noexcept {
268 switch (type) {
269 case memory_region_type::kernel_code:
270 case memory_region_type::kernel_data:
271 case memory_region_type::user_code:
272 case memory_region_type::user_data:
273 return true;
274 case memory_region_type::unknown:
275 case memory_region_type::kernel_stack:
276 case memory_region_type::process_stack:
277 case memory_region_type::direct_map:
278 case memory_region_type::struct_pages:
279 case memory_region_type::kernel_heap:
280 case memory_region_type::device_mmio:
281 case memory_region_type::guard_page:
282 case memory_region_type::ns_map:
283 case memory_region_type::ns_device_mmio:
284 default:
285 return false;
286 }
287 }
288
289 static void print_region_type(const sink &out, memory_region_type type) noexcept {
290 switch (type) {
291 case memory_region_type::kernel_code:
292 out.write("kernel_code");
293 break;
294 case memory_region_type::kernel_data:
295 out.write("kernel_data");
296 break;
297 case memory_region_type::kernel_stack:
298 out.write("kernel_stack");
299 break;
300 case memory_region_type::process_stack:
301 out.write("process_stack");
302 break;
303 case memory_region_type::direct_map:
304 out.write("direct_map");
305 break;
306 case memory_region_type::struct_pages:
307 out.write("struct_pages");
308 break;
309 case memory_region_type::kernel_heap:
310 out.write("kernel_heap");
311 break;
312 case memory_region_type::user_code:
313 out.write("user_code");
314 break;
315 case memory_region_type::user_data:
316 out.write("user_data");
317 break;
318 case memory_region_type::device_mmio:
319 out.write("device_mmio");
320 break;
321 case memory_region_type::guard_page:
322 out.write("guard_page");
323 break;
324 case memory_region_type::ns_map:
325 out.write("ns_map");
326 break;
327 case memory_region_type::ns_device_mmio:
328 out.write("ns_device_mmio");
329 break;
330 case memory_region_type::unknown:
331 default:
332 out.write("unknown");
333 break;
334 }
335 }
336};
337
338} // namespace microfmt
Type-erased remote address-space access and remote string/object views.
Type-erased abstract source iterator providing addresses to scan.
Definition memory_scanner.hpp:30
static constexpr address_source_ref make(const Context &ctx RELOCO_LIFETIMEBOUND) noexcept
Definition memory_scanner.hpp:43
constexpr address_source_ref(Tag, Context &&)=delete
static address_source_ref make(Context &&)=delete
bool next(uintptr_t &out_addr) const noexcept
Definition memory_scanner.hpp:50
constexpr address_source_ref() noexcept=default
Definition memory_scanner.hpp:69
typename traits_type::context_type context_type
Definition memory_scanner.hpp:72
value_ref< const context_type > context() const &&=delete
value_ref< context_type > context() &&=delete
constexpr address_source(context_type context) noexcept
Definition memory_scanner.hpp:74
constexpr value_ref< context_type > context() &noexcept RELOCO_LIFETIMEBOUND
Definition memory_scanner.hpp:76
constexpr value_ref< const context_type > context() const &noexcept RELOCO_LIFETIMEBOUND
Definition memory_scanner.hpp:80
constexpr address_source_ref ref() const &noexcept RELOCO_LIFETIMEBOUND
Definition memory_scanner.hpp:84
Type-erased, two-word handle to a remote address space.
Definition address_space.hpp:204
Type-erased, two-word handle for classifying memory regions and addresses.
Definition memory_classifier.hpp:81
Utility scanner for inspecting, classifying, and conditionally hex-dumping memory addresses.
Definition memory_scanner.hpp:135
static void scan_and_dump(address_space_ref space, memory_classifier_ref classifier, register_context_ref reg_ctx, span< const uintptr_t > raw_addresses, memory_scanner_context &context, const sink &out) noexcept
Scans architecture-specific register candidates followed by a sequence of explicit raw addresses.
Definition memory_scanner.hpp:143
static void scan_and_dump(address_space_ref space, memory_classifier_ref classifier, address_source_ref source, memory_scanner_context &context, const sink &out) noexcept
Scans addresses dynamically pulled from a type-erased abstract source.
Definition memory_scanner.hpp:170
Zero-allocation, type-erased handle for inspecting and modifying CPU registers from a frame context.
Definition register_context.hpp:118
Type-erased, two-word handle to a symbol resolver.
Definition symbol_resolver.hpp:132
Definition symbol_resolver.hpp:286
#define MICROFMT_API_CLASS
Definition compat.hpp:181
Per-architecture ABI traits describing registers used during DWARF unwinding.
Type-erased memory region classifier for system and kernel introspection.
void info(microfmt::string_view fmt, const Args &...args) noexcept
Definition logger.hpp:347
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
RELOCO_BEGIN_UNSAFE_CODE constexpr hexdump_view hexdump_checked(uintptr_t base_addr, size_t total_len, memory_reader_fn_t reader, void *ctx=nullptr, uint8_t bytes_per_line=16, bool show_ascii=true) noexcept
Helper for dumping memory via a fault-checked accessor probe.
Definition hexdump.hpp:64
RELOCO_END_UNSAFE_CODE MICROFMT_API void format_hexdump(const hexdump_view &h, span< uint8_t > line_buffer, const sink &out) noexcept
Formats a hex dump using caller-provided line storage.
constexpr auto as_demangled(microfmt::string_view sym) noexcept
Wraps a symbol for demangled formatting.
Definition demangle.hpp:523
Definition memory_scanner.hpp:25
Definition hexdump.hpp:37
Detailed description of a classified memory region or address.
Definition memory_classifier.hpp:42
Caller-owned reusable storage for memory scanning.
Definition memory_scanner.hpp:118
Optional memory-scanner behavior and caller-owned symbol storage.
Definition memory_scanner.hpp:102
Raw resolution result as returned by a resolver backend.
Definition symbol_resolver.hpp:26
Derived symbol info with precomputed relative offsets.
Definition symbol_resolver.hpp:53
Type-erased symbol resolution for remote addresses and formattable symbol views.