nx_gdbstub.nx source
↩ module page · 868 lines · 40336 B
1// nx_gdbstub.nx -- GDB REMOTE SERIAL PROTOCOL stub over the sovereign RV64 emulator (rv64im_min_sim).
2//
3// THE RUNG: /compare/nishios NO1 "GDB remote-serial stub", watch symbol gdbstub_serve (ver 0.1 N0,
4// ranked #1 2026-08-23). Its accept rule is an EXTERNAL ORACLE: a STOCK gdb attaches to a guest
5// running on our emulator, breaks at an address our own decoder resolved, steps, reads a register
6// whose value an independent sim run derived, and detaches leaving the guest running -- plus the
7// negative control that a breakpoint at a never-executed address does NOT stop. That oracle lives
8// in nx_gdbstub_gate (gdb-multiarch in batch mode); this file is the stub it attaches to.
9//
10// WHAT IT SPEAKS (GDB Remote Serial Protocol, the subset a stock gdb needs for a bare-metal target):
11// framing $<payload>#<2-hex checksum>, '+'/'-' acks until QStartNoAckMode, 0x03 = interrupt
12// qSupported PacketSize + QStartNoAckMode + qXfer:features:read (we SERVE target.xml, so gdb
13// never has to guess the register file: riscv:rv64, x0..x31 + pc, 64-bit)
14// ? g G p P m M Z0 z0 s c D k vKill H T qC qAttached qfThreadInfo qsThreadInfo
15// stop replies: S05 (trap: breakpoint or single-step), S02 (interrupted by 0x03),
16// W<code> (guest halted through the finisher / exit ecall), X04 (illegal instruction)
17// everything else answers the empty packet, which is the protocol's "unsupported" and makes gdb
18// fall back (X -> M, vCont -> s/c) rather than fail.
19//
20// WHAT IT DELIBERATELY DOES NOT DO:
21// - memory packets (m/M) touch RAM ONLY (mem_base..mem_base+mem_size): a debugger read of device
22// MMIO would run the device model's side effects (a UART read clears state), and a debugger must
23// not change what it observes. Device windows answer E14. Addresses are translated through the
24// sim's own nx_rv64im_xlate, so in S/U-mode under Sv39 gdb sees the guest's virtual space, same as
25// the guest does; a walk fault answers E14 and the fault flag is cleared, never left for the guest.
26// (Documented imprecision: the sim's xlate sets the PTE A bit on a successful walk, so an S/U-mode
27// 'm' read can set A on a page the guest had not yet touched.)
28// - no RLE in replies, no binary X writes, no threads beyond the one hart (qC/H answer thread 1).
29//
30// SIZES ARE DERIVED, NOT PICKED:
31// GDB_G_HEX = (NX_RV64IM_RF_N_REGS + 1 pc) * (NX_RV64IM_RF_WIDTH / 4) hex chars = the 'g' reply
32// GDB_PACKETSIZE (advertised) = GDB_G_HEX: gdb bounds every m/M/qXfer chunk by this, so a reply
33// never exceeds the 'g' reply and a request payload never exceeds it either.
34// GDB_BUF = 2*GDB_PACKETSIZE + framing: payload bound + command prefix (shorter than a payload
35// by construction) + '$' '#' and two checksum chars.
36// breakpoints = a BITMAP over RAM, one bit per halfword (the RVC granularity), sized from
37// sim.mem_size -- no breakpoint cap to guess, O(1) test in the hot step loop.
38// GDB_POLL_STEPS = the step count between interrupt polls while continuing, = one millisecond at
39// the 20 MIPS interpreted rate nishios.plan publishes, so Ctrl-C latency is bounded
40// to ~1 ms without a syscall per step.
41//
42// SOVEREIGN: listens on LOOPBACK only through nx_http_server_listen (SIGPIPE-safe, CLOEXEC, REUSEADDR
43// by construction). Port 0 asks the kernel for a free port, which is then READ BACK with getsockname
44// and written to the ready file -- no guessed port, no collision, and the gate learns the port from
45// the artifact. The machine is built by nx_bootcap's bootcap_machine (the SAME device set the boot
46// rulers measure), held STOPPED at reset until the debugger resumes it (the qemu -S -gdb model).
47//
48// usage: nx_gdbstub <image.bin> [port|0] [readyfile]
49// serves ONE debugger session; on detach (D) or disconnect the guest keeps running to completion
50// and the transcript is printed, so "detaches leaving the guest running" is WITNESSED in the
51// output, not assumed. 'k'/vKill stop the guest instead.
52// license_tier: ORIGINAL
53import "nx_syscalls.nx"
54import "nx_http_server.nx"
55import "nx_bootcap.nx"
56
57// ---- protocol bytes, named (the lexer is happier without '#' and '$' in literals; these ARE the protocol)
58const GDB_CH_DOLLAR: i64 = 36 // '$' frame start
59const GDB_CH_HASH: i64 = 35 // '#' frame end
60const GDB_CH_PLUS: i64 = 43 // '+' ack
61const GDB_CH_MINUS: i64 = 45 // '-' nak
62const GDB_CH_INT: i64 = 3 // 0x03 interrupt (gdb Ctrl-C)
63const GDB_CH_COMMA: i64 = 44
64const GDB_CH_COLON: i64 = 58
65const GDB_CH_SEMI: i64 = 59
66const GDB_CH_EQ: i64 = 61
67
68// ---- derived sizes (see header) -- DERIVED from the regfile's own constants, never hand-counted
69const GDB_HEX_CHARS_PER_BYTE: i64 = 2
70const GDB_BITS_PER_BYTE: i64 = 8
71const GDB_HEX_PER_REG: i64 = NX_RV64IM_RF_WIDTH / GDB_BITS_PER_BYTE * GDB_HEX_CHARS_PER_BYTE // 64-bit reg -> 16 hex chars
72const GDB_PC_REGNUM: i64 = NX_RV64IM_RF_N_REGS // gdb's riscv numbering: x0..x31 then pc
73const GDB_N_REGS: i64 = NX_RV64IM_RF_N_REGS + 1 // the 'g' packet: every GPR + pc
74const GDB_G_HEX: i64 = GDB_N_REGS * GDB_HEX_PER_REG // the 'g' reply length
75const GDB_PACKETSIZE: i64 = GDB_G_HEX // advertised bound on every payload
76const GDB_FRAMING: i64 = 4 // '$' + '#' + 2 checksum chars
77const GDB_BUF: i64 = GDB_PACKETSIZE * 2 + GDB_FRAMING // payload + command prefix + framing
78const GDB_MIPS_PUBLISHED: i64 = 20 // interpreted rate, nishios.plan pos| (measured)
79const GDB_STEPS_PER_MS_AT_1MIPS: i64 = 1000
80const GDB_POLL_STEPS: i64 = GDB_MIPS_PUBLISHED * GDB_STEPS_PER_MS_AT_1MIPS // = 1 ms of guest time between interrupt polls
81const GDB_LISTEN_BACKLOG: i64 = 1 // one debugger at a time, by design
82const GDB_BYTE_MASK: i64 = 255
83const GDB_SOCKADDR_LEN: i64 = 16
84const GDB_SYS_GETSOCKNAME_RV64: i64 = 204 // rv64 number; x86ctx translates 204 -> 51
85
86// exit codes of the runner
87const GDB_EXIT_OK: i64 = 0
88const GDB_EXIT_USAGE: i64 = 2
89const GDB_EXIT_NOIMAGE: i64 = 3
90const GDB_EXIT_LISTEN: i64 = 4
91const GDB_EXIT_KILLED: i64 = 5
92
93// ---- tiny output helpers (stdout = the runner's log, read by the gate)
94func gs_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
95func gs_puts(s: *u8) -> i64 { sys_write(1, s, gs_len(s)); return 0 }
96func gs_putn(v: i64) -> i64 {
97 let bb: *u8 = sys_mmap(32)
98 var m: i64 = v
99 var neg: i64 = 0
100 if m < 0 { m = 0 - m; neg = 1 }
101 let t: *u8 = sys_mmap(32)
102 var k: i64 = 0
103 if m == 0 { t[0] = 48 as u8; k = 1 }
104 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
105 var o: i64 = 0
106 if neg == 1 { bb[0] = 45 as u8; o = 1 }
107 var i: i64 = 0
108 while i < k { bb[o + i] = t[k - 1 - i]; i = i + 1 }
109 sys_write(1, bb, k + o)
110 sys_munmap(bb, 32)
111 sys_munmap(t, 32)
112 return 0
113}
114func gs_hexnib(n: i64) -> i64 { if n < 10 { return 48 + n } return 87 + n } // '0'..'9', 'a'..'f'
115func gs_hexval(c: i64) -> i64 {
116 if c >= 48 { if c <= 57 { return c - 48 } }
117 if c >= 97 { if c <= 102 { return c - 87 } }
118 if c >= 65 { if c <= 70 { return c - 55 } }
119 return 0 - 1
120}
121// append the 64-bit value as 16 hex chars, LITTLE-ENDIAN BYTE ORDER (the RSP register/memory encoding)
122func gs_put_le64(b: *u8, o: i64, v: i64) -> i64 {
123 var i: i64 = 0
124 var p: i64 = o
125 while i < 8 {
126 let byte: i64 = (v >> (i * 8)) & GDB_BYTE_MASK
127 b[p] = gs_hexnib(byte >> 4) as u8
128 b[p + 1] = gs_hexnib(byte & 15) as u8
129 p = p + 2
130 i = i + 1
131 }
132 return p
133}
134// parse 16 hex chars at b[o] as a little-endian 64-bit value; returns the value, or sets err[0]=1
135func gs_get_le64(b: *u8, o: i64, err: *i64) -> i64 {
136 var v: i64 = 0
137 var i: i64 = 0
138 while i < 8 {
139 let h: i64 = gs_hexval(b[o + i * 2] as i64)
140 let l: i64 = gs_hexval(b[o + i * 2 + 1] as i64)
141 if h < 0 { err[0] = 1; return 0 }
142 if l < 0 { err[0] = 1; return 0 }
143 v = v | (((h << 4) | l) << (i * 8))
144 i = i + 1
145 }
146 return v
147}
148// parse a big-endian hex number (addresses, lengths, regnums) from b[o..] up to a terminator; returns
149// the value and writes the index of the first non-hex char to endp[0]. A missing number (zero digits)
150// sets err[0]=1 so an empty field can never silently read as 0.
151func gs_parse_hex(b: *u8, o: i64, n: i64, endp: *i64, err: *i64) -> i64 {
152 var v: i64 = 0
153 var i: i64 = o
154 var digits: i64 = 0
155 var go: i64 = 1
156 while go == 1 {
157 if i >= n { go = 0; continue }
158 let d: i64 = gs_hexval(b[i] as i64)
159 if d < 0 { go = 0; continue }
160 v = (v << 4) | d
161 digits = digits + 1
162 i = i + 1
163 }
164 endp[0] = i
165 if digits == 0 { err[0] = 1 }
166 return v
167}
168func gs_cat(d: *u8, o: i64, s: *u8) -> i64 {
169 var i: i64 = 0
170 var p: i64 = o
171 while s[i] != (0 as u8) { d[p] = s[i]; p = p + 1; i = i + 1 }
172 return p
173}
174func gs_starts(b: *u8, n: i64, s: *u8) -> i64 {
175 var i: i64 = 0
176 while s[i] != (0 as u8) {
177 if i >= n { return 0 }
178 if b[i] != s[i] { return 0 }
179 i = i + 1
180 }
181 return 1
182}
183func gs_eq(b: *u8, n: i64, s: *u8) -> i64 {
184 if gs_starts(b, n, s) == 0 { return 0 }
185 if gs_len(s) != n { return 0 }
186 return 1
187}
188
189// ---- the stub
190struct NxGdbStub {
191 sim: *NxRv64imSim
192 lfd: i64
193 cfd: i64
194 port: i64
195 rx: *u8
196 rx_n: i64
197 pkt: *u8 // decoded request payload
198 tx: *u8 // reply payload being built
199 frame: *u8 // framed reply ($..#xx)
200 xml: *u8 // target.xml
201 xml_n: i64
202 bp_bits: *u8 // breakpoint bitmap over RAM, 1 bit per halfword
203 bp_bytes: i64
204 n_bp: i64
205 noack: i64
206 detached: i64
207 killed: i64
208 eof: i64
209 pend_int: i64 // a 0x03 arrived while we were not running
210 pkts_rx: i64
211 pkts_tx: i64
212 naks: i64
213 bad_cksum: i64
214}
215const GDB_STUB_BYTES: i64 = 256 // 24 fields * 8 = 192; room for 8 more -- sized like NX_RV64IM_SIM_BYTES
216
217// cat / decimal-cat that can run in MEASURE mode (no store) -- used by the two-pass XML emitter
218func gs_xcat(d: *u8, o: i64, measure_only: i64, s: *u8) -> i64 {
219 if measure_only == 1 { return o + gs_len(s) }
220 return gs_cat(d, o, s)
221}
222func gs_xcatn(d: *u8, o: i64, measure_only: i64, v: i64) -> i64 {
223 let t: *u8 = sys_mmap(32)
224 var m: i64 = v
225 var k: i64 = 0
226 if m == 0 { t[0] = 48 as u8; k = 1 }
227 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
228 var p: i64 = o
229 if measure_only == 0 { var j: i64 = k; while j > 0 { j = j - 1; d[p] = t[j]; p = p + 1 } }
230 if measure_only == 1 { p = o + k }
231 sys_munmap(t, 32)
232 return p
233}
234
235// ---- target.xml: the register file DESCRIBED to gdb, derived from the regfile constants + the ABI names
236// gdb's riscv feature (org.gnu.gdb.riscv.cpu) requires these names (or xN aliases) for regnums 0..31 and pc.
237func gs_abi_name(i: i64) -> *u8 {
238 if i == 0 { return "zero" as *u8 }
239 if i == 1 { return "ra" as *u8 }
240 if i == 2 { return "sp" as *u8 }
241 if i == 3 { return "gp" as *u8 }
242 if i == 4 { return "tp" as *u8 }
243 if i == 5 { return "t0" as *u8 }
244 if i == 6 { return "t1" as *u8 }
245 if i == 7 { return "t2" as *u8 }
246 if i == 8 { return "fp" as *u8 }
247 if i == 9 { return "s1" as *u8 }
248 if i == 10 { return "a0" as *u8 }
249 if i == 11 { return "a1" as *u8 }
250 if i == 12 { return "a2" as *u8 }
251 if i == 13 { return "a3" as *u8 }
252 if i == 14 { return "a4" as *u8 }
253 if i == 15 { return "a5" as *u8 }
254 if i == 16 { return "a6" as *u8 }
255 if i == 17 { return "a7" as *u8 }
256 if i == 18 { return "s2" as *u8 }
257 if i == 19 { return "s3" as *u8 }
258 if i == 20 { return "s4" as *u8 }
259 if i == 21 { return "s5" as *u8 }
260 if i == 22 { return "s6" as *u8 }
261 if i == 23 { return "s7" as *u8 }
262 if i == 24 { return "s8" as *u8 }
263 if i == 25 { return "s9" as *u8 }
264 if i == 26 { return "s10" as *u8 }
265 if i == 27 { return "s11" as *u8 }
266 if i == 28 { return "t3" as *u8 }
267 if i == 29 { return "t4" as *u8 }
268 if i == 30 { return "t5" as *u8 }
269 if i == 31 { return "t6" as *u8 }
270 return "pc" as *u8
271}
272// Emit the XML into x (or only MEASURE it when x is null): returns the byte length. Two passes --
273// measure, then allocate exactly -- so there is no XML capacity constant to guess or outgrow.
274func gs_xml_emit(x: *u8, measure_only: i64) -> i64 {
275 var o: i64 = 0
276 o = gs_xcat(x, o, measure_only, "<?xml version=\"1.0\"?><!DOCTYPE target SYSTEM \"gdb-target.dtd\"><target version=\"1.0\"><architecture>riscv:rv64</architecture><feature name=\"org.gnu.gdb.riscv.cpu\">" as *u8)
277 var i: i64 = 0
278 while i < NX_RV64IM_RF_N_REGS {
279 o = gs_xcat(x, o, measure_only, "<reg name=\"" as *u8)
280 o = gs_xcat(x, o, measure_only, gs_abi_name(i))
281 o = gs_xcat(x, o, measure_only, "\" bitsize=\"" as *u8)
282 o = gs_xcatn(x, o, measure_only, NX_RV64IM_RF_WIDTH)
283 o = gs_xcat(x, o, measure_only, "\" type=\"int\" regnum=\"" as *u8)
284 o = gs_xcatn(x, o, measure_only, i)
285 o = gs_xcat(x, o, measure_only, "\"/>" as *u8)
286 i = i + 1
287 }
288 o = gs_xcat(x, o, measure_only, "<reg name=\"pc\" bitsize=\"" as *u8)
289 o = gs_xcatn(x, o, measure_only, NX_RV64IM_RF_WIDTH)
290 o = gs_xcat(x, o, measure_only, "\" type=\"code_ptr\" regnum=\"" as *u8)
291 o = gs_xcatn(x, o, measure_only, GDB_PC_REGNUM)
292 o = gs_xcat(x, o, measure_only, "\"/></feature></target>" as *u8)
293 return o
294}
295func gs_build_xml(st: *NxGdbStub) -> i64 {
296 let need: i64 = gs_xml_emit(0 as *u8, 1)
297 let x: *u8 = sys_mmap(need + 1)
298 let got: i64 = gs_xml_emit(x, 0)
299 st.xml = x
300 st.xml_n = got
301 return got
302}
303
304func gs_new(sim: *NxRv64imSim) -> *NxGdbStub {
305 let st: *NxGdbStub = (sys_mmap(GDB_STUB_BYTES)) as *NxGdbStub
306 st.sim = sim
307 st.lfd = 0 - 1
308 st.cfd = 0 - 1
309 st.port = 0
310 st.rx = sys_mmap(GDB_BUF)
311 st.rx_n = 0
312 st.pkt = sys_mmap(GDB_BUF)
313 st.tx = sys_mmap(GDB_BUF)
314 st.frame = sys_mmap(GDB_BUF)
315 // bitmap: one bit per halfword of RAM -> mem_size/2 bits -> mem_size/16 bytes (+1 for the remainder)
316 st.bp_bytes = (sim.mem_size / 16) + 1
317 st.bp_bits = sys_mmap(st.bp_bytes)
318 st.n_bp = 0
319 st.noack = 0
320 st.detached = 0
321 st.killed = 0
322 st.eof = 0
323 st.pend_int = 0
324 st.pkts_rx = 0
325 st.pkts_tx = 0
326 st.naks = 0
327 st.bad_cksum = 0
328 gs_build_xml(st)
329 return st
330}
331
332// ---- breakpoints (bitmap over RAM; addresses outside RAM are refused -- nothing is ever fetched there)
333func gs_bp_ok(st: *NxGdbStub, addr: i64) -> i64 {
334 if addr < st.sim.mem_base { return 0 }
335 if addr >= st.sim.mem_base + st.sim.mem_size { return 0 }
336 return 1
337}
338func gs_bp_test(st: *NxGdbStub, addr: i64) -> i64 {
339 if st.n_bp == 0 { return 0 }
340 if gs_bp_ok(st, addr) == 0 { return 0 }
341 let idx: i64 = (addr - st.sim.mem_base) >> 1
342 let byte: i64 = st.bp_bits[idx >> 3] as i64
343 if ((byte >> (idx & 7)) & 1) == 1 { return 1 }
344 return 0
345}
346func gs_bp_set(st: *NxGdbStub, addr: i64, on: i64) -> i64 {
347 if gs_bp_ok(st, addr) == 0 { return 0 }
348 let idx: i64 = (addr - st.sim.mem_base) >> 1
349 let was: i64 = gs_bp_test(st, addr)
350 let cur: i64 = st.bp_bits[idx >> 3] as i64
351 let bit: i64 = 1 << (idx & 7)
352 if on == 1 {
353 st.bp_bits[idx >> 3] = (cur | bit) as u8
354 if was == 0 { st.n_bp = st.n_bp + 1 }
355 } else {
356 st.bp_bits[idx >> 3] = (cur & (GDB_BYTE_MASK - bit)) as u8
357 if was == 1 { st.n_bp = st.n_bp - 1 }
358 }
359 return 1
360}
361
362// ---- wire: fill rx from the socket. Returns bytes read, 0 on EOF (sets st.eof), <0 on error.
363func gs_fill(st: *NxGdbStub) -> i64 {
364 if st.rx_n >= GDB_BUF { st.rx_n = 0 } // a frame larger than the advertised bound is discarded, not overflowed
365 let n: i64 = sys_read(st.cfd, ((st.rx as i64) + st.rx_n) as *u8, GDB_BUF - st.rx_n)
366 if n <= 0 { st.eof = 1; return n }
367 st.rx_n = st.rx_n + n
368 return n
369}
370func gs_consume(st: *NxGdbStub, n: i64) -> i64 {
371 var i: i64 = 0
372 while i + n < st.rx_n { st.rx[i] = st.rx[i + n]; i = i + 1 }
373 st.rx_n = st.rx_n - n
374 if st.rx_n < 0 { st.rx_n = 0 }
375 return 0
376}
377// frame + send one reply. In ack mode, resend on '-' and consume the '+'. A 0x03 seen while waiting
378// for the ack is remembered as a pending interrupt (gdb may type Ctrl-C at any time).
379func gs_send(st: *NxGdbStub, payload: *u8, n: i64) -> i64 {
380 let f: *u8 = st.frame
381 f[0] = GDB_CH_DOLLAR as u8
382 var sum: i64 = 0
383 var i: i64 = 0
384 while i < n { f[1 + i] = payload[i]; sum = sum + (payload[i] as i64); i = i + 1 }
385 sum = sum & GDB_BYTE_MASK
386 f[1 + n] = GDB_CH_HASH as u8
387 f[2 + n] = gs_hexnib(sum >> 4) as u8
388 f[3 + n] = gs_hexnib(sum & 15) as u8
389 let total: i64 = n + GDB_FRAMING
390 var sent: i64 = 0
391 var resends: i64 = 0
392 var done: i64 = 0
393 while done == 0 {
394 sent = sys_write(st.cfd, f, total)
395 st.pkts_tx = st.pkts_tx + 1
396 if sent < 0 { st.eof = 1; return 0 - 1 }
397 if st.noack == 1 { return 0 }
398 // wait for '+' or '-'
399 var got: i64 = 0
400 while got == 0 {
401 if st.rx_n == 0 { if gs_fill(st) <= 0 { return 0 - 1 } }
402 let c: i64 = st.rx[0] as i64
403 gs_consume(st, 1)
404 if c == GDB_CH_PLUS { got = 1; done = 1 }
405 if c == GDB_CH_MINUS {
406 got = 1
407 st.naks = st.naks + 1
408 resends = resends + 1
409 // a link that NAKs more frames than a frame has bytes is not a link -- give up, announced
410 if resends > GDB_PACKETSIZE { gs_puts("GDBSTUB link-dead: nak storm\n" as *u8); st.eof = 1; return 0 - 1 }
411 }
412 if c == GDB_CH_INT { st.pend_int = 1 }
413 // any other byte before the ack is protocol noise: dropped, and the line is held until '+'
414 // arrives (gdbserver resends on it; a stock gdb always acks, so neither choice is ever exercised
415 // by a conforming peer -- a non-acking peer sees its next frame consumed here, by design)
416 }
417 }
418 return 0
419}
420func gs_send_str(st: *NxGdbStub, s: *u8) -> i64 { return gs_send(st, s, gs_len(s)) }
421func gs_send_empty(st: *NxGdbStub) -> i64 { return gs_send(st, st.tx, 0) }
422
423// receive the next packet into st.pkt; returns payload length (>=0), or -1 on EOF/error.
424// Bytes before the '$' are acks (ignored), or 0x03 (remembered as a pending interrupt). A frame whose
425// checksum does not match is NAK'd ('-') and skipped; a good frame is ACK'd ('+') unless no-ack mode.
426func gs_recv(st: *NxGdbStub, outlen: *i64) -> i64 {
427 var forever: i64 = 1
428 while forever == 1 {
429 // locate '$'
430 var s: i64 = 0 - 1
431 var i: i64 = 0
432 while i < st.rx_n {
433 if s < 0 {
434 let c: i64 = st.rx[i] as i64
435 if c == GDB_CH_DOLLAR { s = i }
436 if s < 0 { if c == GDB_CH_INT { st.pend_int = 1 } }
437 }
438 i = i + 1
439 }
440 if s < 0 {
441 st.rx_n = 0 // nothing but acks / noise: drop it
442 if gs_fill(st) <= 0 { return 0 - 1 }
443 continue
444 }
445 if s > 0 { gs_consume(st, s); s = 0 }
446 // locate '#' followed by two checksum chars
447 var h: i64 = 0 - 1
448 var j: i64 = 1
449 while j < st.rx_n {
450 if h < 0 { if (st.rx[j] as i64) == GDB_CH_HASH { h = j } }
451 j = j + 1
452 }
453 if h < 0 { if gs_fill(st) <= 0 { return 0 - 1 } continue }
454 if h + 2 >= st.rx_n { if gs_fill(st) <= 0 { return 0 - 1 } continue }
455 // checksum
456 var sum: i64 = 0
457 var k: i64 = 1
458 while k < h { sum = sum + (st.rx[k] as i64); k = k + 1 }
459 sum = sum & GDB_BYTE_MASK
460 let c1: i64 = gs_hexval(st.rx[h + 1] as i64)
461 let c2: i64 = gs_hexval(st.rx[h + 2] as i64)
462 var want: i64 = 0 - 1
463 if c1 >= 0 { if c2 >= 0 { want = (c1 << 4) | c2 } }
464 let plen: i64 = h - 1
465 if want != sum {
466 st.bad_cksum = st.bad_cksum + 1
467 if st.noack == 0 { let nak: *u8 = sys_mmap(1); nak[0] = GDB_CH_MINUS as u8; sys_write(st.cfd, nak, 1); sys_munmap(nak, 1) }
468 gs_consume(st, h + 3)
469 continue
470 }
471 var p: i64 = 0
472 while p < plen { st.pkt[p] = st.rx[1 + p]; p = p + 1 }
473 st.pkt[plen] = 0 as u8
474 gs_consume(st, h + 3)
475 if st.noack == 0 { let ack: *u8 = sys_mmap(1); ack[0] = GDB_CH_PLUS as u8; sys_write(st.cfd, ack, 1); sys_munmap(ack, 1) }
476 st.pkts_rx = st.pkts_rx + 1
477 outlen[0] = plen
478 return plen
479 }
480 return 0 - 1
481}
482
483// ---- stop replies
484func gs_reply_halt(st: *NxGdbStub) -> i64 {
485 let s: *NxRv64imSim = st.sim
486 let t: *u8 = st.tx
487 if s.halt_code >= 0 {
488 t[0] = 87 as u8 // 'W' exited with code
489 let code: i64 = s.halt_code & GDB_BYTE_MASK
490 t[1] = gs_hexnib(code >> 4) as u8
491 t[2] = gs_hexnib(code & 15) as u8
492 return gs_send(st, t, 3)
493 }
494 return gs_send_str(st, "X04" as *u8) // the sim's illegal-instruction halt -> SIGILL
495}
496func gs_reply_trap(st: *NxGdbStub) -> i64 { return gs_send_str(st, "S05" as *u8) }
497func gs_reply_int(st: *NxGdbStub) -> i64 { return gs_send_str(st, "S02" as *u8) }
498
499// poll the debugger socket without blocking; returns 1 if an interrupt (0x03) arrived, -1 on EOF, else 0.
500func gs_poll_int(st: *NxGdbStub) -> i64 {
501 let pfd: *u8 = sys_mmap(8)
502 pfd[0] = (st.cfd & GDB_BYTE_MASK) as u8
503 pfd[1] = ((st.cfd >> 8) & GDB_BYTE_MASK) as u8
504 pfd[2] = ((st.cfd >> 16) & GDB_BYTE_MASK) as u8
505 pfd[3] = ((st.cfd >> 24) & GDB_BYTE_MASK) as u8
506 pfd[4] = 1 as u8 // POLLIN
507 pfd[5] = 0 as u8
508 pfd[6] = 0 as u8
509 pfd[7] = 0 as u8
510 let r: i64 = sys_poll(pfd, 1, 0)
511 sys_munmap(pfd, 8)
512 if r <= 0 { return 0 }
513 if gs_fill(st) <= 0 { return 0 - 1 }
514 var i: i64 = 0
515 var found: i64 = 0
516 while i < st.rx_n {
517 if (st.rx[i] as i64) == GDB_CH_INT { found = 1 }
518 i = i + 1
519 }
520 if found == 1 {
521 // drop the interrupt byte(s); any packet bytes after it stay queued for gs_recv
522 var w: i64 = 0
523 var rd: i64 = 0
524 while rd < st.rx_n { let c: i64 = st.rx[rd] as i64; if c != GDB_CH_INT { st.rx[w] = st.rx[rd]; w = w + 1 } rd = rd + 1 }
525 st.rx_n = w
526 return 1
527 }
528 return 0
529}
530
531// resume: single_step=1 -> exactly one instruction; else run until a breakpoint, a halt, or an interrupt.
532// A breakpoint at the CURRENT pc is stepped over first (the resume-from-breakpoint rule); every later
533// pc is tested BEFORE it executes, so gdb observes pc == breakpoint address.
534func gs_resume(st: *NxGdbStub, single_step: i64) -> i64 {
535 let s: *NxRv64imSim = st.sim
536 if s.halted == 1 { return gs_reply_halt(st) }
537 if st.pend_int == 1 { st.pend_int = 0; return gs_reply_int(st) }
538 var since_poll: i64 = 0
539 var go: i64 = 1
540 while go == 1 {
541 nx_rv64im_sim_step(s)
542 if s.halted == 1 { return gs_reply_halt(st) }
543 if single_step == 1 { return gs_reply_trap(st) }
544 if gs_bp_test(st, s.pc) == 1 { return gs_reply_trap(st) }
545 since_poll = since_poll + 1
546 if since_poll >= GDB_POLL_STEPS {
547 since_poll = 0
548 let pi: i64 = gs_poll_int(st)
549 if pi == 1 { return gs_reply_int(st) }
550 if pi < 0 { return 0 - 1 }
551 }
552 }
553 return 0
554}
555
556// ---- memory access for m/M: RAM only, through the sim's own translation
557// returns the RAM offset for vaddr, or -1 (device window, outside RAM, or a walk fault)
558func gs_ram_off(st: *NxGdbStub, vaddr: i64) -> i64 {
559 let s: *NxRv64imSim = st.sim
560 s.xlate_fault = 0
561 let pa: i64 = nx_rv64im_xlate(s, vaddr, NX_ACC_LOAD)
562 if s.xlate_fault == 1 { s.xlate_fault = 0; return 0 - 1 }
563 if pa < s.mem_base { return 0 - 1 }
564 if pa >= s.mem_base + s.mem_size { return 0 - 1 }
565 return pa - s.mem_base
566}
567
568// ---- packet handlers. Each returns 0 normally, -1 if the link died.
569func gs_do_g(st: *NxGdbStub) -> i64 {
570 let t: *u8 = st.tx
571 var o: i64 = 0
572 var i: i64 = 0
573 while i < NX_RV64IM_RF_N_REGS { o = gs_put_le64(t, o, nx_rv64im_rf_read(st.sim.rf, i)); i = i + 1 }
574 o = gs_put_le64(t, o, st.sim.pc)
575 return gs_send(st, t, o)
576}
577func gs_do_G(st: *NxGdbStub, n: i64) -> i64 {
578 if n < 1 + GDB_G_HEX { return gs_send_str(st, "E01" as *u8) }
579 let err: *i64 = sys_mmap(8) as *i64
580 err[0] = 0
581 var i: i64 = 0
582 while i < NX_RV64IM_RF_N_REGS {
583 let v: i64 = gs_get_le64(st.pkt, 1 + i * GDB_HEX_PER_REG, err)
584 if i > 0 { nx_rv64im_rf_write(st.sim.rf, i, v) } // x0 stays hardwired zero
585 i = i + 1
586 }
587 let pcv: i64 = gs_get_le64(st.pkt, 1 + NX_RV64IM_RF_N_REGS * GDB_HEX_PER_REG, err)
588 if err[0] == 1 { sys_munmap(err as *u8, 8); return gs_send_str(st, "E01" as *u8) }
589 st.sim.pc = pcv
590 sys_munmap(err as *u8, 8)
591 return gs_send_str(st, "OK" as *u8)
592}
593func gs_do_p(st: *NxGdbStub, n: i64) -> i64 {
594 let endp: *i64 = sys_mmap(8) as *i64
595 let err: *i64 = sys_mmap(8) as *i64
596 err[0] = 0
597 let r: i64 = gs_parse_hex(st.pkt, 1, n, endp, err)
598 if err[0] == 1 { return gs_send_str(st, "E01" as *u8) }
599 var v: i64 = 0
600 if r < NX_RV64IM_RF_N_REGS { v = nx_rv64im_rf_read(st.sim.rf, r) }
601 if r == GDB_PC_REGNUM { v = st.sim.pc }
602 if r > GDB_PC_REGNUM { return gs_send_str(st, "E01" as *u8) }
603 let o: i64 = gs_put_le64(st.tx, 0, v)
604 return gs_send(st, st.tx, o)
605}
606func gs_do_P(st: *NxGdbStub, n: i64) -> i64 {
607 let endp: *i64 = sys_mmap(8) as *i64
608 let err: *i64 = sys_mmap(8) as *i64
609 err[0] = 0
610 let r: i64 = gs_parse_hex(st.pkt, 1, n, endp, err)
611 if err[0] == 1 { return gs_send_str(st, "E01" as *u8) }
612 if (st.pkt[endp[0]] as i64) != GDB_CH_EQ { return gs_send_str(st, "E01" as *u8) }
613 let v: i64 = gs_get_le64(st.pkt, endp[0] + 1, err)
614 if err[0] == 1 { return gs_send_str(st, "E01" as *u8) }
615 if r > GDB_PC_REGNUM { return gs_send_str(st, "E01" as *u8) }
616 if r == GDB_PC_REGNUM { st.sim.pc = v }
617 if r < NX_RV64IM_RF_N_REGS { if r > 0 { nx_rv64im_rf_write(st.sim.rf, r, v) } }
618 return gs_send_str(st, "OK" as *u8)
619}
620func gs_do_m(st: *NxGdbStub, n: i64) -> i64 {
621 let endp: *i64 = sys_mmap(8) as *i64
622 let err: *i64 = sys_mmap(8) as *i64
623 err[0] = 0
624 let addr: i64 = gs_parse_hex(st.pkt, 1, n, endp, err)
625 if err[0] == 1 { return gs_send_str(st, "E01" as *u8) }
626 if (st.pkt[endp[0]] as i64) != GDB_CH_COMMA { return gs_send_str(st, "E01" as *u8) }
627 let len: i64 = gs_parse_hex(st.pkt, endp[0] + 1, n, endp, err)
628 if err[0] == 1 { return gs_send_str(st, "E01" as *u8) }
629 if len < 0 { return gs_send_str(st, "E01" as *u8) }
630 if len * 2 > GDB_PACKETSIZE { return gs_send_str(st, "E01" as *u8) } // gdb honours PacketSize; refuse, never overflow
631 var i: i64 = 0
632 var o: i64 = 0
633 while i < len {
634 let off: i64 = gs_ram_off(st, addr + i)
635 if off < 0 { return gs_send_str(st, "E14" as *u8) }
636 let b: i64 = st.sim.mem_buf[off] as i64
637 st.tx[o] = gs_hexnib(b >> 4) as u8
638 st.tx[o + 1] = gs_hexnib(b & 15) as u8
639 o = o + 2
640 i = i + 1
641 }
642 return gs_send(st, st.tx, o)
643}
644func gs_do_M(st: *NxGdbStub, n: i64) -> i64 {
645 let endp: *i64 = sys_mmap(8) as *i64
646 let err: *i64 = sys_mmap(8) as *i64
647 err[0] = 0
648 let addr: i64 = gs_parse_hex(st.pkt, 1, n, endp, err)
649 if err[0] == 1 { return gs_send_str(st, "E01" as *u8) }
650 if (st.pkt[endp[0]] as i64) != GDB_CH_COMMA { return gs_send_str(st, "E01" as *u8) }
651 let len: i64 = gs_parse_hex(st.pkt, endp[0] + 1, n, endp, err)
652 if err[0] == 1 { return gs_send_str(st, "E01" as *u8) }
653 if (st.pkt[endp[0]] as i64) != GDB_CH_COLON { return gs_send_str(st, "E01" as *u8) }
654 let data: i64 = endp[0] + 1
655 if data + len * 2 > n { return gs_send_str(st, "E01" as *u8) }
656 // translate and validate EVERY byte first, so a write is all-or-nothing (a half-applied M is worse than E14)
657 var i: i64 = 0
658 while i < len {
659 if gs_ram_off(st, addr + i) < 0 { return gs_send_str(st, "E14" as *u8) }
660 i = i + 1
661 }
662 i = 0
663 while i < len {
664 let off: i64 = gs_ram_off(st, addr + i)
665 let h: i64 = gs_hexval(st.pkt[data + i * 2] as i64)
666 let l: i64 = gs_hexval(st.pkt[data + i * 2 + 1] as i64)
667 if h < 0 { return gs_send_str(st, "E01" as *u8) }
668 if l < 0 { return gs_send_str(st, "E01" as *u8) }
669 st.sim.mem_buf[off] = ((h << 4) | l) as u8
670 i = i + 1
671 }
672 return gs_send_str(st, "OK" as *u8)
673}
674// Z0,addr,kind / z0,addr,kind -- software breakpoint (we never patch guest memory: the bitmap is
675// the breakpoint, so the guest's own code bytes stay exactly what it loaded). Other Z types: unsupported.
676func gs_do_Z(st: *NxGdbStub, n: i64, on: i64) -> i64 {
677 if (st.pkt[1] as i64) != 48 { return gs_send_empty(st) } // only type 0
678 if (st.pkt[2] as i64) != GDB_CH_COMMA { return gs_send_str(st, "E01" as *u8) }
679 let endp: *i64 = sys_mmap(8) as *i64
680 let err: *i64 = sys_mmap(8) as *i64
681 err[0] = 0
682 let addr: i64 = gs_parse_hex(st.pkt, 3, n, endp, err)
683 if err[0] == 1 { return gs_send_str(st, "E01" as *u8) }
684 if gs_bp_set(st, addr, on) == 0 { return gs_send_str(st, "E01" as *u8) } // outside RAM: refused, named by the error
685 return gs_send_str(st, "OK" as *u8)
686}
687func gs_do_qSupported(st: *NxGdbStub) -> i64 {
688 var o: i64 = gs_cat(st.tx, 0, "PacketSize=" as *u8)
689 // GDB_PACKETSIZE in hex
690 var v: i64 = GDB_PACKETSIZE
691 let t: *u8 = sys_mmap(16)
692 var k: i64 = 0
693 while v > 0 { t[k] = gs_hexnib(v & 15) as u8; v = v >> 4; k = k + 1 }
694 while k > 0 { k = k - 1; st.tx[o] = t[k]; o = o + 1 }
695 sys_munmap(t, 16)
696 o = gs_cat(st.tx, o, ";QStartNoAckMode+;qXfer:features:read+" as *u8)
697 return gs_send(st, st.tx, o)
698}
699// qXfer:features:read:target.xml:OFF,LEN -> 'm'<chunk> or 'l'<rest>
700func gs_do_qXfer(st: *NxGdbStub, n: i64) -> i64 {
701 let pre: *u8 = "qXfer:features:read:target.xml:" as *u8
702 if gs_starts(st.pkt, n, pre) == 0 { return gs_send_empty(st) }
703 let endp: *i64 = sys_mmap(8) as *i64
704 let err: *i64 = sys_mmap(8) as *i64
705 err[0] = 0
706 let off: i64 = gs_parse_hex(st.pkt, gs_len(pre), n, endp, err)
707 if err[0] == 1 { return gs_send_str(st, "E01" as *u8) }
708 if (st.pkt[endp[0]] as i64) != GDB_CH_COMMA { return gs_send_str(st, "E01" as *u8) }
709 var len: i64 = gs_parse_hex(st.pkt, endp[0] + 1, n, endp, err)
710 if err[0] == 1 { return gs_send_str(st, "E01" as *u8) }
711 if len > GDB_PACKETSIZE - 1 { len = GDB_PACKETSIZE - 1 }
712 if off >= st.xml_n { return gs_send_str(st, "l" as *u8) }
713 var rem: i64 = st.xml_n - off
714 var o: i64 = 0
715 if rem <= len { st.tx[0] = 108 as u8 } else { st.tx[0] = 109 as u8; rem = len } // 'l' final / 'm' more
716 o = 1
717 var i: i64 = 0
718 while i < rem { st.tx[o] = st.xml[off + i]; o = o + 1; i = i + 1 }
719 return gs_send(st, st.tx, o)
720}
721
722// ---- the session: one debugger, packets until detach / kill / disconnect. Returns 0 (detached or
723// disconnected: guest may continue), 1 (killed).
724func gs_session(st: *NxGdbStub) -> i64 {
725 let lenp: *i64 = sys_mmap(8) as *i64
726 var go: i64 = 1
727 while go == 1 {
728 let n: i64 = gs_recv(st, lenp)
729 if n < 0 { go = 0; continue }
730 let c: i64 = st.pkt[0] as i64
731 var rc: i64 = 0
732 var handled: i64 = 0
733 if n == 0 { rc = gs_send_empty(st); handled = 1 }
734 if handled == 0 { if c == 63 { rc = gs_reply_trap(st); handled = 1 } } // '?'
735 if handled == 0 { if c == 103 { rc = gs_do_g(st); handled = 1 } } // 'g'
736 if handled == 0 { if c == 71 { rc = gs_do_G(st, n); handled = 1 } } // 'G'
737 if handled == 0 { if c == 112 { rc = gs_do_p(st, n); handled = 1 } } // 'p'
738 if handled == 0 { if c == 80 { rc = gs_do_P(st, n); handled = 1 } } // 'P'
739 if handled == 0 { if c == 109 { rc = gs_do_m(st, n); handled = 1 } } // 'm'
740 if handled == 0 { if c == 77 { rc = gs_do_M(st, n); handled = 1 } } // 'M'
741 if handled == 0 { if c == 90 { rc = gs_do_Z(st, n, 1); handled = 1 } } // 'Z'
742 if handled == 0 { if c == 122 { rc = gs_do_Z(st, n, 0); handled = 1 } } // 'z'
743 if handled == 0 { if c == 115 { // 's' [addr]
744 if n > 1 { let endp: *i64 = sys_mmap(8) as *i64; let err: *i64 = sys_mmap(8) as *i64; err[0] = 0
745 let a: i64 = gs_parse_hex(st.pkt, 1, n, endp, err); if err[0] == 0 { st.sim.pc = a } }
746 rc = gs_resume(st, 1); handled = 1 } }
747 if handled == 0 { if c == 99 { // 'c' [addr]
748 if n > 1 { let endp: *i64 = sys_mmap(8) as *i64; let err: *i64 = sys_mmap(8) as *i64; err[0] = 0
749 let a: i64 = gs_parse_hex(st.pkt, 1, n, endp, err); if err[0] == 0 { st.sim.pc = a } }
750 rc = gs_resume(st, 0); handled = 1 } }
751 if handled == 0 { if c == 68 { rc = gs_send_str(st, "OK" as *u8); st.detached = 1; go = 0; handled = 1 } } // 'D'
752 if handled == 0 { if c == 107 { st.killed = 1; go = 0; handled = 1 } } // 'k' (no reply by protocol)
753 if handled == 0 { if c == 72 { rc = gs_send_str(st, "OK" as *u8); handled = 1 } } // 'H' thread ops
754 if handled == 0 { if c == 84 { rc = gs_send_str(st, "OK" as *u8); handled = 1 } } // 'T' thread alive
755 if handled == 0 { if gs_eq(st.pkt, n, "qC" as *u8) == 1 { rc = gs_send_str(st, "QC1" as *u8); handled = 1 } }
756 if handled == 0 { if gs_eq(st.pkt, n, "qAttached" as *u8) == 1 { rc = gs_send_str(st, "1" as *u8); handled = 1 } }
757 if handled == 0 { if gs_eq(st.pkt, n, "qfThreadInfo" as *u8) == 1 { rc = gs_send_str(st, "m1" as *u8); handled = 1 } }
758 if handled == 0 { if gs_eq(st.pkt, n, "qsThreadInfo" as *u8) == 1 { rc = gs_send_str(st, "l" as *u8); handled = 1 } }
759 if handled == 0 { if gs_starts(st.pkt, n, "qSupported" as *u8) == 1 { rc = gs_do_qSupported(st); handled = 1 } }
760 if handled == 0 { if gs_starts(st.pkt, n, "qXfer:" as *u8) == 1 { rc = gs_do_qXfer(st, n); handled = 1 } }
761 if handled == 0 { if gs_eq(st.pkt, n, "QStartNoAckMode" as *u8) == 1 { rc = gs_send_str(st, "OK" as *u8); st.noack = 1; handled = 1 } }
762 if handled == 0 { if gs_starts(st.pkt, n, "vKill" as *u8) == 1 { rc = gs_send_str(st, "OK" as *u8); st.killed = 1; go = 0; handled = 1 } }
763 if handled == 0 { rc = gs_send_empty(st) } // unsupported -> empty (gdb falls back)
764 if rc < 0 { go = 0 }
765 if st.eof == 1 { go = 0 }
766 }
767 if st.killed == 1 { return 1 }
768 return 0
769}
770
771// ---- gdbstub_serve: the watch symbol. Listen on loopback, announce the port (stdout + ready file),
772// serve one session, return 0 on detach/disconnect (guest may run on), 1 on kill, <0 if listen failed.
773func gdbstub_serve(sim: *NxRv64imSim, port: i64, readyfile: *u8) -> i64 {
774 let st: *NxGdbStub = gs_new(sim)
775 let sa: *u8 = sys_mmap(GDB_SOCKADDR_LEN)
776 nx_http_server_addr_loopback(sa, port)
777 let verdict: *i64 = sys_mmap(8) as *i64
778 let lfd: i64 = nx_http_server_listen(sa, GDB_LISTEN_BACKLOG, verdict)
779 if lfd < 0 { gs_puts("GDBSTUB listen-failed verdict=" as *u8); gs_putn(verdict[0]); gs_puts("\n" as *u8); return 0 - 1 }
780 st.lfd = lfd
781 // read back the bound port (port 0 = kernel-chosen) -- the artifact says what we listen on
782 let ga: *u8 = sys_mmap(GDB_SOCKADDR_LEN)
783 let gl: *i64 = sys_mmap(8) as *i64
784 gl[0] = GDB_SOCKADDR_LEN
785 __syscall(GDB_SYS_GETSOCKNAME_RV64, lfd, ga, gl, 0, 0, 0)
786 st.port = ((ga[2] as i64) << 8) | (ga[3] as i64)
787 gs_puts("GDBSTUB listening addr=127.0.0.1 port=" as *u8); gs_putn(st.port)
788 gs_puts(" regs=" as *u8); gs_putn(GDB_N_REGS); gs_puts(" packetsize=" as *u8); gs_putn(GDB_PACKETSIZE)
789 gs_puts(" target_xml_bytes=" as *u8); gs_putn(st.xml_n); gs_puts("\n" as *u8)
790 if readyfile != (0 as *u8) {
791 let rf: i64 = sys_openat_wr(readyfile, 420)
792 if rf >= 0 {
793 let line: *u8 = sys_mmap(64)
794 var o: i64 = gs_cat(line, 0, "port=" as *u8)
795 var v: i64 = st.port
796 let t: *u8 = sys_mmap(16)
797 var k: i64 = 0
798 if v == 0 { t[0] = 48 as u8; k = 1 }
799 while v > 0 { t[k] = (48 + (v % 10)) as u8; v = v / 10; k = k + 1 }
800 while k > 0 { k = k - 1; line[o] = t[k]; o = o + 1 }
801 line[o] = 10 as u8; o = o + 1
802 sys_write(rf, line, o)
803 sys_close(rf)
804 }
805 }
806 let cfd: i64 = sys_accept(lfd)
807 if cfd < 0 { gs_puts("GDBSTUB accept-failed\n" as *u8); return 0 - 1 }
808 st.cfd = cfd
809 gs_puts("GDBSTUB debugger-attached\n" as *u8)
810 let rc: i64 = gs_session(st)
811 sys_close(cfd)
812 sys_close(lfd)
813 gs_puts("GDBSTUB session-end detached=" as *u8); gs_putn(st.detached)
814 gs_puts(" killed=" as *u8); gs_putn(st.killed)
815 gs_puts(" eof=" as *u8); gs_putn(st.eof)
816 gs_puts(" pkts_rx=" as *u8); gs_putn(st.pkts_rx)
817 gs_puts(" pkts_tx=" as *u8); gs_putn(st.pkts_tx)
818 gs_puts(" naks_sent_by_peer=" as *u8); gs_putn(st.naks)
819 gs_puts(" bad_checksum_frames=" as *u8); gs_putn(st.bad_cksum)
820 gs_puts(" breakpoints_live=" as *u8); gs_putn(st.n_bp)
821 gs_puts(" guest_pc=" as *u8); gs_putn(sim.pc)
822 gs_puts(" guest_steps=" as *u8); gs_putn(sim.steps)
823 gs_puts("\n" as *u8)
824 return rc
825}
826
827func main(argc: i64, argv: *i64) -> i64 {
828 if argc < 2 {
829 gs_puts("usage: nx_gdbstub <image.bin> [port|0] [readyfile]\n" as *u8)
830 return GDB_EXIT_USAGE
831 }
832 let binp: *u8 = argv[1] as *u8
833 var port: i64 = 0
834 if argc >= 3 {
835 let ps: *u8 = argv[2] as *u8
836 var i: i64 = 0
837 while ps[i] != (0 as u8) { port = port * 10 + ((ps[i] as i64) - 48); i = i + 1 }
838 }
839 var readyfile: *u8 = 0 as *u8
840 if argc >= 4 { readyfile = argv[3] as *u8 }
841 let lenp: *i64 = sys_mmap(16) as *i64
842 let img: *u8 = sys_read_file(binp, lenp)
843 let ilen: i64 = lenp[0]
844 if ilen <= 0 { gs_puts("GDBSTUB image-missing " as *u8); gs_puts(binp); gs_puts("\n" as *u8); return GDB_EXIT_NOIMAGE }
845 // UART capture bound = the guest's RAM size (the UART model stops capturing at tx_cap and reports the
846 // count it kept): a transcript longer than the program's whole address space is a runaway, not a
847 // transcript, and the boot rulers' golden is 85 bytes. Derived from the machine, not picked.
848 let tx: *u8 = sys_mmap(BOOTCAP_MEM_SIZE)
849 let sim: *NxRv64imSim = bootcap_machine(img, ilen, tx, BOOTCAP_MEM_SIZE)
850 gs_puts("GDBSTUB image=" as *u8); gs_puts(binp); gs_puts(" bytes=" as *u8); gs_putn(ilen)
851 gs_puts(" mem_base=" as *u8); gs_putn(sim.mem_base); gs_puts(" mem_size=" as *u8); gs_putn(sim.mem_size)
852 gs_puts(" guest held at reset pc=" as *u8); gs_putn(sim.pc); gs_puts("\n" as *u8)
853 let rc: i64 = gdbstub_serve(sim, port, readyfile)
854 if rc < 0 { return GDB_EXIT_LISTEN }
855 if rc == 1 {
856 gs_puts("GDBSTUB guest-killed-by-debugger steps=" as *u8); gs_putn(sim.steps); gs_puts("\n" as *u8)
857 return GDB_EXIT_KILLED
858 }
859 // detached or disconnected: THE GUEST KEEPS RUNNING -- to completion, witnessed here.
860 if sim.halted == 0 { nx_rv64im_sim_run(sim, BOOTCAP_MAX_STEPS) }
861 let cnt: i64 = nx_uart_tx_count(sim.uart)
862 gs_puts("GDBSTUB after-detach halted=" as *u8); gs_putn(sim.halted)
863 gs_puts(" code=" as *u8); gs_putn(sim.halt_code)
864 gs_puts(" steps=" as *u8); gs_putn(sim.steps)
865 gs_puts(" transcript_bytes=" as *u8); gs_putn(cnt)
866 gs_puts(" transcript=[" as *u8); sys_write(1, tx, cnt); gs_puts("]\n" as *u8)
867 return GDB_EXIT_OK
868}