microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
memory_diff.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
7// std-interop-file: opt-in std::span overloads for interop with APIs that already speak <span>.
8
11#include <algorithm>
12#include <cstddef>
13#include <cstdint>
14
15#include "../microfmt.hpp"
16#include "../reloco.hpp"
17
18namespace microfmt {
19
26struct MICROFMT_API_CLASS RELOCO_POINTER memory_diff_view {
27 span<const std::byte> old_data;
28 span<const std::byte> new_data;
29 uintptr_t base_address{0};
30 uint8_t bytes_per_row{16};
31};
32
43// decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths checked
44// here.
45RELOCO_BEGIN_UNSAFE_CODE
46template <typename T1, typename T2>
47[[nodiscard]] constexpr memory_diff_view mem_diff(span<const T1> old_span,
48 span<const std::byte> new_span, // Allow mixed or standard byte spans
49 uintptr_t base_addr = 0) noexcept {
50 return memory_diff_view{
51 span<const std::byte>(reinterpret_cast<const std::byte *>(old_span.data()), old_span.size_bytes()),
52 span<const std::byte>(reinterpret_cast<const std::byte *>(new_span.data()), new_span.size_bytes()), base_addr,
53 16};
54}
56
57// Overload for matching template types
58// decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths checked
59// here.
60RELOCO_BEGIN_UNSAFE_CODE
61template <typename T1, typename T2>
62[[nodiscard]] constexpr memory_diff_view mem_diff(span<const T1> old_span, span<const T2> new_span,
63 uintptr_t base_addr = 0) noexcept {
64 return memory_diff_view{
65 span<const std::byte>(reinterpret_cast<const std::byte *>(old_span.data()), old_span.size_bytes()),
66 span<const std::byte>(reinterpret_cast<const std::byte *>(new_span.data()), new_span.size_bytes()), base_addr,
67 16};
68}
70
71#if RELOCO_HAS_STD_SPAN
75// decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths checked
76// here.
77RELOCO_BEGIN_UNSAFE_CODE
78template <typename T1, typename T2, std::size_t Extent1, std::size_t Extent2>
79[[nodiscard]] constexpr memory_diff_view mem_diff(std::span<const T1, Extent1> old_span,
80 std::span<const std::byte, Extent2> new_span,
81 uintptr_t base_addr = 0) noexcept {
82 return memory_diff_view{
83 span<const std::byte>(reinterpret_cast<const std::byte *>(old_span.data()), old_span.size_bytes()),
84 span<const std::byte>(reinterpret_cast<const std::byte *>(new_span.data()), new_span.size_bytes()), base_addr,
85 16};
86}
88
89// decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths checked
90// here.
91RELOCO_BEGIN_UNSAFE_CODE
92template <typename T1, typename T2, std::size_t Extent1, std::size_t Extent2>
93[[nodiscard]] constexpr memory_diff_view mem_diff(std::span<const T1, Extent1> old_span,
94 std::span<const T2, Extent2> new_span,
95 uintptr_t base_addr = 0) noexcept {
96 return memory_diff_view{
97 span<const std::byte>(reinterpret_cast<const std::byte *>(old_span.data()), old_span.size_bytes()),
98 span<const std::byte>(reinterpret_cast<const std::byte *>(new_span.data()), new_span.size_bytes()), base_addr,
99 16};
100}
102#endif
103
104} // namespace microfmt
105
106// ============================================================================
107// Formatter Specialization
108// ============================================================================
109
110namespace microfmt {
111
112template <> struct formatter<memory_diff_view> {
113 constexpr void parse(format_parse_context &ctx) noexcept {
114 (void)ctx; // Reserved for optional configuration specifiers (e.g. custom layout)
115 }
116
117 void format(const memory_diff_view &diff, const sink &out) const noexcept {
118 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
119 const size_t total_size = std::min(diff.old_data.size(), diff.new_data.size());
120 const size_t row_size = (diff.bytes_per_row > 0) ? size_t{diff.bytes_per_row} : size_t{16};
121
122 size_t identical_rows_streak = 0;
123 size_t offset = 0;
124
125 while (offset < total_size) {
126 const size_t current_chunk_size = std::min(row_size, total_size - offset);
127
128 // Compare old and new memory chunks for this row
129 bool rows_match = true;
130 for (size_t i = 0; i < current_chunk_size; ++i) {
131 if (diff.old_data[offset + i] != diff.new_data[offset + i]) {
132 rows_match = false;
133 break;
134 }
135 }
136
137 if (rows_match) {
138 identical_rows_streak++;
139 } else {
140 // If we skipped matching rows prior to this divergence, collapse them into a summary line
141 if (identical_rows_streak > 0) {
142 out.write(microfmt::string_view(" [... "));
143 detail::format_unsigned<detail::radix::decimal>(out, identical_rows_streak, false, 0);
144 out.write(microfmt::string_view(" identical rows hidden ...]\n"));
145 identical_rows_streak = 0;
146 }
147
148 // Emit Absolute Address Header: e.g. " 0x20000000: "
149 out.write(microfmt::string_view(" 0x"));
150 detail::format_unsigned<detail::radix::hex>(out, diff.base_address + offset, false, 8);
151 out.write(microfmt::string_view(": "));
152
153 // Stream Old Bytes
154 format_row_bytes(out, diff.old_data.subspan(offset, current_chunk_size));
155 out.write(microfmt::string_view(" -> "));
156
157 // Stream New Bytes
158 format_row_bytes(out, diff.new_data.subspan(offset, current_chunk_size));
159 out.write(microfmt::string_view("\n"));
160 }
161
162 offset += row_size;
163 }
164
165 // Flush any trailing identical row streaks at the end of the dump
166 if (identical_rows_streak > 0) {
167 out.write(microfmt::string_view(" [... "));
168 detail::format_unsigned<detail::radix::decimal>(out, identical_rows_streak, false, 0);
169 out.write(microfmt::string_view(" identical rows hidden ...]\n"));
170 }
171 RELOCO_END_UNSAFE_BUFFER_USAGE;
172 }
173
174private:
175 static void format_row_bytes(const sink &out, span<const std::byte> chunk) noexcept {
176 for (size_t i = 0; i < chunk.size(); ++i) {
177 detail::format_unsigned<detail::radix::hex>(out, static_cast<uint64_t>(chunk[i]), true, 2);
178 out.write(microfmt::string_view(" ", 1));
179 }
180 }
181};
182
183} // namespace microfmt
Definition microfmt.hpp:881
#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_END_UNSAFE_CODE
Definition remote_object.hpp:132
RELOCO_BEGIN_UNSAFE_CODE constexpr memory_diff_view mem_diff(span< const T1 > old_span, span< const std::byte > new_span, uintptr_t base_addr=0) noexcept
Creates a memory diff view over two microfmt::span containers.
Definition memory_diff.hpp:47
void format(const memory_diff_view &diff, const sink &out) const noexcept
Definition memory_diff.hpp:117
constexpr void parse(format_parse_context &ctx) noexcept
Definition memory_diff.hpp:113
Definition microfmt.hpp:985
Non-owning view representing a byte-level comparison between two memory regions.
Definition memory_diff.hpp:26
span< const std::byte > old_data
Definition memory_diff.hpp:27
span< const std::byte > new_data
Definition memory_diff.hpp:28