code wiki / (root) / nx_dbg_step_gate.nx

nx_dbg_step_gate.nx

buildroot/runtime/nx_dbg_step_gate.nx

34255 B648 linesdepth 6pulls 21 transitivereach 0 importersview sourcekind gate/proof
docsdependenciesstructsconstsfunctions

about

nx_dbg_step_gate.nx -- the VERDICT gate for LN12 (nx_dbg_step, watch symbol dbg_step_over). THE ACCEPT RULE (/compare/lang LN12): break and step on our own binaries over the line table we emit. So this gate does not mock anything: * it ASSEMBLES a real RV64 program containing a real linking CALL, * it EMITS that program's .debug_line with nx_dwarf_line.nx -- OUR OWN DWARF v5 emitter, the one LB5 shipped and GNU readelf validates -- so the table under test is the table we produce, * it runs the deployed nx_dbg_step.elf against the pair and compares against expectations that the debugger had no part in computing. THE SUBJECT IS NEVER ITS OWN ORACLE. Every expected value comes from somewhere else: breakpoint address = the gate's OWN fixture layout (it placed line 12 at offset 8; the debugger has to rediscover that through the line table, and the two must agree). callee / caller line = likewise the gate's layout. exit code and halt = a SECOND, DEBUGGER-FREE sim (bootcap_machine) run in this process. never-executed addr = not assumed -- the reference sim records every pc it fetches and the gate PROVES the neg-control address is absent from that set before using it. the call is a call = decoded arithmetically with rv64im_min_decoder (kind == JAL, rd == ra) BEFORE any stepping outcome is asserted, so a fixture that could not exercise step-over cannot score a pass. WHY THE DISCRIMINATING TOOTH IS THE ONE THAT MATTERS. A debugger that single-steps INSTRUCTIONS passes "the line changed" trivially. The only thing that separates a stepping debugger from an instruction stepper is that STEP-OVER MUST NOT REPORT THE CALLEE'S LINE while STEP-INTO MUST. Both are asserted here, on the same fixture, from the same starting pc -- two tests that each isolate one signal would not prove discrimination between them. NEVER-BRICK IS TESTED BEHAVIOURALLY, NOT BY GREPPING FOR A WORD. A pid-shaped argument must be REFUSED (a process id can never become a subject), and the source must contain no sys_ptrace call site. The behavioural half cannot be fooled by prose, which a source scan can be -- this file and the organ both discuss ptrace at length, and a detector that finds its own explanation of the bug it hunts is the trap this estate has already paid for twice. Fixtures live in /tmp/nx_dbg_step_gate/ (created at setup, so a crashed run cannot leave a fixture beside a production beat) and are ASSEMBLED AT RUNTIME, never checked in. license_tier: ORIGINAL

dependencies 6 imports · 0 importers

nx_syscalls.nx nx_gate_verdict.nx nx_gatekit_lib.nx nx_dwarf_line.nx nx_bootcap.nx rv64im_min_decoder.nx nx_dbg_step_gate.nx

imports: nx_syscalls.nxnx_gate_verdict.nxnx_gatekit_lib.nxnx_dwarf_line.nxnx_bootcap.nxrv64im_min_decoder.nx

imported by: nobody (leaf or entry point)

call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown

main gv_head gv_puts sys_write gv_ctr sys_mmap nxa_die sys_write ↻ sys_exit nxa_lock_take nxa_lock_addr sys_write ↻ nxa_lock_give nxa_lock_addr ↻ nxa_report_overrun sys_write ↻ nxa_dump_printable sys_write ↻ nxa_dump_sizes sys_write ↻ gk_mkdir sys_mkdir sys_mmap ↻ gk_exists sys_openat_rd sys_close gk_cat gk_len dg_p gv_puts ↻ gv_need gv_puts ↻ gv_verdict gv_note_bare_rate gv_bare_rate gv_at gv_obj_has_n gv_at ↻ gv_puts ↻ gv_num

structs

none

consts

44const DG_DIR: *u8 = "/tmp/nx_dbg_step_gate"
45const DG_IMG: *u8 = "/tmp/nx_dbg_step_gate/fixture.bin"
46const DG_LT: *u8 = "/tmp/nx_dbg_step_gate/fixture.debug_line"
47const DG_SRCNAME: *u8 = "dbgfix.nx"
48const DG_SRC: *u8 = "buildroot/runtime/nx_dbg_step.nx"
52const DG_RUN_MS: i64 = 60000
53const DG_POLL_MS: i64 = 25
54const DG_OUT_CAP: i64 = 1048576 // the gatekit capture bound; asserted not reached, never silent
55const DG_ARGV_MAX: i64 = 16
56const DG_MODE_644: i64 = 420
57const DG_ASCII_ZERO: i64 = 48
58const DG_ASCII_NINE: i64 = 57
59const DG_ASCII_a: i64 = 97
60const DG_ASCII_f: i64 = 102
61const DG_DECIMAL: i64 = 10
62const DG_HEXBASE: i64 = 16
63const DG_PTR: i64 = 8
64const DG_SCRATCH: i64 = 64
65const DG_BYTE_MASK: i64 = 255
70const DG_N_INSTR: i64 = 10
71const DG_OFF_L10: i64 = 0 // addi a0, x0, 7
72const DG_OFF_L11: i64 = 4 // addi a1, x0, 2
73const DG_OFF_CALL: i64 = 8 // jal ra, +24 <- THE CALL
74const DG_OFF_AFTER: i64 = 12 // addi a2, x0, 3 <- where a step-OVER must land
75const DG_OFF_SETRC: i64 = 16 // addi a0, x0, 5
76const DG_OFF_HALT: i64 = 20 // ebreak <- halt_code = a0
77const DG_OFF_NEVER: i64 = 24 // addi a3, x0, 9 <- NEVER executed (neg-control)
78const DG_OFF_NEVER2: i64 = 28 // ebreak <- NEVER executed
79const DG_OFF_CALLEE: i64 = 32 // addi a4, x0, 4 <- where a step-INTO must land
80const DG_OFF_CALLRET: i64 = 36 // jalr x0, 0(ra)
81const DG_IMG_BYTES: i64 = 40
83const DG_LINE_L10: i64 = 10
84const DG_LINE_L11: i64 = 11
85const DG_LINE_CALL: i64 = 12
86const DG_LINE_AFTER: i64 = 13
87const DG_LINE_SETRC: i64 = 14
88const DG_LINE_HALT: i64 = 15
89const DG_LINE_NEVER: i64 = 20
90const DG_LINE_NEVER2: i64 = 21
91const DG_LINE_CALLEE: i64 = 100
92const DG_LINE_CALLRET: i64 = 101
93const DG_LINE_BOGUS: i64 = 999 // present in no row: the resolver must refuse it BY NAME
94const DG_END_OFF: i64 = 40 // end_sequence address = one past the last instruction
99const DG_OPC_OP_IMM: i64 = 0x13
100const DG_OPC_JAL: i64 = 0x6F
101const DG_OPC_JALR: i64 = 0x67
102const DG_EBREAK: i64 = 0x00100073
103const DG_REG_RA: i64 = 1
104const DG_REG_A0: i64 = 10
105const DG_REG_A1: i64 = 11
106const DG_REG_A2: i64 = 12
107const DG_REG_A3: i64 = 13
108const DG_REG_A4: i64 = 14
109const DG_IMM12_MASK: i64 = 4095
110const DG_EXIT_RC: i64 = 5 // what the fixture puts in a0 before ebreak
111const DG_A0_INITIAL: i64 = 7 // deliberately different, so a wrong exit code cannot look right

functions

113func dg_addi(rd: i64, rs1: i64, imm: i64) -> i64
called by 1: main
116func dg_jal(rd: i64, off: i64) -> i64
called by 1: main
123func dg_jalr(rd: i64, rs1: i64, imm: i64) -> i64
called by 1: main
126func dg_put32(b: *u8, off: i64, w: i64) -> i64
called by 1: main
131func dg_get32(b: *u8, off: i64) -> i64
called by 1: main
139func dg_p(s: *u8) -> i64 { return gv_puts(s) }
called by 2: dg_reapmain calls 1: gv_puts
140func dg_n(v: i64) -> i64 { return gv_num(v) }
called by 1: main calls 1: gv_num
142func dg_write_bin(path: *u8, buf: *u8, n: i64) -> i64
152func dg_spawn(elf: *u8, words: *i64, nwords: i64, logpath: *u8) -> i64
171func dg_reap(pid: i64, budget_ms: i64) -> i64
188func dg_session(elf: *u8, words: *i64, nwords: i64, log: *u8, outbuf: *u8, outlen: *i64) -> i64
called by 1: main calls 3: dg_spawndg_reapgk_read
202func dg_find(buf: *u8, n: i64, from: i64, needle: *u8) -> i64
216func dg_dec_after(buf: *u8, n: i64, anchor: *u8, field: *u8, found: *i64) -> i64
called by 1: main calls 2: gk_lendg_find
243func dg_hex_after(buf: *u8, n: i64, anchor: *u8, field: *u8, found: *i64) -> i64
called by 1: main calls 2: gk_lendg_find
272func dg_has(buf: *u8, n: i64, needle: *u8) -> i64
called by 1: main calls 1: dg_find
278func dg_word(v: i64) -> *u8
called by 1: main calls 1: sys_mmap
291func main() -> i64