nx_dbg_step.nx
buildroot/runtime/nx_dbg_step.nx
about
nx_dbg_step.nx -- THE STEPPING DEBUGGER (/compare/lang LN12, watch symbol dbg_step_over).
THE RUNG (lang.plan): "Stepping debugger -- the doctor lane's DT1: break and step on our own
binaries over the line table we emit (one contract, two pages)". LB5 shipped the line table
(lm_stmt_stamp, statement granularity, readelf-validated). This is the debugger ON it.
WHAT WAS REUSED, NOT REWRITTEN (nx_spendgate 2026-08-25 named all three):
nx_addr2line_lib.nx -- the sovereign DWARF v5 line-program primitives (uleb/sleb/u32/u64le,
nx_a2l_prog_off, and nx_a2l_func_at which already reads .debug_info
subprograms). Every ULEB/SLEB byte in this file is decoded by THAT
library's functions; there is still exactly one DWARF integer decoder.
nx_bootcap.nx -- bootcap_machine: the SAME device set the boot rulers measure.
rv64im_min_decoder.nx-- nx_rv64im_decode_kind + field extractors, for call/return classification.
nx_lineconf_lib.nx -- the one line-anchored conf reader.
nx_elf_read.nx -- ELF64 section walk.
WHAT IS NEW HERE, AND WHY THE INCUMBENT COULD NOT DO IT:
nx_a2l_run answers ONE question -- "which line is at address X" -- and to do it cheaply it keeps
only the best row and STOPS AT THE FIRST end_sequence. A debugger needs the WHOLE table and it
needs it BOTH WAYS:
* file:line -> address (setting a breakpoint) <- the direction the incumbent cannot answer
* address -> row index (where am I)
* row i -> row i+1 (where does this statement END) <- the step primitive
So this file runs the line program once into a two-pass-sized ROW TABLE, across ALL sequences.
It also decodes THREE opcode classes nx_a2l_run refuses: SPECIAL opcodes (the compact bias form),
DW_LNS_const_add_pc and DW_LNS_fixed_advance_pc, plus any other standard opcode via the header's
own standard_opcode_lengths table. That is not scope creep: it is the difference between a table
that works on our emitter's output and one that works on a line table from any DWARF v5 producer.
Anything still undecodable sets decode_complete=0 and then RESOLUTION REFUSES WITH "UNPROVEN"
RATHER THAN "NO SUCH LINE" -- a partial table that answers "absent" is a confident false negative,
which is the one failure mode a breakpoint resolver must never have.
STEPPING SEMANTICS (the actual rung). A statement step is NOT an instruction step:
dbg_step_into -- single-step until the covering line-table ROW CHANGES. Entering a callee is a
row change, so this stops on the callee's first statement.
dbg_step_over -- the same, except while the call DEPTH is above the starting depth nothing is
tested: the whole callee runs and the stop lands on the statement AFTER the
call, in the caller. A `ret` past the starting frame is depth < 0, which is
tested (that is gdb's behaviour at the end of a function, deliberately kept).
dependencies 6 imports · 0 importers
imports: nx_syscalls.nxnx_lineconf_lib.nxnx_addr2line_lib.nxnx_elf_read.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
structs
| 278 | struct DsLineTable |
| 728 | struct DsDbg |
consts
| 106 | const DS_DW_VERSION_5: i64 = 5 |
| 107 | const DS_LNCT_path: i64 = 0x01 |
| 108 | const DS_LNCT_directory_index: i64 = 0x02 |
| 109 | const DS_FORM_string: i64 = 0x08 |
| 110 | const DS_FORM_udata: i64 = 0x0f |
| 111 | const DS_LNS_copy: i64 = 0x01 |
| 112 | const DS_LNS_advance_pc: i64 = 0x02 |
| 113 | const DS_LNS_advance_line: i64 = 0x03 |
| 114 | const DS_LNS_set_file: i64 = 0x04 |
| 115 | const DS_LNS_const_add_pc: i64 = 0x08 |
| 116 | const DS_LNS_fixed_advance_pc: i64 = 0x09 |
| 117 | const DS_LNE_end_sequence: i64 = 0x01 |
| 118 | const DS_LNE_set_address: i64 = 0x02 |
| 121 | const DS_OFF_UNIT_LEN: i64 = 0 |
| 122 | const DS_OFF_VERSION: i64 = 4 |
| 123 | const DS_OFF_ADDR_SIZE: i64 = 6 |
| 124 | const DS_OFF_SEG_SEL: i64 = 7 |
| 125 | const DS_OFF_HEADER_LEN: i64 = 8 |
| 126 | const DS_OFF_PROLOGUE: i64 = 12 // header_length is measured from HERE |
| 127 | const DS_U32_BYTES: i64 = 4 |
| 128 | const DS_UNIT_LEN_MIN: i64 = 4 |
| 129 | const DS_HDR_MIN_BYTES: i64 = 12 |
| 130 | const DS_BYTE_MOD: i64 = 256 |
| 131 | const DS_BYTE_SIGN: i64 = 128 |
| 132 | const DS_BYTE_MASK: i64 = 255 |
| 133 | const DS_SPECIAL_MAX: i64 = 255 |
| 134 | const DS_PTR_BYTES: i64 = 8 |
| 135 | const DS_SET_ADDR_BYTES: i64 = 8 |
| 136 | const DS_ASCII_ZERO: i64 = 48 |
| 137 | const DS_ASCII_NINE: i64 = 57 |
| 138 | const DS_ASCII_a: i64 = 97 |
| 139 | const DS_ASCII_f: i64 = 102 |
| 140 | const DS_ASCII_A: i64 = 65 |
| 141 | const DS_ASCII_F: i64 = 70 |
| 142 | const DS_ASCII_SLASH: i64 = 47 |
| 143 | const DS_DECIMAL: i64 = 10 |
| 144 | const DS_HEXBASE: i64 = 16 |
| 147 | const DS_INST_BYTES: i64 = 4 |
| 148 | const DS_RVC_BYTES: i64 = 2 |
| 149 | const DS_ILEN32_MASK: i64 = 3 |
| 150 | const DS_ILEN32_VAL: i64 = 3 |
| 151 | const DS_REG_RA: i64 = 1 // x1, the primary link register |
| 152 | const DS_REG_T0: i64 = 5 // x5, the ALTERNATE link register (RISC-V spec) |
| 153 | const DS_RVC_OP_C2: i64 = 2 |
| 154 | const DS_RVC_F3_JALR: i64 = 4 |
| 155 | const DS_RVC_RS2_SHIFT: i64 = 2 |
| 156 | const DS_RVC_RD_SHIFT: i64 = 7 |
| 157 | const DS_RVC_F3_SHIFT: i64 = 13 |
| 158 | const DS_RVC_BIT12_SHIFT: i64 = 12 |
| 159 | const DS_REG_MASK: i64 = 31 |
| 160 | const DS_F3_MASK: i64 = 7 |
| 163 | const DS_STOP_BREAKPOINT: i64 = 1 |
| 164 | const DS_STOP_STEP: i64 = 2 |
| 165 | const DS_STOP_HALT: i64 = 3 |
| 166 | const DS_STOP_BUDGET: i64 = 4 |
| 168 | const DS_EXIT_OK: i64 = 0 |
| 169 | const DS_EXIT_USAGE: i64 = 2 |
| 170 | const DS_EXIT_NOIMAGE: i64 = 3 |
| 171 | const DS_EXIT_NOLINES: i64 = 4 |
| 172 | const DS_EXIT_REFUSED: i64 = 5 |
| 173 | const DS_EXIT_BUDGET: i64 = 6 |
| 180 | const DS_CONF_PATH: *u8 = "knowledge/dbg_step.conf" |
| 181 | const DS_CONF_KEY_BUDGET: *u8 = "max-steps-per-resume" |
| 183 | const DS_NAME_CAP_PER_FILE: i64 = 1024 // one path; the arena is sized nfiles * this, then filled exactly |
| 184 | const DS_FUNCNAME_CAP: i64 = 512 |
| 185 | const DS_SCRATCH: i64 = 64 |
| 186 | const DS_PATHBUF: i64 = 1024 |
| 299 | const DS_LT_BYTES: i64 = 256 // 19 fields * 8 = 152; sized like NX_RV64IM_SIM_BYTES, room to grow |
| 653 | const DS_RESOLVE_ABSENT: i64 = 0 - 1 |
| 654 | const DS_RESOLVE_UNPROVEN: i64 = 0 - 2 |
| 745 | const DS_DBG_BYTES: i64 = 256 |
| 819 | const DS_MODE_CONTINUE: i64 = 0 |
| 820 | const DS_MODE_INTO: i64 = 1 |
| 821 | const DS_MODE_OVER: i64 = 2 |
functions
| 189 | func ds_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } called by 11: ds_numds_report_tableds_dump_rowsds_report_stopdbg_backtraceds_load_table_raw+5 calls 1: sys_write |
| 190 | func ds_num(v: i64) -> i64 |
| 205 | func ds_hexnib(n: i64) -> i64 { if n < DS_DECIMAL { return DS_ASCII_ZERO + n } return (DS_ASCII_a - DS_DECIMAL) + n } called by 1: ds_hex |
| 206 | func ds_hex(v: i64) -> i64 called by 5: ds_dump_rowsds_report_stopdbg_backtraceds_do_resolvemain calls 4: sys_mmapds_hexnibsys_writesys_munmap |
| 222 | func ds_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } |
| 223 | func ds_streq(a: *u8, b: *u8) -> i64 |
| 234 | func ds_basename(p: *u8) -> *u8 called by 1: ds_addr_for_line |
| 240 | func ds_atoi(s: *u8) -> i64 |
| 255 | func ds_atox(s: *u8) -> i64 called by 1: main |
| 301 | func ds_u16(b: *u8, o: i64) -> i64 { return (b[o] as i64) | ((b[o + 1] as i64) << 8) } |
| 302 | func ds_sbyte(b: *u8, o: i64) -> i64 called by 1: ds_parse_prologue |
| 311 | func ds_parse_prologue(lt: *DsLineTable, buf: *u8, len: i64) -> i64 |
| 472 | func ds_file_name(lt: *DsLineTable, idx: i64) -> *u8 |
| 481 | func ds_run_program(lt: *DsLineTable, buf: *u8, len: i64, measure_only: i64) -> i64 |
| 605 | func ds_table_build(buf: *u8, len: i64) -> *DsLineTable |
| 625 | func ds_row_at(lt: *DsLineTable, target: i64) -> i64 |
| 655 | func ds_addr_for_line(lt: *DsLineTable, file: *u8, line: i64) -> i64 |
| 681 | func ds_is_link_reg(r: i64) -> i64 called by 1: ds_ras_delta |
| 687 | func ds_ras_delta(inst: i64) -> i64 |
| 749 | func ds_code_sum(dbg: *DsDbg) -> i64 |
| 760 | func ds_bp_ok(dbg: *DsDbg, addr: i64) -> i64 |
| 765 | func ds_bp_test(dbg: *DsDbg, addr: i64) -> i64 |
| 773 | func ds_bp_set(dbg: *DsDbg, addr: i64) -> i64 |
| 784 | func ds_fetch(dbg: *DsDbg, pc: i64) -> i64 called by 1: ds_resume |
| 795 | func dbg_attach(sim: *NxRv64imSim, lt: *DsLineTable, img_len: i64, budget: i64, budget_src: *u8) -> *DsDbg |
| 822 | func ds_resume(dbg: *DsDbg, mode: i64) -> i64 called by 3: dbg_continuedbg_step_intodbg_step_over calls 4: ds_row_atds_fetchds_ras_deltads_bp_test |
| 876 | func dbg_continue(dbg: *DsDbg) -> i64 { return ds_resume(dbg, DS_MODE_CONTINUE) } |
| 877 | func dbg_step_into(dbg: *DsDbg) -> i64 { return ds_resume(dbg, DS_MODE_INTO) } |
| 881 | func dbg_step_over(dbg: *DsDbg) -> i64 { return ds_resume(dbg, DS_MODE_OVER) } |
| 886 | func dbg_detach(dbg: *DsDbg) -> i64 called by 1: main |
| 894 | func ds_func_at(elf: *u8, elen: i64, addr: i64, out: *u8) -> i64 |
| 910 | func ds_find_section(buf: *u8, len: i64, want: *u8, outbox: *i64) -> i64 called by 2: ds_func_atds_load_table_elf calls 4: nx_elf_parse_headernx_elf_read_shdrnx_elf_strtab_atds_streq |
| 932 | func ds_report_table(lt: *DsLineTable) -> i64 |
| 946 | func ds_dump_rows(lt: *DsLineTable) -> i64 |
| 960 | func ds_report_stop(dbg: *DsDbg, what: *u8) -> i64 |
| 983 | func dbg_backtrace(dbg: *DsDbg) -> i64 |
| 1005 | func ds_load_table_raw(path: *u8) -> *DsLineTable |
| 1019 | func ds_load_table_elf(path: *u8) -> *DsLineTable |
| 1040 | func ds_do_resolve(lt: *DsLineTable, file: *u8, lines: *u8) -> i64 |
| 1069 | func ds_selftest() -> i64 |
| 1077 | func ds_usage() -> i64 |
| 1090 | func main(argc: i64, argv: *i64) -> i64 |