code wiki / (root) / nx_gdbstub.nx

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}