microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
gdb_register_array.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
4#pragma once
5
6#include "../microfmt.hpp"
7#include "../reloco.hpp"
8#include "dwarf_abi.hpp"
9#include "register_context.hpp" // Assuming the provided snippet is here
10#include <cstdint>
11
12namespace microfmt::gdb {
13
20public:
31 template <typename AbiTraits> static void encode_all_registers(sink out, const register_context_ref &ctx) noexcept {
32 if (ctx.is_null()) {
33 return;
34 }
35
36 for (const auto &reg : AbiTraits::gdb_register_traits::layout()) {
37 encode_single_register(out, ctx, reg);
38 }
39 }
40
48 // decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths
49 // checked here.
50 RELOCO_BEGIN_UNSAFE_CODE
51 static void encode_single_register(sink out, const register_context_ref &ctx, const register_mapping &reg) noexcept {
52 if (ctx.is_null()) {
53 emit_unavailable(out, reg.bit_size / 8);
54 return;
55 }
56
57 const size_t byte_size = reg.bit_size / 8;
58 if (byte_size == 0)
59 return;
60
61 // Fast path: Most registers (including 512-bit AVX vectors) fit in 64 bytes.
62 // We use a stack buffer to avoid touching the scratch allocator for routine lookups.
63 if (byte_size <= 64) {
64 uint8_t stack_buf[64] = {};
65 if (ctx.read_raw(reg.dwarf_index, stack_buf, byte_size)) {
66 emit_hex_bytes(out, span<const uint8_t>{stack_buf, byte_size});
67 } else {
68 emit_unavailable(out, byte_size); // Context refused to read (e.g., power-gated core)
69 }
70 }
71 // Slow path: Scalable Vector Extensions (SVE) can exceed 64 bytes.
72 // Fall back to the execution context's attached scratch memory.
73 else {
74 const auto scratch = ctx.scratch();
75 if (byte_size <= scratch.size()) {
76 if (ctx.read_raw(reg.dwarf_index, scratch.data(), byte_size)) {
77 emit_hex_bytes(out, span<const uint8_t>{reinterpret_cast<const uint8_t *>(scratch.data()), byte_size});
78 } else {
79 emit_unavailable(out, byte_size);
80 }
81 } else {
82 // Context scratch buffer is too small to hold this register.
83 emit_unavailable(out, byte_size);
84 }
85 }
86 }
88
89private:
93 static void emit_hex_bytes(sink target, span<const uint8_t> data) noexcept {
94 for (uint8_t b : data) {
95 target.put(microfmt::detail::hex_digits_lower[(b >> 4) & 0x0F]);
96 target.put(microfmt::detail::hex_digits_lower[b & 0x0F]);
97 }
98 }
99
106 static void emit_unavailable(sink target, size_t byte_size) noexcept {
107 for (size_t i = 0; i < byte_size * 2; ++i) {
108 target.put('x');
109 }
110 }
111};
112
119public:
131 template <typename AbiTraits>
132 [[nodiscard]] static bool decode_all_registers(string_view hex_payload, const register_context_ref &ctx) noexcept {
133 if (ctx.is_null()) {
134 return false;
135 }
136
137 size_t offset = 0;
138 for (const auto &reg : AbiTraits::gdb_register_traits::layout()) {
139 const size_t hex_len = reg.bit_size / 4; // 2 hex chars per byte
140
141 // GDB sometimes sends truncated 'G' packets containing only core registers.
142 // If we run out of hex payload, we stop gracefully.
143 if (offset + hex_len > hex_payload.size()) {
144 break;
145 }
146
147 string_view reg_hex = hex_payload.substr(offset, hex_len);
148 offset += hex_len;
149
150 // If the register data starts with 'x', GDB is explicitly telling us
151 // this register is unavailable/ignored. Skip writing it.
152 if (!reg_hex.empty() && (reg_hex[0] == 'x' || reg_hex[0] == 'X')) {
153 continue;
154 }
155
156 if (!decode_single_register(reg_hex, ctx, reg)) {
157 return false; // Stop on first decoding/write failure
158 }
159 }
160
161 return true;
162 }
163
172 // decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths
173 // checked here.
174 RELOCO_BEGIN_UNSAFE_CODE
175 [[nodiscard]] static bool decode_single_register(string_view hex_payload, const register_context_ref &ctx,
176 const register_mapping &reg) noexcept {
177 if (ctx.is_null())
178 return false;
179
180 const size_t byte_size = reg.bit_size / 8;
181 if (byte_size == 0)
182 return true;
183
184 if (hex_payload.size() < byte_size * 2) {
185 return false; // Hex string is too short for this register
186 }
187
188 // Ignore 'x' masked registers
189 if (hex_payload[0] == 'x' || hex_payload[0] == 'X') {
190 return true;
191 }
192
193 // Fast path: Use stack buffer for registers up to 64 bytes (AVX-512)
194 if (byte_size <= 64) {
195 uint8_t stack_buf[64] = {};
196 const auto out_span = span<uint8_t>{stack_buf, byte_size};
197
198 if (!parse_hex_to_bytes(hex_payload.substr(0, byte_size * 2), out_span)) {
199 return false;
200 }
201 return ctx.write_raw(reg.dwarf_index, stack_buf, byte_size);
202 }
203 // Slow path: Fallback to context scratch buffer for massive vectors (SVE)
204 else {
205 const auto scratch = ctx.scratch();
206 if (byte_size <= scratch.size()) {
207 const auto out_span = span<uint8_t>{reinterpret_cast<uint8_t *>(scratch.data()), byte_size};
208
209 if (!parse_hex_to_bytes(hex_payload.substr(0, byte_size * 2), out_span)) {
210 return false;
211 }
212 return ctx.write_raw(reg.dwarf_index, scratch.data(), byte_size);
213 }
214 return false; // Context scratch buffer too small
215 }
216 }
218
219private:
223 // decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths
224 // checked here.
225 RELOCO_BEGIN_UNSAFE_CODE
226 [[nodiscard]] static bool parse_hex_to_bytes(string_view hex, span<uint8_t> out) noexcept {
227 for (size_t i = 0; i < out.size(); ++i) {
228 const uint8_t hi = hex_char_to_val(hex[i * 2]);
229 const uint8_t lo = hex_char_to_val(hex[i * 2 + 1]);
230
231 if (hi == 0xFF || lo == 0xFF) {
232 return false; // Invalid hex character encountered
233 }
234 out[i] = static_cast<uint8_t>((hi << 4) | lo);
235 }
236 return true;
237 }
239
243 // decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths
244 // checked here.
245 RELOCO_BEGIN_UNSAFE_CODE
246 [[nodiscard]] static constexpr uint8_t hex_char_to_val(char c) noexcept {
247 if (c >= '0' && c <= '9')
248 return static_cast<uint8_t>(c - '0');
249 if (c >= 'a' && c <= 'f')
250 return static_cast<uint8_t>(c - 'a' + 10);
251 if (c >= 'A' && c <= 'F')
252 return static_cast<uint8_t>(c - 'A' + 10);
253 return 0xFF; // Error marker
254 }
256};
257
258} // namespace microfmt::gdb
Decodes GDB RSP hex strings into binary values and writes them to the execution context.
Definition gdb_register_array.hpp:118
static bool decode_all_registers(string_view hex_payload, const register_context_ref &ctx) noexcept
Decodes the full register layout hex payload ('G' packet payload).
Definition gdb_register_array.hpp:132
static RELOCO_BEGIN_UNSAFE_CODE bool decode_single_register(string_view hex_payload, const register_context_ref &ctx, const register_mapping &reg) noexcept
Decodes a single register hex payload ('P' packet value).
Definition gdb_register_array.hpp:175
Encodes register values from a execution context into GDB RSP hex strings.
Definition gdb_register_array.hpp:19
static void encode_all_registers(sink out, const register_context_ref &ctx) noexcept
Encodes the full register layout for a specific architecture ('g' packet response).
Definition gdb_register_array.hpp:31
static RELOCO_BEGIN_UNSAFE_CODE void encode_single_register(sink out, const register_context_ref &ctx, const register_mapping &reg) noexcept
Encodes a single register based on its mapping ('p' packet response).
Definition gdb_register_array.hpp:51
Zero-allocation, type-erased handle for inspecting and modifying CPU registers from a frame context.
Definition register_context.hpp:118
#define MICROFMT_API_CLASS
Definition compat.hpp:181
Per-architecture ABI traits describing registers used during DWARF unwinding.
Definition gdb_decoders.hpp:15
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
RELOCO_END_UNSAFE_CODE
Definition remote_object.hpp:132
Maps a single register between GDB and DWARF definitions.
Definition gdb_registers.hpp:15