microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
memory_pattern_scanner.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Jarosław Pelczar <jarek@jpelczar.com>
2// SPDX-License-Identifier: BSD-2-Clause
3
14#pragma once
15
16#include "../microfmt.hpp"
17#include "../reloco.hpp"
18#include "address_space.hpp"
19#include <cstddef>
20#include <cstdint>
21#include <limits>
22#include <type_traits>
23#include <utility>
24
25namespace microfmt {
26
32 bool found{};
34 uintptr_t address{};
35};
36
37// ============================================================================
38// Provider Traits Definition
39// ============================================================================
40
49template <typename Tag> struct memory_pattern_scanner_traits;
50
51// ============================================================================
52// Type-Erased Reference
53// ============================================================================
54
61class RELOCO_POINTER memory_pattern_scanner_ref {
62public:
63 struct vtable {
64 expected<memory_scan_result, address_space_error> (*scan)(const void *ctx, uintptr_t start_addr,
65 size_t search_length, span<const std::byte> pattern,
66 span<const std::byte> mask) noexcept = nullptr;
67 };
68
70 constexpr memory_pattern_scanner_ref() noexcept = default;
71
75 template <typename Tag, typename Traits = memory_pattern_scanner_traits<Tag>,
76 std::enable_if_t<std::is_void_v<typename Traits::context_type>, int> = 0>
77 constexpr explicit memory_pattern_scanner_ref(Tag) noexcept : ctx_(nullptr), vtbl_(&s_vtbl<Tag>) {}
78
83 template <typename Tag, typename Context, typename Traits = memory_pattern_scanner_traits<Tag>,
84 std::enable_if_t<!std::is_void_v<typename Traits::context_type> &&
85 std::is_convertible_v<const Context *, const typename Traits::context_type *>,
86 int> = 0>
88 const Context &ctx RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
89 : ctx_(&ctx), vtbl_(&s_vtbl<Tag>) {}
90
91 template <typename Tag, typename Context, std::enable_if_t<!std::is_lvalue_reference_v<Context>, int> = 0>
92 constexpr memory_pattern_scanner_ref(Tag, Context &&) = delete;
93
94 template <typename Tag, typename Traits = memory_pattern_scanner_traits<Tag>,
95 std::enable_if_t<std::is_void_v<typename Traits::context_type>, int> = 0>
96 [[nodiscard]] static constexpr memory_pattern_scanner_ref make() noexcept {
97 return memory_pattern_scanner_ref(Tag{});
98 }
99
100 template <typename Tag, typename Context, typename Traits = memory_pattern_scanner_traits<Tag>,
101 std::enable_if_t<!std::is_void_v<typename Traits::context_type>, int> = 0>
102 [[nodiscard]] static constexpr memory_pattern_scanner_ref make(const Context &ctx RELOCO_LIFETIMEBOUND) noexcept {
103 return memory_pattern_scanner_ref(Tag{}, ctx);
104 }
105
106 template <typename Tag, typename Context, std::enable_if_t<!std::is_lvalue_reference_v<Context>, int> = 0>
107 static memory_pattern_scanner_ref make(Context &&) = delete;
108
119 [[nodiscard]] expected<memory_scan_result, address_space_error> scan(uintptr_t start_addr, size_t search_length,
120 span<const std::byte> pattern,
121 span<const std::byte> mask = {}) const noexcept {
122 if (vtbl_ == nullptr)
123 return unexpected(address_space_error::invalid_handle);
124 return vtbl_->scan(ctx_.get(), start_addr, search_length, pattern, mask);
125 }
126
128 [[nodiscard]] constexpr explicit operator bool() const noexcept { return vtbl_ != nullptr; }
129
130private:
131 template <typename Tag>
132 static expected<memory_scan_result, address_space_error>
133 scan_entry(const void *context, uintptr_t start_addr, size_t search_length, span<const std::byte> pattern,
134 span<const std::byte> mask) noexcept {
135 using context_type = typename memory_pattern_scanner_traits<Tag>::context_type;
136 if constexpr (std::is_void_v<context_type>) {
137 return memory_pattern_scanner_traits<Tag>::scan(start_addr, search_length, pattern, mask);
138 } else {
139 const auto &typed_context = *static_cast<const context_type *>(context);
140 return memory_pattern_scanner_traits<Tag>::scan(value_ref<const context_type>(typed_context), start_addr,
141 search_length, pattern, mask);
142 }
143 }
144
145 template <typename Tag> static constexpr vtable s_vtbl{&scan_entry<Tag>};
146
147 value_ptr<const void> ctx_{};
148 const vtable *vtbl_{nullptr};
149};
150
151// ============================================================================
152// Typed Wrapper
153// ============================================================================
154
155template <typename Tag, bool Stateless = std::is_void_v<typename memory_pattern_scanner_traits<Tag>::context_type>>
157
163template <typename Tag> class RELOCO_OWNER memory_pattern_scanner<Tag, false> {
164public:
166 using context_type = typename traits_type::context_type;
167
168 constexpr explicit memory_pattern_scanner(context_type context) noexcept : context_(std::move(context)) {}
169
170 [[nodiscard]] constexpr value_ref<context_type> context() & noexcept RELOCO_LIFETIMEBOUND {
171 return value_ref<context_type>(context_);
172 }
173
174 [[nodiscard]] constexpr value_ref<const context_type> context() const & noexcept RELOCO_LIFETIMEBOUND {
175 return value_ref<const context_type>(context_);
176 }
177
179 [[nodiscard]] constexpr memory_pattern_scanner_ref ref() const & noexcept RELOCO_LIFETIMEBOUND {
180 return memory_pattern_scanner_ref(Tag{}, context_);
181 }
182
183 value_ref<context_type> context() && = delete;
184 value_ref<const context_type> context() const && = delete;
185 memory_pattern_scanner_ref ref() const && = delete;
186
187private:
188 context_type context_;
189};
190
196template <typename Tag> class memory_pattern_scanner<Tag, true> {
197public:
198 [[nodiscard]] static constexpr memory_pattern_scanner_ref ref() noexcept { return memory_pattern_scanner_ref(Tag{}); }
199};
200
201// ============================================================================
202// Standard Linear Scanner Implementation
203// ============================================================================
204
207
213 span<std::byte> scratch;
214
215 constexpr linear_memory_scanner_context(address_space_ref space_ref, span<std::byte> scratch_buf RELOCO_LIFETIMEBOUND
216 RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
217 : space(space_ref), scratch(scratch_buf) {}
218};
219
225
226 static expected<memory_scan_result, address_space_error> scan(value_ref<const context_type> ctx, uintptr_t start_addr,
227 size_t search_length, span<const std::byte> pattern,
228 span<const std::byte> mask) noexcept {
229 if (pattern.empty()) {
230 return memory_scan_result{true, start_addr};
231 }
232 if (ctx->scratch.size() < pattern.size()) {
233 return unexpected(address_space_error::invalid_buffer);
234 }
235 if (!mask.empty() && mask.size() != pattern.size()) {
236 return unexpected(address_space_error::invalid_buffer);
237 }
238 if (!ctx->space) {
239 return unexpected(address_space_error::invalid_handle);
240 }
241
242 uintptr_t current_addr = start_addr;
243 size_t remaining_search = search_length;
244
245 // Iterate through memory using the scratch buffer as a sliding window
246 while (remaining_search >= pattern.size()) {
247 const size_t to_read = (remaining_search < ctx->scratch.size()) ? remaining_search : ctx->scratch.size();
248
249 auto read_res = ctx->space.read_bytes(current_addr, ctx->scratch.data(), to_read);
250 if (!read_res) {
251 // Stop on first read error (e.g., reaching unmapped memory)
252 return unexpected(read_res.error());
253 }
254
255 const size_t search_limit = to_read - pattern.size();
256 for (size_t i = 0; i <= search_limit; ++i) {
257 if (check_match(ctx->scratch.subspan(i), pattern, mask)) {
258 if (current_addr > std::numeric_limits<uintptr_t>::max() - i) {
259 return unexpected(address_space_error::invalid_address);
260 }
261 return memory_scan_result{true, current_addr + i};
262 }
263 }
264
265 // Advance the window, keeping (pattern.size() - 1) overlap to catch cross-chunk matches
266 const size_t advance = to_read - pattern.size() + 1;
267 if (current_addr > std::numeric_limits<uintptr_t>::max() - advance) {
268 return unexpected(address_space_error::invalid_address);
269 }
270 current_addr += advance;
271 remaining_search -= advance;
272 }
273
274 return memory_scan_result{false, 0};
275 }
276
277private:
278 [[nodiscard]] static bool check_match(span<const std::byte> mem_window, span<const std::byte> pattern,
279 span<const std::byte> mask) noexcept {
280 for (size_t i = 0; i < pattern.size(); ++i) {
281 if (!mask.empty()) {
282 if ((mem_window[i] & mask[i]) != (pattern[i] & mask[i])) {
283 return false;
284 }
285 } else {
286 if (mem_window[i] != pattern[i]) {
287 return false;
288 }
289 }
290 }
291 return true;
292 }
293};
294
295} // namespace microfmt
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
typename traits_type::context_type context_type
Definition memory_pattern_scanner.hpp:166
constexpr value_ref< const context_type > context() const &noexcept RELOCO_LIFETIMEBOUND
Definition memory_pattern_scanner.hpp:174
value_ref< const context_type > context() const &&=delete
constexpr value_ref< context_type > context() &noexcept RELOCO_LIFETIMEBOUND
Definition memory_pattern_scanner.hpp:170
value_ref< context_type > context() &&=delete
constexpr memory_pattern_scanner_ref ref() const &noexcept RELOCO_LIFETIMEBOUND
Creates a type-erased reference borrowing this scanner's context.
Definition memory_pattern_scanner.hpp:179
constexpr memory_pattern_scanner(context_type context) noexcept
Definition memory_pattern_scanner.hpp:168
static constexpr memory_pattern_scanner_ref ref() noexcept
Definition memory_pattern_scanner.hpp:198
Type-erased, zero-allocation handle to a memory pattern scanner.
Definition memory_pattern_scanner.hpp:61
static memory_pattern_scanner_ref make(Context &&)=delete
constexpr memory_pattern_scanner_ref() noexcept=default
Constructs an empty (invalid) handle.
static constexpr memory_pattern_scanner_ref make(const Context &ctx RELOCO_LIFETIMEBOUND) noexcept
Definition memory_pattern_scanner.hpp:102
static constexpr memory_pattern_scanner_ref make() noexcept
Definition memory_pattern_scanner.hpp:96
constexpr memory_pattern_scanner_ref(Tag, Context &&)=delete
expected< memory_scan_result, address_space_error > scan(uintptr_t start_addr, size_t search_length, span< const std::byte > pattern, span< const std::byte > mask={}) const noexcept
Executes a type-erased pattern scan across remote memory.
Definition memory_pattern_scanner.hpp:119
constexpr memory_pattern_scanner_ref(Tag, const Context &ctx RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
Constructs a handle for a stateful scanner tag.
Definition memory_pattern_scanner.hpp:87
Definition memory_pattern_scanner.hpp:156
#define MICROFMT_API_CLASS
Definition compat.hpp:181
Definition assert.hpp:19
State retained by the standard linear memory scanner.
Definition memory_pattern_scanner.hpp:209
span< std::byte > scratch
Caller-provided chunk buffer used for the sliding window.
Definition memory_pattern_scanner.hpp:213
constexpr linear_memory_scanner_context(address_space_ref space_ref, span< std::byte > scratch_buf RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
Definition memory_pattern_scanner.hpp:215
address_space_ref space
Target memory address space to scan.
Definition memory_pattern_scanner.hpp:211
Tag identifying the standard linear, sliding-window memory scanner.
Definition memory_pattern_scanner.hpp:206
Definition memory_pattern_scanner.hpp:63
static expected< memory_scan_result, address_space_error > scan(value_ref< const context_type > ctx, uintptr_t start_addr, size_t search_length, span< const std::byte > pattern, span< const std::byte > mask) noexcept
Definition memory_pattern_scanner.hpp:226
Static customization point for memory pattern scanners.
Definition memory_pattern_scanner.hpp:49
Result of a memory pattern scan operation.
Definition memory_pattern_scanner.hpp:30