code wiki / (root) / nx_dwarf_cfi.nx

nx_dwarf_cfi.nx source

↩ module page · 277 lines · 9595 B

1// nx_dwarf_cfi.nx -- DWARF .debug_frame / .eh_frame builder. 2// 3// Encodes Call Frame Information used by stack unwinders (debuggers, 4// exception handlers, profilers, perf). Today our riscv.nx emits 5// .cfi_* DIRECTIVES for the gcc-as path; this module produces the 6// BINARY section directly so the off-C path doesn't need gcc-as. 7// 8// CFI section structure (DWARF v5 §6.4): 9// 10// Common Information Entry (CIE) -- shared header for many FDEs: 11// length u32 bytes after this field 12// CIE_id u32 = 0xFFFFFFFF for .debug_frame, 0 for .eh_frame 13// version u8 = 1 14// augstr NUL-term ASCII augmentation 15// code_align uleb128 multiplier for advance_loc 16// data_align sleb128 multiplier for cfa_offset 17// ra_reg uleb128 return-address register (typically 1=ra) 18// init_insns ... DW_CFA_* opcodes for default state 19// 20// Frame Description Entry (FDE) per function: 21// length u32 22// CIE_ptr u32 offset back to the CIE (.debug_frame) / 23// relative offset (.eh_frame) 24// init_loc u64 function start address (RV64) 25// range u64 function size 26// insns ... DW_CFA_* opcodes describing per-PC state 27// 28// What we ship today (v0.0.1): 29// * CIE writer (begin/end + opcode emit) 30// * FDE writer (begin/end + opcode emit) 31// * DW_CFA_* opcode constants 32// * advance_loc / def_cfa_offset / offset(reg, off) helpers 33// 34// Pairs with riscv.nx's .cfi directive emission: same logical 35// information, different encoding (binary vs assembler-text). 36// When the off-C path is the only path, riscv.nx will swap to 37// emitting straight to nx_dwarf_cfi instead of textual directives. 38 39// nx_safety_envelope: 40// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 41// sil_target: SIL1 42// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 43// verdict: NOT_YET_EVALUATED 44 45import "syscalls.nx" 46 47// ---- DW_CFA_* opcodes (DWARF v5 §6.4.2) -------------------------- 48 49const NX_DW_CFA_advance_loc: i64 = 0x40 // | delta (low 6 bits) 50const NX_DW_CFA_offset: i64 = 0x80 // | reg (low 6 bits) 51const NX_DW_CFA_restore: i64 = 0xC0 // | reg 52const NX_DW_CFA_nop: i64 = 0x00 53const NX_DW_CFA_set_loc: i64 = 0x01 54const NX_DW_CFA_advance_loc1: i64 = 0x02 55const NX_DW_CFA_advance_loc2: i64 = 0x03 56const NX_DW_CFA_advance_loc4: i64 = 0x04 57const NX_DW_CFA_offset_extended: i64 = 0x05 58const NX_DW_CFA_restore_extended: i64 = 0x06 59const NX_DW_CFA_undefined: i64 = 0x07 60const NX_DW_CFA_same_value: i64 = 0x08 61const NX_DW_CFA_register: i64 = 0x09 62const NX_DW_CFA_remember_state: i64 = 0x0A 63const NX_DW_CFA_restore_state: i64 = 0x0B 64const NX_DW_CFA_def_cfa: i64 = 0x0C 65const NX_DW_CFA_def_cfa_register: i64 = 0x0D 66const NX_DW_CFA_def_cfa_offset: i64 = 0x0E 67 68// ---- builder struct ---------------------------------------------- 69 70struct NxDwCfi { 71 buf: *u8, 72 cap: i64, 73 used: i64, 74 cie_off: i64, // offset of the start of the open CIE/FDE 75 open_kind: i64, // 0 none, 1 CIE, 2 FDE 76} 77 78const NX_DW_CFI_BYTES: i64 = 40 79 80func nx_dwcfi_new(cap: i64) -> *NxDwCfi { 81 let raw: *u8 = sys_mmap(NX_DW_CFI_BYTES) 82 let c: *NxDwCfi = raw as *NxDwCfi 83 c.buf = sys_mmap(cap) 84 c.cap = cap 85 c.used = 0 86 c.cie_off = 0 87 c.open_kind = 0 88 return c 89} 90 91// ---- low-level write helpers ------------------------------------- 92 93func nx_dwcfi_byte(c: *NxDwCfi, v: i64) -> i64 { 94 if c.used >= c.cap { return -1 } 95 c.buf[c.used] = v & 0xFF 96 c.used = c.used + 1 97 return 0 98} 99 100func nx_dwcfi_u32(c: *NxDwCfi, v: i64) -> i64 { 101 nx_dwcfi_byte(c, v & 0xFF) 102 nx_dwcfi_byte(c, (v >> 8) & 0xFF) 103 nx_dwcfi_byte(c, (v >> 16) & 0xFF) 104 nx_dwcfi_byte(c, (v >> 24) & 0xFF) 105 return 0 106} 107 108func nx_dwcfi_u64(c: *NxDwCfi, v: i64) -> i64 { 109 var i: i64 = 0 110 while i < 8 { 111 nx_dwcfi_byte(c, (v >> (i * 8)) & 0xFF) 112 i = i + 1 113 } 114 return 0 115} 116 117func nx_dwcfi_uleb(c: *NxDwCfi, v: i64) -> i64 { 118 var x: i64 = v 119 var go: i64 = 1 120 while go == 1 { 121 var b: i64 = x & 0x7F 122 x = x >> 7 123 if x != 0 { b = b | 0x80 } 124 if nx_dwcfi_byte(c, b) < 0 { return -1 } 125 if x == 0 { go = 0 } 126 } 127 return 0 128} 129 130func nx_dwcfi_sleb(c: *NxDwCfi, v: i64) -> i64 { 131 var x: i64 = v 132 var go: i64 = 1 133 while go == 1 { 134 var b: i64 = x & 0x7F 135 x = x >> 7 136 let sign_bit: i64 = b & 0x40 137 let done: i64 = ((x == 0) & (sign_bit == 0)) | ((x == -1) & (sign_bit != 0)) 138 if done == 0 { b = b | 0x80 } 139 if nx_dwcfi_byte(c, b) < 0 { return -1 } 140 if done != 0 { go = 0 } 141 } 142 return 0 143} 144 145// ---- CIE --------------------------------------------------------- 146 147// Begin a CIE. Caller writes initial DW_CFA_* opcodes via 148// nx_dwcfi_advance_loc / def_cfa_offset / offset(reg, off) etc., 149// then calls nx_dwcfi_end to patch the length field. 150func nx_dwcfi_cie_begin(c: *NxDwCfi, code_align: i64, data_align: i64, 151 ra_reg: i64) -> i64 { 152 c.cie_off = c.used 153 c.open_kind = 1 154 nx_dwcfi_u32(c, 0) // length placeholder 155 nx_dwcfi_u32(c, 0xFFFFFFFF) // CIE_id (.debug_frame convention) 156 nx_dwcfi_byte(c, 1) // version 157 nx_dwcfi_byte(c, 0) // augstr "" (empty) 158 nx_dwcfi_uleb(c, code_align) 159 nx_dwcfi_sleb(c, data_align) 160 nx_dwcfi_uleb(c, ra_reg) 161 return 0 162} 163 164// ---- FDE --------------------------------------------------------- 165 166// Begin an FDE for a function. cie_offset = byte offset of the 167// CIE this FDE inherits from. init_loc = function start address. 168// range = function byte count. 169func nx_dwcfi_fde_begin(c: *NxDwCfi, cie_offset: i64, 170 init_loc: i64, range: i64) -> i64 { 171 c.cie_off = c.used 172 c.open_kind = 2 173 nx_dwcfi_u32(c, 0) // length placeholder 174 nx_dwcfi_u32(c, cie_offset) 175 nx_dwcfi_u64(c, init_loc) 176 nx_dwcfi_u64(c, range) 177 return 0 178} 179 180// End the open CIE / FDE: patch the length field to whatever 181// follows the 4-byte length itself. 182func nx_dwcfi_end(c: *NxDwCfi) -> i64 { 183 if c.open_kind == 0 { return -1 } 184 let length: i64 = c.used - (c.cie_off + 4) 185 c.buf[c.cie_off + 0] = length & 0xFF 186 c.buf[c.cie_off + 1] = (length >> 8) & 0xFF 187 c.buf[c.cie_off + 2] = (length >> 16) & 0xFF 188 c.buf[c.cie_off + 3] = (length >> 24) & 0xFF 189 c.open_kind = 0 190 return 0 191} 192 193// ---- per-op helpers ---------------------------------------------- 194 195// DW_CFA_advance_loc with delta (in code units) <= 63 packed into 196// the opcode's low 6 bits. For larger deltas use advance_loc1/2/4. 197func nx_dwcfi_advance_loc(c: *NxDwCfi, delta: i64) -> i64 { 198 if delta <= 63 { 199 return nx_dwcfi_byte(c, NX_DW_CFA_advance_loc | (delta & 0x3F)) 200 } 201 if delta <= 0xFF { 202 nx_dwcfi_byte(c, NX_DW_CFA_advance_loc1) 203 return nx_dwcfi_byte(c, delta) 204 } 205 if delta <= 0xFFFF { 206 nx_dwcfi_byte(c, NX_DW_CFA_advance_loc2) 207 nx_dwcfi_byte(c, delta & 0xFF) 208 return nx_dwcfi_byte(c, (delta >> 8) & 0xFF) 209 } 210 nx_dwcfi_byte(c, NX_DW_CFA_advance_loc4) 211 return nx_dwcfi_u32(c, delta) 212} 213 214// DW_CFA_def_cfa_offset: redefine CFA's offset (CFA stays at 215// previous register, just bumps offset). 216func nx_dwcfi_def_cfa_offset(c: *NxDwCfi, off: i64) -> i64 { 217 nx_dwcfi_byte(c, NX_DW_CFA_def_cfa_offset) 218 return nx_dwcfi_uleb(c, off) 219} 220 221// DW_CFA_def_cfa: define CFA = (reg, offset). 222func nx_dwcfi_def_cfa(c: *NxDwCfi, reg: i64, off: i64) -> i64 { 223 nx_dwcfi_byte(c, NX_DW_CFA_def_cfa) 224 nx_dwcfi_uleb(c, reg) 225 return nx_dwcfi_uleb(c, off) 226} 227 228// DW_CFA_offset(reg, factored_offset): "reg was saved at CFA + N". 229// `off` is the FACTORED offset (caller multiplies by abs(data_align)). 230func nx_dwcfi_offset(c: *NxDwCfi, reg: i64, factored_off: i64) -> i64 { 231 if reg <= 63 { 232 nx_dwcfi_byte(c, NX_DW_CFA_offset | (reg & 0x3F)) 233 } else { 234 nx_dwcfi_byte(c, NX_DW_CFA_offset_extended) 235 nx_dwcfi_uleb(c, reg) 236 } 237 return nx_dwcfi_uleb(c, factored_off) 238} 239 240func nx_dwcfi_size(c: *NxDwCfi) -> i64 { return c.used } 241func nx_dwcfi_bytes(c: *NxDwCfi) -> *u8 { return c.buf } 242 243// ---- self-test --------------------------------------------------- 244 245func main() -> i64 { 246 let c: *NxDwCfi = nx_dwcfi_new(256) 247 248 // CIE: code_align=1, data_align=-4, ra_reg=1 (ra register). 249 let cie_off: i64 = c.used 250 nx_dwcfi_cie_begin(c, 1, -4, 1) 251 // Default state: CFA = sp (reg 2) + 0 252 nx_dwcfi_def_cfa(c, 2, 0) 253 nx_dwcfi_end(c) 254 255 // Verify length patched. 256 let cie_len: i64 = c.buf[cie_off] | (c.buf[cie_off+1] << 8) | (c.buf[cie_off+2] << 16) | (c.buf[cie_off+3] << 24) 257 if cie_len <= 0 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 258 if cie_len != c.used - cie_off - 4 { return __syscall(93, 11, 0, 0, 0, 0, 0) } 259 260 // FDE: function at 0x10000, 64 bytes. 261 let fde_off: i64 = c.used 262 nx_dwcfi_fde_begin(c, cie_off, 0x10000, 64) 263 // After 16 bytes, CFA offset becomes 32. 264 nx_dwcfi_advance_loc(c, 16) 265 nx_dwcfi_def_cfa_offset(c, 32) 266 // ra (reg 1) saved at CFA - 8 -> factored = 8 / 4 = 2 (data_align=-4) 267 nx_dwcfi_offset(c, 1, 2) 268 nx_dwcfi_end(c) 269 270 let fde_len: i64 = c.buf[fde_off] | (c.buf[fde_off+1] << 8) | (c.buf[fde_off+2] << 16) | (c.buf[fde_off+3] << 24) 271 if fde_len <= 0 { return __syscall(93, 20, 0, 0, 0, 0, 0) } 272 273 // Total size > 0. 274 if nx_dwcfi_size(c) <= 0 { return __syscall(93, 30, 0, 0, 0, 0, 0) } 275 276 return 0 277}