microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
gdb_encoders.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
8#include "../microfmt.hpp"
10
11namespace microfmt::gdb {
12
13// ============================================================================
14// Shared Encoding Helpers
15// ============================================================================
16
17namespace detail {
18inline void emit_hex_bytes(sink target, span<const uint8_t> data) noexcept {
19 static constexpr microfmt::string_view hex = "0123456789abcdef";
20 for (uint8_t b : data) {
21 target.put(hex[(b >> 4) & 0x0F]);
22 target.put(hex[b & 0x0F]);
23 }
24}
25
26// decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths checked
27// here.
28RELOCO_BEGIN_UNSAFE_CODE
29inline void emit_escaped_binary(sink target, span<const uint8_t> data) noexcept {
30 for (uint8_t b : data) {
31 if (b == '#' || b == '$' || b == '}' || b == '*') {
32 target.put('}');
33 target.put(static_cast<char>(b ^ 0x20));
34 } else {
35 target.put(static_cast<char>(b));
36 }
37 }
38}
40
41inline void write_thread_id(sink target, uint64_t tid) noexcept {
42 if (tid == static_cast<uint64_t>(-1)) {
43 target.write("-1");
44 } else {
45 format_to(target, MICROFMT_STRING("{:x}"), tid);
46 }
47}
48} // namespace detail
49
50// ============================================================================
51// GDB Client Role: Request Encoder
52// ============================================================================
53
58 uintptr_t addr{0};
59 size_t length{0};
60 span<const uint8_t> data{};
61
62 uint64_t thread_id{0};
63 char thread_action{'g'};
64
65 uint32_t reg_index{0};
66 uint8_t signal{0};
67
68 uint8_t bp_type{0};
69 uint32_t bp_kind{0};
70
71 string_view extra_text{};
72};
73
78public:
82 static void encode(sink target, packet_type type, const client_request_view &view) noexcept {
83 const packet_metadata &meta = get_packet_metadata(type);
84 if (!meta.prefix.empty()) {
85 target.write(meta.prefix);
86 }
87
88 // NOLINTNEXTLINE(reloco-enum-switch): most packets need no per-type handling
89 switch (type) {
90 case packet_type::continue_with_signal:
91 case packet_type::step_with_signal:
92 format_to(target, MICROFMT_STRING("{:02x}"), view.signal);
93 break;
94
95 case packet_type::read_memory:
96 format_to(target, MICROFMT_STRING("{:x},{:x}"), view.addr, view.length);
97 break;
98
99 case packet_type::write_memory_hex:
100 format_to(target, MICROFMT_STRING("{:x},{:x}:"), view.addr, view.length);
101 detail::emit_hex_bytes(target, view.data);
102 break;
103
104 case packet_type::write_memory_binary:
105 format_to(target, MICROFMT_STRING("{:x},{:x}:"), view.addr, view.length);
106 detail::emit_escaped_binary(target, view.data);
107 break;
108
109 case packet_type::read_single_register:
110 format_to(target, MICROFMT_STRING("{:x}"), view.reg_index);
111 break;
112
113 case packet_type::write_single_register:
114 format_to(target, MICROFMT_STRING("{:x}="), view.reg_index);
115 detail::emit_hex_bytes(target, view.data);
116 break;
117
118 case packet_type::write_general_registers:
119 detail::emit_hex_bytes(target, view.data);
120 break;
121
122 case packet_type::insert_break_watch:
123 case packet_type::remove_break_watch:
124 format_to(target, MICROFMT_STRING("{},{:x},{:x}"), view.bp_type, view.addr, view.bp_kind);
125 break;
126
127 case packet_type::set_thread:
128 target.put(view.thread_action);
129 detail::write_thread_id(target, view.thread_id);
130 break;
131
132 case packet_type::thread_is_alive:
133 detail::write_thread_id(target, view.thread_id);
134 break;
135
136 case packet_type::search_memory:
137 format_to(target, MICROFMT_STRING(":{:x};{:x};"), view.addr, view.length);
138 detail::emit_hex_bytes(target, view.data);
139 break;
140
141 case packet_type::query_supported:
142 case packet_type::query_thread_extra_info:
143 case packet_type::query_offsets:
144 case packet_type::query_symbol:
145 case packet_type::v_cont:
146 case packet_type::v_attach:
147 case packet_type::v_run:
148 case packet_type::v_kill:
149 if (!view.extra_text.empty()) {
150 target.write(type >= packet_type::v_cont ? ";" : ":");
151 target.write(view.extra_text);
152 }
153 break;
154
155 case packet_type::unknown:
156 case packet_type::enable_extended_mode:
157 case packet_type::halt_reason:
158 case packet_type::continue_exec:
159 case packet_type::step_exec:
160 case packet_type::kill:
161 case packet_type::detach:
162 case packet_type::restart:
163 case packet_type::read_general_registers:
164 case packet_type::query_first_thread_info:
165 case packet_type::query_subsequent_thread:
166 case packet_type::query_current_thread:
167 case packet_type::query_attached:
168 default:
169 if (!view.extra_text.empty()) {
170 target.write(view.extra_text);
171 }
172 break;
173 }
174 }
175};
176
177// ============================================================================
178// GDB Server Role: Response Encoder
179// ============================================================================
180
181enum class server_response_type : uint8_t {
182 empty, // Unsupported/Empty
183 ok, // "OK"
184 error, // "E<errcode>"
185 hex_data, // Memory/register read responses
186 binary_data, // Escaped binary responses
187 console_output, // "O<hex_string>"
188 thread_list, // "m<id1>,<id2>..."
189 thread_list_end, // "l"
190 stop_signal, // "T<sig>..."
191 stop_exit, // "W<status>" or "X<sig>"
192 raw_string // Generic strings (e.g., qSupported responses)
193};
194
199 uint8_t status_code{0}; // Error code, POSIX signal, or exit code
200 uint64_t thread_id{0}; // Context thread ID
201
202 span<const uint8_t> data{}; // Payload for hex/binary data
203 span<const uint64_t> thread_ids{}; // Active threads
204
205 string_view text{}; // Raw string or console output
206 string_view stop_reason_extra{}; // Expedited registers (e.g., "pc:0800;sp:2000;")
207};
208
213public:
217 // decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths
218 // checked here.
219 RELOCO_BEGIN_UNSAFE_CODE
220 static void encode(sink target, server_response_type type, const server_response_view &view) noexcept {
221 switch (type) {
222 case server_response_type::empty:
223 break;
224
225 case server_response_type::ok:
226 target.write("OK");
227 break;
228
229 case server_response_type::error:
230 format_to(target, MICROFMT_STRING("E{:02x}"), view.status_code);
231 break;
232
233 case server_response_type::hex_data:
234 detail::emit_hex_bytes(target, view.data);
235 break;
236
237 case server_response_type::binary_data:
238 detail::emit_escaped_binary(target, view.data);
239 break;
240
241 case server_response_type::console_output:
242 target.put('O');
243 detail::emit_hex_bytes(
244 target, span<const uint8_t>(reinterpret_cast<const uint8_t *>(view.text.data()), view.text.size()));
245 break;
246
247 case server_response_type::thread_list:
248 target.put('m');
249 for (size_t i = 0; i < view.thread_ids.size(); ++i) {
250 if (i > 0)
251 target.put(',');
252 format_to(target, MICROFMT_STRING("{:x}"), view.thread_ids[i]);
253 }
254 break;
255
256 case server_response_type::thread_list_end:
257 target.put('l');
258 break;
259
260 case server_response_type::stop_signal:
261 format_to(target, MICROFMT_STRING("T{:02x}"), view.status_code);
262 if (view.thread_id != 0) {
263 format_to(target, MICROFMT_STRING("thread:{:x};"), view.thread_id);
264 }
265 if (!view.stop_reason_extra.empty()) {
266 target.write(view.stop_reason_extra);
267 }
268 break;
269
270 case server_response_type::stop_exit:
271 format_to(target, MICROFMT_STRING("W{:02x}"), view.status_code);
272 break;
273
274 case server_response_type::raw_string:
275 target.write(view.text);
276 break;
277 }
278 }
280};
281
282} // namespace microfmt::gdb
Encodes GDB requests (acting as a GDB frontend/client).
Definition gdb_encoders.hpp:77
static void encode(sink target, packet_type type, const client_request_view &view) noexcept
Formats a GDB request payload into a sink using compile-time metadata.
Definition gdb_encoders.hpp:82
Encodes GDB responses (acting as a GDB server/stub).
Definition gdb_encoders.hpp:212
static RELOCO_BEGIN_UNSAFE_CODE void encode(sink target, server_response_type type, const server_response_view &view) noexcept
Formats a GDB response payload into a sink.
Definition gdb_encoders.hpp:220
#define MICROFMT_API_CLASS
Definition compat.hpp:181
Metadata table for GDB RSP packet types.
#define MICROFMT_STRING(s)
Definition microfmt.hpp:1801
RELOCO_BEGIN_UNSAFE_CODE void emit_escaped_binary(sink target, span< const uint8_t > data) noexcept
Definition gdb_encoders.hpp:29
void emit_hex_bytes(sink target, span< const uint8_t > data) noexcept
Definition gdb_encoders.hpp:18
RELOCO_END_UNSAFE_CODE void write_thread_id(sink target, uint64_t tid) noexcept
Definition gdb_encoders.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
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_END_UNSAFE_CODE
Definition remote_object.hpp:132
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