microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
gdb_decoders.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
9#include "../microfmt.hpp"
10#include "../reloco.hpp"
11#include "../scratch_allocator.hpp"
12#include "gdb_encoders.hpp"
14
15namespace microfmt::gdb {
16
17namespace detail {
18// decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths checked
19// here.
20RELOCO_BEGIN_UNSAFE_CODE
21[[nodiscard]] constexpr bool parse_hex_nibble(char c, uint8_t &val) noexcept {
22 if (c >= '0' && c <= '9') {
23 val = static_cast<uint8_t>(c - '0');
24 return true;
25 }
26 if (c >= 'a' && c <= 'f') {
27 val = static_cast<uint8_t>(c - 'a' + 10);
28 return true;
29 }
30 if (c >= 'A' && c <= 'F') {
31 val = static_cast<uint8_t>(c - 'A' + 10);
32 return true;
33 }
34 return false;
35}
37
38// decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths checked
39// here.
40RELOCO_BEGIN_UNSAFE_CODE
41[[nodiscard]] constexpr uintptr_t parse_hex_int(string_view hex, size_t *chars_consumed = nullptr) noexcept {
42 uintptr_t val = 0;
43 size_t count = 0;
44 for (char c : hex) {
45 uint8_t n = 0;
46 if (!parse_hex_nibble(c, n))
47 break;
48 val = (val << 4) | n;
49 count++;
50 }
51 if (chars_consumed != nullptr)
52 *chars_consumed = count;
53 return val;
54}
56
57[[nodiscard]] constexpr uint64_t parse_thread_id(string_view hex) noexcept {
58 if (hex == "-1")
59 return static_cast<uint64_t>(-1);
60 return static_cast<uint64_t>(parse_hex_int(hex));
61}
62} // namespace detail
63
64// ============================================================================
65// GDB Server Role: Decoding Client Requests
66// ============================================================================
67
72public:
81 // decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths
82 // checked here.
83 RELOCO_BEGIN_UNSAFE_CODE
84 static packet_type decode(string_view payload, scratch_allocator &scratch, client_request_view &out_view) noexcept {
85 out_view = {}; // Reset view
86 const packet_type type = packet_recognizer::recognize(payload);
87 if (type == packet_type::unknown)
88 return type;
89
90 const packet_metadata &meta = get_packet_metadata(type);
91 string_view args = payload;
92 if (payload.starts_with(meta.prefix)) {
93 args = payload.substr(meta.prefix.size());
94 }
95
96 // NOLINTNEXTLINE(reloco-enum-switch): most packets need no per-type handling
97 switch (type) {
98 case packet_type::continue_with_signal:
99 case packet_type::step_with_signal:
100 out_view.signal = static_cast<uint8_t>(detail::parse_hex_int(args));
101 break;
102
103 case packet_type::read_memory: {
104 size_t consumed = 0;
105 out_view.addr = detail::parse_hex_int(args, &consumed);
106 if (consumed < args.size() && args[consumed] == ',') {
107 out_view.length = static_cast<size_t>(detail::parse_hex_int(args.substr(consumed + 1)));
108 }
109 break;
110 }
111
112 case packet_type::write_memory_hex:
113 case packet_type::write_memory_binary:
114 case packet_type::search_memory: {
115 // Format: <addr>,<length>:<data> (or :addr;len;data for search)
116 if (type == packet_type::search_memory && args.starts_with(":")) {
117 args = args.substr(1);
118 }
119 const size_t sep1 = args.find(type == packet_type::search_memory ? ';' : ',');
120 const size_t sep2 =
121 args.find(type == packet_type::search_memory ? ';' : ':', sep1 != string_view::npos ? sep1 + 1 : 0);
122
123 if (sep1 != string_view::npos && sep2 != string_view::npos) {
124 out_view.addr = detail::parse_hex_int(args.substr(0, sep1));
125 out_view.length = static_cast<size_t>(detail::parse_hex_int(args.substr(sep1 + 1, sep2 - sep1 - 1)));
126
127 const string_view data_str = args.substr(sep2 + 1);
128 if (type == packet_type::write_memory_binary) {
129 out_view.data = decode_escaped_binary(data_str, scratch);
130 } else {
131 out_view.data = decode_hex_bytes(data_str, scratch);
132 }
133 }
134 break;
135 }
136
137 case packet_type::read_single_register:
138 out_view.reg_index = static_cast<uint32_t>(detail::parse_hex_int(args));
139 break;
140
141 case packet_type::write_single_register: {
142 const size_t eq = args.find('=');
143 if (eq != string_view::npos) {
144 out_view.reg_index = static_cast<uint32_t>(detail::parse_hex_int(args.substr(0, eq)));
145 out_view.data = decode_hex_bytes(args.substr(eq + 1), scratch);
146 }
147 break;
148 }
149
150 case packet_type::write_general_registers:
151 out_view.data = decode_hex_bytes(args, scratch);
152 break;
153
154 case packet_type::insert_break_watch:
155 case packet_type::remove_break_watch: {
156 if (args.size() > 0)
157 out_view.bp_type = static_cast<uint8_t>(args[0] - '0');
158 const size_t c1 = args.find(',');
159 const size_t c2 = args.find(',', c1 != string_view::npos ? c1 + 1 : 0);
160 if (c1 != string_view::npos && c2 != string_view::npos) {
161 out_view.addr = detail::parse_hex_int(args.substr(c1 + 1, c2 - c1 - 1));
162 out_view.bp_kind = static_cast<uint32_t>(detail::parse_hex_int(args.substr(c2 + 1)));
163 }
164 break;
165 }
166
167 case packet_type::set_thread:
168 if (args.size() > 0) {
169 out_view.thread_action = args[0];
170 out_view.thread_id = detail::parse_thread_id(args.substr(1));
171 }
172 break;
173
174 case packet_type::thread_is_alive:
175 out_view.thread_id = detail::parse_thread_id(args);
176 break;
177
178 case packet_type::unknown:
179 case packet_type::enable_extended_mode:
180 case packet_type::halt_reason:
181 case packet_type::continue_exec:
182 case packet_type::step_exec:
183 case packet_type::kill:
184 case packet_type::detach:
185 case packet_type::restart:
186 case packet_type::read_general_registers:
187 case packet_type::query_supported:
188 case packet_type::query_first_thread_info:
189 case packet_type::query_subsequent_thread:
190 case packet_type::query_current_thread:
191 default:
192 // Pass complex or unparsed fields (like vCont) natively
193 out_view.extra_text = args;
194 break;
195 }
196 return type;
197 }
199
200private:
201 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE
202 // decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths
203 // checked here.
204 RELOCO_BEGIN_UNSAFE_CODE
205 static span<const uint8_t> decode_hex_bytes(string_view hex, scratch_allocator &scratch) noexcept {
206 if ((hex.size() % 2) != 0) {
207 return {};
208 }
209 const size_t len = hex.size() / 2;
210
211 uint8_t *dest = scratch.allocate<uint8_t>(len);
212 if (dest == nullptr) {
213 return {}; // Scratch memory exhausted
214 }
215
216 for (size_t i = 0; i < len; ++i) {
217 uint8_t h = 0, l = 0;
218 if (!detail::parse_hex_nibble(hex[i * 2], h) || !detail::parse_hex_nibble(hex[i * 2 + 1], l)) {
219 return {};
220 }
221 dest[i] = static_cast<uint8_t>((h << 4) | l);
222 }
223
224 return {dest, len};
225 }
227
228 // decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths
229 // checked here.
230 RELOCO_BEGIN_UNSAFE_CODE
231 static span<const uint8_t> decode_escaped_binary(string_view bin, scratch_allocator &scratch) noexcept {
232 // Worst-case size is identical to string_view length. We allocate the max bound.
233 uint8_t *dest = scratch.allocate<uint8_t>(bin.size());
234 if (dest == nullptr) {
235 return {}; // Scratch memory exhausted
236 }
237
238 size_t written = 0;
239 bool escaped = false;
240
241 for (char c : bin) {
242 if (escaped) {
243 dest[written++] = static_cast<uint8_t>(c ^ 0x20);
244 escaped = false;
245 } else if (c == '}') {
246 escaped = true;
247 } else {
248 dest[written++] = static_cast<uint8_t>(c);
249 }
250 }
251
252 // Return a precisely sized span over the decoded segment
253 return {dest, written};
254 }
256 RELOCO_END_UNSAFE_BUFFER_USAGE
257};
258
259// ============================================================================
260// GDB Client Role: Decoding Server Responses
261// ============================================================================
262
267public:
268 // decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths
269 // checked here.
270 RELOCO_BEGIN_UNSAFE_CODE
271 static server_response_type decode(string_view payload, server_response_view &out_view) noexcept {
272 out_view = {};
273
274 if (payload.empty())
275 return server_response_type::empty;
276 if (payload == "OK")
277 return server_response_type::ok;
278 if (payload == "l")
279 return server_response_type::thread_list_end;
280
281 const char prefix = payload[0];
282
283 if (prefix == 'E' && payload.size() >= 3) {
284 out_view.status_code = static_cast<uint8_t>(detail::parse_hex_int(payload.substr(1, 2)));
285 return server_response_type::error;
286 }
287
288 if (prefix == 'T' && payload.size() >= 3) {
289 out_view.status_code = static_cast<uint8_t>(detail::parse_hex_int(payload.substr(1, 2)));
290 out_view.stop_reason_extra = payload.substr(3);
291 return server_response_type::stop_signal;
292 }
293
294 if (prefix == 'W' || prefix == 'X') {
295 out_view.status_code = static_cast<uint8_t>(detail::parse_hex_int(payload.substr(1)));
296 return server_response_type::stop_exit;
297 }
298
299 if (prefix == 'O') {
300 out_view.text = payload.substr(1);
301 return server_response_type::console_output;
302 }
303
304 if (prefix == 'm' && payload.find(',') != string_view::npos) {
305 out_view.text = payload.substr(1);
306 return server_response_type::thread_list;
307 }
308
309 out_view.text = payload;
310 return server_response_type::raw_string;
311 }
313
318 // decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths
319 // checked here.
320 RELOCO_BEGIN_UNSAFE_CODE
321 static span<const uint8_t> decode_hex_data(string_view hex_data, scratch_allocator &scratch) noexcept {
322 if ((hex_data.size() % 2) != 0) {
323 return {};
324 }
325 const size_t len = hex_data.size() / 2;
326
327 uint8_t *dest = scratch.allocate<uint8_t>(len);
328 if (dest == nullptr) {
329 return {}; // Scratch memory exhausted
330 }
331
332 for (size_t i = 0; i < len; ++i) {
333 uint8_t h = 0, l = 0;
334 if (!detail::parse_hex_nibble(hex_data[i * 2], h) || !detail::parse_hex_nibble(hex_data[i * 2 + 1], l)) {
335 return {};
336 }
337 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
338 dest[i] = static_cast<uint8_t>((h << 4) | l);
339 RELOCO_END_UNSAFE_BUFFER_USAGE;
340 }
341
342 return {dest, len};
343 }
345};
346
347} // namespace microfmt::gdb
Parses incoming GDB client requests (acting as a GDB server).
Definition gdb_decoders.hpp:71
static RELOCO_BEGIN_UNSAFE_CODE packet_type decode(string_view payload, scratch_allocator &scratch, client_request_view &out_view) noexcept
Decodes a raw payload into a structured request view.
Definition gdb_decoders.hpp:84
Parses incoming GDB server responses (acting as a GDB client).
Definition gdb_decoders.hpp:266
RELOCO_END_UNSAFE_CODE static RELOCO_BEGIN_UNSAFE_CODE span< const uint8_t > decode_hex_data(string_view hex_data, scratch_allocator &scratch) noexcept
Utility for the client to decode a raw_string hex response (e.g., memory or register blocks) directly...
Definition gdb_decoders.hpp:321
static RELOCO_BEGIN_UNSAFE_CODE server_response_type decode(string_view payload, server_response_view &out_view) noexcept
Definition gdb_decoders.hpp:271
Zero-allocation bump allocator for scratch buffers.
Definition scratch_allocator.hpp:27
#define MICROFMT_API_CLASS
Definition compat.hpp:181
GDB RSP encoders for both Client (requests) and Server (responses) roles.
Fast compile-time two-level lookup for GDB RSP packets.
RELOCO_END_UNSAFE_CODE constexpr uint64_t parse_thread_id(string_view hex) noexcept
Definition gdb_decoders.hpp:57
RELOCO_BEGIN_UNSAFE_CODE constexpr bool parse_hex_nibble(char c, uint8_t &val) noexcept
Definition gdb_decoders.hpp:21
RELOCO_END_UNSAFE_CODE RELOCO_BEGIN_UNSAFE_CODE constexpr uintptr_t parse_hex_int(string_view hex, size_t *chars_consumed=nullptr) noexcept
Definition gdb_decoders.hpp:41
Definition gdb_decoders.hpp:15
packet_type
High-level classification of GDB Remote Serial Protocol commands.
Definition gdb_packet_types.hpp:64
constexpr const packet_metadata & get_packet_metadata(packet_type type) noexcept
Retrieves metadata for a specific packet type with strict bounds checking.
Definition gdb_packet_metadata.hpp:86
server_response_type
Definition gdb_encoders.hpp:181
constexpr hex_view< T > hex(T val, int width=0, bool prefix=false, bool uppercase=false) noexcept
Definition format_helpers.hpp:29
constexpr escaped_view escaped(microfmt::string_view sv, bool quote=true, bool escape_quotes=true) noexcept
Definition escaped.hpp:32
RELOCO_END_UNSAFE_CODE
Definition remote_object.hpp:132
constexpr auto bin(T val) noexcept
Definition binary.hpp:34
Zero-allocation parameters for generating a GDB client request.
Definition gdb_encoders.hpp:57
Metadata descriptor for a GDB RSP packet.
Definition gdb_packet_metadata.hpp:17
string_view prefix
Definition gdb_packet_metadata.hpp:20
Zero-allocation parameters for generating a GDB server response.
Definition gdb_encoders.hpp:198