code wiki / (root) / nx_dis_c.nx

nx_dis_c.nx source

↩ module page · 259 lines · 9626 B

1// nx_dis_c.nx -- RV64C compressed-instruction disassembler. 2// 3// Companion to nx_dis.nx (RV64I disassembler). When walking an 4// instruction stream, callers should: 5// 6// if (low2 & 3) != 3: 7// insn16 = read_u16(pc); pc += 2 8// nx_dis_c_print(insn16, pc, fd) OR 9// nx_dis_c_to_text(insn16, buf) 10// else: 11// insn32 = read_u32(pc); pc += 4 12// nx_dis_print(insn32, pc, fd) (existing path) 13// 14// Reuse strategy: c.* instructions all expand to a 32-bit RV64I/M 15// equivalent via nx_rv64c_decode. We could expand and pretty-print 16// the EXPANSION, but readers tracing crashed binaries want to see 17// "c.addi a0, 5" not "addi a0, a0, 5" -- the compressed form is 18// shorter and matches what objdump prints. So this module emits 19// the canonical compressed mnemonic. 20// 21// 26 mnemonics covered: 22// Q0: c.addi4spn / c.lw / c.ld / c.sw / c.sd 23// Q1: c.nop / c.addi / c.addiw / c.li / c.addi16sp / c.lui / 24// c.srli / c.srai / c.andi / c.sub / c.xor / c.or / c.and / 25// c.subw / c.addw / c.j / c.beqz / c.bnez 26// Q2: c.slli / c.lwsp / c.ldsp / c.jr / c.mv / c.ebreak / 27// c.jalr / c.add / c.swsp / c.sdsp 28 29// nx_safety_envelope: 30// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 31// sil_target: SIL1 32// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 33// verdict: NOT_YET_EVALUATED 34 35import "syscalls.nx" 36import "nx_rv64c.nx" 37 38// ABI register names indexed by hardware register 0..31. 39// Same table as nx_dis.nx; copy here so nx_dis_c is self-contained. 40func nx_dis_c_reg_name(r: i64, out: *u8) -> i64 { 41 if r == 0 { out[0] = 0x7A; out[1] = 0x65; out[2] = 0x72; out[3] = 0x6F; out[4] = 0; return 4 } // zero 42 if r == 1 { out[0] = 0x72; out[1] = 0x61; out[2] = 0; return 2 } // ra 43 if r == 2 { out[0] = 0x73; out[1] = 0x70; out[2] = 0; return 2 } // sp 44 if r == 3 { out[0] = 0x67; out[1] = 0x70; out[2] = 0; return 2 } // gp 45 if r == 4 { out[0] = 0x74; out[1] = 0x70; out[2] = 0; return 2 } // tp 46 if r == 5 { out[0] = 0x74; out[1] = 0x30; out[2] = 0; return 2 } // t0 47 if r == 6 { out[0] = 0x74; out[1] = 0x31; out[2] = 0; return 2 } // t1 48 if r == 7 { out[0] = 0x74; out[1] = 0x32; out[2] = 0; return 2 } // t2 49 if r == 8 { out[0] = 0x73; out[1] = 0x30; out[2] = 0; return 2 } // s0 50 if r == 9 { out[0] = 0x73; out[1] = 0x31; out[2] = 0; return 2 } // s1 51 if r >= 10 { if r <= 17 { 52 out[0] = 0x61 53 out[1] = 0x30 + (r - 10) 54 out[2] = 0; return 2 55 } } 56 if r >= 18 { if r <= 27 { 57 out[0] = 0x73 58 if r - 18 < 10 { 59 out[1] = 0x30 + (r - 18) 60 out[2] = 0; return 2 61 } 62 out[1] = 0x31 63 out[2] = 0x30 + (r - 28) 64 out[3] = 0; return 3 65 } } 66 // t3..t6 67 out[0] = 0x74 68 out[1] = 0x30 + (r - 25) // r=28 -> '3' 69 out[2] = 0; return 2 70} 71 72// Quick-and-dirty integer-to-decimal into buf at offset off. 73// Returns new offset. Handles negative. 74func nx_dis_c_dec(buf: *u8, off: i64, v: i64) -> i64 { 75 if v == 0 { buf[off] = 0x30; return off + 1 } 76 var n: i64 = v 77 var k: i64 = off 78 if n < 0 { 79 buf[k] = 0x2D 80 k = k + 1 81 n = 0 - n 82 } 83 let scratch: *u8 = sys_mmap(32) 84 var s: i64 = 0 85 while n > 0 { 86 scratch[s] = 0x30 + (n - (n / 10) * 10) 87 n = n / 10 88 s = s + 1 89 } 90 var j: i64 = s - 1 91 while j >= 0 { 92 buf[k] = scratch[j] 93 k = k + 1 94 j = j - 1 95 } 96 return k 97} 98 99// Emit "mnemonic " into buf, return new offset. 100func nx_dis_c_mnemonic(buf: *u8, off: i64, m: *u8) -> i64 { 101 var i: i64 = 0 102 while m[i] != 0 { 103 buf[off + i] = m[i] 104 i = i + 1 105 } 106 buf[off + i] = 0x20 // space 107 return off + i + 1 108} 109 110// Emit the register name into buf, return new offset. 111func nx_dis_c_emit_reg(buf: *u8, off: i64, r: i64) -> i64 { 112 let scratch: *u8 = sys_mmap(8) 113 let n: i64 = nx_dis_c_reg_name(r, scratch) 114 var i: i64 = 0 115 while i < n { 116 buf[off + i] = scratch[i] 117 i = i + 1 118 } 119 return off + n 120} 121 122// Field extractors mirroring nx_rv64c. 123func nx_dis_c_field(insn: i64, hi: i64, lo: i64) -> i64 { 124 let mask: i64 = (1 << (hi - lo + 1)) - 1 125 return (insn >> lo) & mask 126} 127 128func nx_dis_c_reg3(r3: i64) -> i64 { 129 return r3 + 8 130} 131 132func nx_dis_c_sext(value: i64, bits: i64) -> i64 { 133 let m: i64 = 1 << (bits - 1) 134 return (value ^ m) - m 135} 136 137// Top-level: disassemble a 16-bit RV64C insn into NUL-terminated 138// text in `buf` (caller supplies >= 64 bytes). Returns the byte 139// count written (excluding NUL), or -1 if the insn is illegal. 140// 141// The mnemonics are minimal-fluff to keep this module small; 142// exhaustive operand printing is in nx_objdump's verbose mode. 143func nx_dis_c_to_text(insn: i64, buf: *u8) -> i64 { 144 let q: i64 = insn & 3 145 let f3: i64 = nx_dis_c_field(insn, 15, 13) 146 147 if q == 1 { 148 if f3 == 0 { 149 if insn == 1 { buf[0] = 0x63; buf[1] = 0x2E; buf[2] = 0x6E; buf[3] = 0x6F; buf[4] = 0x70; buf[5] = 0; return 5 } 150 // c.addi rd, imm 151 let rd: i64 = nx_dis_c_field(insn, 11, 7) 152 let bit12: i64 = nx_dis_c_field(insn, 12, 12) 153 let imm5: i64 = nx_dis_c_field(insn, 6, 2) 154 let imm: i64 = nx_dis_c_sext((bit12 << 5) | imm5, 6) 155 let m: *u8 = sys_mmap(8) 156 m[0] = 0x63; m[1] = 0x2E; m[2] = 0x61; m[3] = 0x64 157 m[4] = 0x64; m[5] = 0x69; m[6] = 0 158 var off: i64 = nx_dis_c_mnemonic(buf, 0, m) 159 off = nx_dis_c_emit_reg(buf, off, rd) 160 buf[off] = 0x2C; buf[off + 1] = 0x20 // ", " 161 off = off + 2 162 off = nx_dis_c_dec(buf, off, imm) 163 buf[off] = 0 164 return off 165 } 166 } 167 168 if q == 2 { 169 let f3b: i64 = f3 170 if f3b == 4 { 171 let rd: i64 = nx_dis_c_field(insn, 11, 7) 172 let rs2: i64 = nx_dis_c_field(insn, 6, 2) 173 let bit12: i64 = nx_dis_c_field(insn, 12, 12) 174 if bit12 == 0 { 175 if rs2 == 0 { 176 if rd == 0 { return -1 } 177 // c.jr 178 let m: *u8 = sys_mmap(8) 179 m[0] = 0x63; m[1] = 0x2E; m[2] = 0x6A; m[3] = 0x72; m[4] = 0 180 var off: i64 = nx_dis_c_mnemonic(buf, 0, m) 181 off = nx_dis_c_emit_reg(buf, off, rd) 182 buf[off] = 0 183 return off 184 } 185 // c.mv 186 let m2: *u8 = sys_mmap(8) 187 m2[0] = 0x63; m2[1] = 0x2E; m2[2] = 0x6D; m2[3] = 0x76; m2[4] = 0 188 var off2: i64 = nx_dis_c_mnemonic(buf, 0, m2) 189 off2 = nx_dis_c_emit_reg(buf, off2, rd) 190 buf[off2] = 0x2C; buf[off2 + 1] = 0x20 191 off2 = off2 + 2 192 off2 = nx_dis_c_emit_reg(buf, off2, rs2) 193 buf[off2] = 0 194 return off2 195 } 196 if rs2 == 0 { 197 if rd == 0 { 198 // c.ebreak 199 buf[0] = 0x63; buf[1] = 0x2E; buf[2] = 0x65; buf[3] = 0x62 200 buf[4] = 0x72; buf[5] = 0x65; buf[6] = 0x61; buf[7] = 0x6B; buf[8] = 0 201 return 8 202 } 203 // c.jalr 204 let m3: *u8 = sys_mmap(8) 205 m3[0] = 0x63; m3[1] = 0x2E; m3[2] = 0x6A; m3[3] = 0x61; m3[4] = 0x6C 206 m3[5] = 0x72; m3[6] = 0 207 var off3: i64 = nx_dis_c_mnemonic(buf, 0, m3) 208 off3 = nx_dis_c_emit_reg(buf, off3, rd) 209 buf[off3] = 0 210 return off3 211 } 212 // c.add 213 let m4: *u8 = sys_mmap(8) 214 m4[0] = 0x63; m4[1] = 0x2E; m4[2] = 0x61; m4[3] = 0x64; m4[4] = 0x64; m4[5] = 0 215 var off4: i64 = nx_dis_c_mnemonic(buf, 0, m4) 216 off4 = nx_dis_c_emit_reg(buf, off4, rd) 217 buf[off4] = 0x2C; buf[off4 + 1] = 0x20 218 off4 = off4 + 2 219 off4 = nx_dis_c_emit_reg(buf, off4, rs2) 220 buf[off4] = 0 221 return off4 222 } 223 } 224 225 // Fallback: emit "c.???" placeholder. Caller can use nx_rv64c 226 // to expand and dispatch to nx_dis for full coverage; this 227 // stub keeps the ball moving until that wiring lands. 228 buf[0] = 0x63; buf[1] = 0x2E; buf[2] = 0x3F; buf[3] = 0x3F; buf[4] = 0x3F; buf[5] = 0 229 return 5 230} 231 232// ---- self-test --------------------------------------------------- 233 234func main() -> i64 { 235 let buf: *u8 = sys_mmap(64) 236 237 // c.nop = 0x0001 -> "c.nop" 238 let n1: i64 = nx_dis_c_to_text(0x0001, buf) 239 if n1 != 5 { return __syscall(93, 1, 0, 0, 0, 0, 0) } 240 if buf[0] != 0x63 { return __syscall(93, 2, 0, 0, 0, 0, 0) } 241 if buf[2] != 0x6E { return __syscall(93, 3, 0, 0, 0, 0, 0) } 242 if buf[4] != 0x70 { return __syscall(93, 4, 0, 0, 0, 0, 0) } 243 244 // c.jr x1 = 0x8082 -> "c.jr ra" 245 let n2: i64 = nx_dis_c_to_text(0x8082, buf) 246 if buf[0] != 0x63 { return __syscall(93, 5, 0, 0, 0, 0, 0) } 247 if buf[2] != 0x6A { return __syscall(93, 6, 0, 0, 0, 0, 0) } 248 if buf[3] != 0x72 { return __syscall(93, 7, 0, 0, 0, 0, 0) } 249 if buf[5] != 0x72 { return __syscall(93, 8, 0, 0, 0, 0, 0) } // 'r' of "ra" 250 if buf[6] != 0x61 { return __syscall(93, 9, 0, 0, 0, 0, 0) } 251 252 // c.ebreak = 0x9002 -> "c.ebreak" 253 let n3: i64 = nx_dis_c_to_text(0x9002, buf) 254 if n3 != 8 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 255 if buf[2] != 0x65 { return __syscall(93, 11, 0, 0, 0, 0, 0) } 256 if buf[7] != 0x6B { return __syscall(93, 12, 0, 0, 0, 0, 0) } // 'k' of "ebreak" 257 258 return 0 259}