code wiki / (root) / nx_dis.nx

nx_dis.nx source

↩ module page · 480 lines · 16786 B

1// nx_dis.nx -- RV64I disassembler (machine bytes -> mnemonic). 2// 3// Inverse of nxasm: takes 4-byte RV64 instructions and prints the 4// canonical mnemonic + operand list. Foundation for sovereign 5// `objdump -d` (the disassembly view), the future nx_dbg.nx 6// stepping debugger, and Wheeler-DDC byte-level audit. 7// 8// Coverage (v0.0.1 -- the instruction set our codegen actually 9// emits): 10// 11// R-type: add, sub, and, or, xor, sll, srl, sra, slt, sltu 12// mul, mulh, div, divu, rem, remu (M extension) 13// I-type: addi, andi, ori, xori, slli, srli, srai, slti, sltiu 14// jalr, ld, lw, lh, lb, lwu, lhu, lbu, ebreak, ecall 15// S-type: sd, sw, sh, sb 16// B-type: beq, bne, blt, bge, bltu, bgeu 17// U-type: lui, auipc 18// J-type: jal 19// 20// Floating-point (F/D extension), atomic (A), compressed (C), 21// vector (V) deferred -- separate commits when nxc2 emits them. 22// 23// Pseudoinstruction recognition (addi rd,zero,N -> li, etc.) is 24// also deferred to nx_dis_pretty.nx. 25 26// nx_safety_envelope: 27// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 28// sil_target: SIL1 29// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 30// verdict: NOT_YET_EVALUATED 31 32import "syscalls.nx" 33 34// ---- decode helpers ------------------------------------------------ 35 36// Read a 4-byte little-endian instruction word. 37func nx_dis_word_at(buf: *u8, off: i64) -> i64 { 38 let b0: i64 = buf[off + 0] 39 let b1: i64 = buf[off + 1] 40 let b2: i64 = buf[off + 2] 41 let b3: i64 = buf[off + 3] 42 return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24) 43} 44 45func nx_dis_opcode(w: i64) -> i64 { return w & 0x7F } 46func nx_dis_rd(w: i64) -> i64 { return (w >> 7) & 0x1F } 47func nx_dis_funct3(w: i64) -> i64 { return (w >> 12) & 0x7 } 48func nx_dis_rs1(w: i64) -> i64 { return (w >> 15) & 0x1F } 49func nx_dis_rs2(w: i64) -> i64 { return (w >> 20) & 0x1F } 50func nx_dis_funct7(w: i64) -> i64 { return (w >> 25) & 0x7F } 51 52// I-type immediate (signed 12-bit). 53func nx_dis_imm_i(w: i64) -> i64 { 54 let imm: i64 = (w >> 20) & 0xFFF 55 if (imm & 0x800) != 0 { return imm | (~0xFFF) } 56 return imm 57} 58 59// S-type immediate (signed 12-bit, split across two fields). 60func nx_dis_imm_s(w: i64) -> i64 { 61 let imm: i64 = ((w >> 25) & 0x7F) << 5 | ((w >> 7) & 0x1F) 62 if (imm & 0x800) != 0 { return imm | (~0xFFF) } 63 return imm 64} 65 66// B-type immediate (signed 13-bit, split across funky fields). 67func nx_dis_imm_b(w: i64) -> i64 { 68 let bit12: i64 = (w >> 31) & 0x1 69 let bit11: i64 = (w >> 7) & 0x1 70 let bits_10_5: i64 = (w >> 25) & 0x3F 71 let bits_4_1: i64 = (w >> 8) & 0xF 72 let imm: i64 = (bit12 << 12) | (bit11 << 11) | (bits_10_5 << 5) | (bits_4_1 << 1) 73 if (imm & 0x1000) != 0 { return imm | (~0x1FFF) } 74 return imm 75} 76 77// U-type immediate (upper 20 bits, sign-extended after shift left). 78func nx_dis_imm_u(w: i64) -> i64 { 79 let imm: i64 = w & 0xFFFFF000 80 if (imm & 0x80000000) != 0 { return imm | (~0xFFFFFFFF) } 81 return imm 82} 83 84// J-type immediate (signed 21-bit, scrambled). 85func nx_dis_imm_j(w: i64) -> i64 { 86 let bit20: i64 = (w >> 31) & 0x1 87 let bits_10_1: i64 = (w >> 21) & 0x3FF 88 let bit11: i64 = (w >> 20) & 0x1 89 let bits_19_12: i64 = (w >> 12) & 0xFF 90 let imm: i64 = (bit20 << 20) | (bits_19_12 << 12) | (bit11 << 11) | (bits_10_1 << 1) 91 if (imm & 0x100000) != 0 { return imm | (~0x1FFFFF) } 92 return imm 93} 94 95// ---- register names ---------------------------------------------- 96 97// Returns a *u8 to a static name string for register `n` (0..31). 98// Standard ABI mnemonics: zero, ra, sp, gp, tp, t0..t6, s0..s11, 99// a0..a7. 100func nx_dis_reg_name(n: i64) -> *u8 { 101 if n == 0 { return "zero" as *u8 } 102 if n == 1 { return "ra" as *u8 } 103 if n == 2 { return "sp" as *u8 } 104 if n == 3 { return "gp" as *u8 } 105 if n == 4 { return "tp" as *u8 } 106 if n == 5 { return "t0" as *u8 } 107 if n == 6 { return "t1" as *u8 } 108 if n == 7 { return "t2" as *u8 } 109 if n == 8 { return "s0" as *u8 } 110 if n == 9 { return "s1" as *u8 } 111 if n == 10 { return "a0" as *u8 } 112 if n == 11 { return "a1" as *u8 } 113 if n == 12 { return "a2" as *u8 } 114 if n == 13 { return "a3" as *u8 } 115 if n == 14 { return "a4" as *u8 } 116 if n == 15 { return "a5" as *u8 } 117 if n == 16 { return "a6" as *u8 } 118 if n == 17 { return "a7" as *u8 } 119 if n == 18 { return "s2" as *u8 } 120 if n == 19 { return "s3" as *u8 } 121 if n == 20 { return "s4" as *u8 } 122 if n == 21 { return "s5" as *u8 } 123 if n == 22 { return "s6" as *u8 } 124 if n == 23 { return "s7" as *u8 } 125 if n == 24 { return "s8" as *u8 } 126 if n == 25 { return "s9" as *u8 } 127 if n == 26 { return "s10" as *u8 } 128 if n == 27 { return "s11" as *u8 } 129 if n == 28 { return "t3" as *u8 } 130 if n == 29 { return "t4" as *u8 } 131 if n == 30 { return "t5" as *u8 } 132 if n == 31 { return "t6" as *u8 } 133 return "?" as *u8 134} 135 136// ---- mnemonic lookup ---------------------------------------------- 137 138// R-type opcode (0x33) and 0x3B (RV64 word ops). Returns mnemonic. 139func nx_dis_r_mnem(funct3: i64, funct7: i64, is_w: i64) -> *u8 { 140 if is_w == 1 { 141 if funct3 == 0 { if funct7 == 0 { return "addw" as *u8 } else { return "subw" as *u8 } } 142 if funct3 == 1 { return "sllw" as *u8 } 143 if funct3 == 5 { if funct7 == 0 { return "srlw" as *u8 } else { return "sraw" as *u8 } } 144 } 145 if funct3 == 0 { 146 if funct7 == 0 { return "add" as *u8 } 147 if funct7 == 0x20 { return "sub" as *u8 } 148 if funct7 == 1 { return "mul" as *u8 } 149 } 150 if funct3 == 1 { 151 if funct7 == 0 { return "sll" as *u8 } 152 if funct7 == 1 { return "mulh" as *u8 } 153 } 154 if funct3 == 2 { if funct7 == 0 { return "slt" as *u8 } } 155 if funct3 == 3 { if funct7 == 0 { return "sltu" as *u8 } } 156 if funct3 == 4 { 157 if funct7 == 0 { return "xor" as *u8 } 158 if funct7 == 1 { return "div" as *u8 } 159 } 160 if funct3 == 5 { 161 if funct7 == 0 { return "srl" as *u8 } 162 if funct7 == 0x20 { return "sra" as *u8 } 163 if funct7 == 1 { return "divu" as *u8 } 164 } 165 if funct3 == 6 { 166 if funct7 == 0 { return "or" as *u8 } 167 if funct7 == 1 { return "rem" as *u8 } 168 } 169 if funct3 == 7 { 170 if funct7 == 0 { return "and" as *u8 } 171 if funct7 == 1 { return "remu" as *u8 } 172 } 173 return "?r" as *u8 174} 175 176// I-type opcode 0x13 / 0x1B (immediate ALU). 177func nx_dis_i_alu_mnem(funct3: i64, funct7: i64, is_w: i64) -> *u8 { 178 if is_w == 1 { 179 if funct3 == 0 { return "addiw" as *u8 } 180 if funct3 == 1 { return "slliw" as *u8 } 181 if funct3 == 5 { 182 if funct7 == 0 { return "srliw" as *u8 } 183 return "sraiw" as *u8 184 } 185 } 186 if funct3 == 0 { return "addi" as *u8 } 187 if funct3 == 2 { return "slti" as *u8 } 188 if funct3 == 3 { return "sltiu" as *u8 } 189 if funct3 == 4 { return "xori" as *u8 } 190 if funct3 == 6 { return "ori" as *u8 } 191 if funct3 == 7 { return "andi" as *u8 } 192 if funct3 == 1 { return "slli" as *u8 } 193 if funct3 == 5 { 194 if funct7 == 0 { return "srli" as *u8 } 195 return "srai" as *u8 196 } 197 return "?i" as *u8 198} 199 200// I-type opcode 0x03 (loads). 201func nx_dis_load_mnem(funct3: i64) -> *u8 { 202 if funct3 == 0 { return "lb" as *u8 } 203 if funct3 == 1 { return "lh" as *u8 } 204 if funct3 == 2 { return "lw" as *u8 } 205 if funct3 == 3 { return "ld" as *u8 } 206 if funct3 == 4 { return "lbu" as *u8 } 207 if funct3 == 5 { return "lhu" as *u8 } 208 if funct3 == 6 { return "lwu" as *u8 } 209 return "?l" as *u8 210} 211 212// S-type opcode 0x23 (stores). 213func nx_dis_store_mnem(funct3: i64) -> *u8 { 214 if funct3 == 0 { return "sb" as *u8 } 215 if funct3 == 1 { return "sh" as *u8 } 216 if funct3 == 2 { return "sw" as *u8 } 217 if funct3 == 3 { return "sd" as *u8 } 218 return "?s" as *u8 219} 220 221// B-type opcode 0x63 (branches). 222func nx_dis_branch_mnem(funct3: i64) -> *u8 { 223 if funct3 == 0 { return "beq" as *u8 } 224 if funct3 == 1 { return "bne" as *u8 } 225 if funct3 == 4 { return "blt" as *u8 } 226 if funct3 == 5 { return "bge" as *u8 } 227 if funct3 == 6 { return "bltu" as *u8 } 228 if funct3 == 7 { return "bgeu" as *u8 } 229 return "?b" as *u8 230} 231 232// ---- print helpers ------------------------------------------------ 233 234func nx_dis_puts(fd: i64, s: *u8) -> i64 { 235 var n: i64 = 0 236 while s[n] != 0 { n = n + 1 } 237 sys_write(fd, s, n) 238 return 0 239} 240 241func nx_dis_putdec(fd: i64, v: i64) -> i64 { 242 let buf: *u8 = sys_mmap(16) 243 var n: i64 = v 244 if n < 0 { 245 sys_write(fd, "-" as *u8, 1) 246 n = 0 - n 247 } 248 if n == 0 { sys_write(fd, "0" as *u8, 1); return 0 } 249 var k: i64 = 0 250 while n > 0 { 251 buf[k] = 0x30 + (n - (n / 10) * 10) 252 n = n / 10 253 k = k + 1 254 } 255 var j: i64 = 0 256 var l: i64 = k - 1 257 while j < l { 258 let t: i64 = buf[j]; buf[j] = buf[l]; buf[l] = t 259 j = j + 1 260 l = l - 1 261 } 262 sys_write(fd, buf, k) 263 return 0 264} 265 266// ---- the main disassembler --------------------------------------- 267 268// Disassemble one 4-byte instruction at byte offset `off` in `buf`. 269// Prints to `fd`. Returns the number of bytes consumed (4 for full 270// RV64 instr; future C-extension will return 2). 271func nx_dis_one(buf: *u8, off: i64, fd: i64) -> i64 { 272 let w: i64 = nx_dis_word_at(buf, off) 273 let op: i64 = nx_dis_opcode(w) 274 275 // R-type ALU (0x33 = full, 0x3B = word). 276 if op == 0x33 { 277 nx_dis_puts(fd, nx_dis_r_mnem(nx_dis_funct3(w), nx_dis_funct7(w), 0)) 278 sys_write(fd, " " as *u8, 1) 279 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rd(w))) 280 sys_write(fd, ", " as *u8, 2) 281 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs1(w))) 282 sys_write(fd, ", " as *u8, 2) 283 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs2(w))) 284 return 4 285 } 286 if op == 0x3B { 287 nx_dis_puts(fd, nx_dis_r_mnem(nx_dis_funct3(w), nx_dis_funct7(w), 1)) 288 sys_write(fd, " " as *u8, 1) 289 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rd(w))) 290 sys_write(fd, ", " as *u8, 2) 291 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs1(w))) 292 sys_write(fd, ", " as *u8, 2) 293 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs2(w))) 294 return 4 295 } 296 297 // I-type ALU (0x13 = full, 0x1B = word). 298 if op == 0x13 { 299 nx_dis_puts(fd, nx_dis_i_alu_mnem(nx_dis_funct3(w), nx_dis_funct7(w), 0)) 300 sys_write(fd, " " as *u8, 1) 301 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rd(w))) 302 sys_write(fd, ", " as *u8, 2) 303 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs1(w))) 304 sys_write(fd, ", " as *u8, 2) 305 nx_dis_putdec(fd, nx_dis_imm_i(w)) 306 return 4 307 } 308 if op == 0x1B { 309 nx_dis_puts(fd, nx_dis_i_alu_mnem(nx_dis_funct3(w), nx_dis_funct7(w), 1)) 310 sys_write(fd, " " as *u8, 1) 311 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rd(w))) 312 sys_write(fd, ", " as *u8, 2) 313 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs1(w))) 314 sys_write(fd, ", " as *u8, 2) 315 nx_dis_putdec(fd, nx_dis_imm_i(w)) 316 return 4 317 } 318 319 // Loads (0x03). 320 if op == 0x03 { 321 nx_dis_puts(fd, nx_dis_load_mnem(nx_dis_funct3(w))) 322 sys_write(fd, " " as *u8, 1) 323 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rd(w))) 324 sys_write(fd, ", " as *u8, 2) 325 nx_dis_putdec(fd, nx_dis_imm_i(w)) 326 sys_write(fd, "(" as *u8, 1) 327 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs1(w))) 328 sys_write(fd, ")" as *u8, 1) 329 return 4 330 } 331 332 // Stores (0x23). 333 if op == 0x23 { 334 nx_dis_puts(fd, nx_dis_store_mnem(nx_dis_funct3(w))) 335 sys_write(fd, " " as *u8, 1) 336 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs2(w))) 337 sys_write(fd, ", " as *u8, 2) 338 nx_dis_putdec(fd, nx_dis_imm_s(w)) 339 sys_write(fd, "(" as *u8, 1) 340 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs1(w))) 341 sys_write(fd, ")" as *u8, 1) 342 return 4 343 } 344 345 // Branches (0x63). 346 if op == 0x63 { 347 nx_dis_puts(fd, nx_dis_branch_mnem(nx_dis_funct3(w))) 348 sys_write(fd, " " as *u8, 1) 349 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs1(w))) 350 sys_write(fd, ", " as *u8, 2) 351 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs2(w))) 352 sys_write(fd, ", " as *u8, 2) 353 nx_dis_putdec(fd, nx_dis_imm_b(w)) 354 return 4 355 } 356 357 // U-type: lui (0x37), auipc (0x17). 358 if op == 0x37 { 359 nx_dis_puts(fd, "lui " as *u8) 360 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rd(w))) 361 sys_write(fd, ", " as *u8, 2) 362 nx_dis_putdec(fd, nx_dis_imm_u(w) >> 12) 363 return 4 364 } 365 if op == 0x17 { 366 nx_dis_puts(fd, "auipc " as *u8) 367 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rd(w))) 368 sys_write(fd, ", " as *u8, 2) 369 nx_dis_putdec(fd, nx_dis_imm_u(w) >> 12) 370 return 4 371 } 372 373 // J-type: jal (0x6F). 374 if op == 0x6F { 375 nx_dis_puts(fd, "jal " as *u8) 376 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rd(w))) 377 sys_write(fd, ", " as *u8, 2) 378 nx_dis_putdec(fd, nx_dis_imm_j(w)) 379 return 4 380 } 381 382 // I-type jump: jalr (0x67). 383 if op == 0x67 { 384 nx_dis_puts(fd, "jalr " as *u8) 385 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rd(w))) 386 sys_write(fd, ", " as *u8, 2) 387 nx_dis_putdec(fd, nx_dis_imm_i(w)) 388 sys_write(fd, "(" as *u8, 1) 389 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs1(w))) 390 sys_write(fd, ")" as *u8, 1) 391 return 4 392 } 393 394 // System: ecall / ebreak (0x73 with rd=0, rs1=0). 395 if op == 0x73 { 396 if nx_dis_imm_i(w) == 0 { 397 nx_dis_puts(fd, "ecall" as *u8) 398 } else { 399 nx_dis_puts(fd, "ebreak" as *u8) 400 } 401 return 4 402 } 403 404 // Unknown -- print as raw word. 405 nx_dis_puts(fd, "?? 0x" as *u8) 406 let hi: i64 = (w >> 16) & 0xFFFF 407 let lo: i64 = w & 0xFFFF 408 nx_dis_putdec(fd, hi) 409 sys_write(fd, "_" as *u8, 1) 410 nx_dis_putdec(fd, lo) 411 return 4 412} 413 414// Disassemble a contiguous range; one instr per line. 415func nx_dis_range(buf: *u8, off: i64, len: i64, fd: i64) -> i64 { 416 var p: i64 = off 417 while p < off + len { 418 let n: i64 = nx_dis_one(buf, p, fd) 419 sys_write(fd, "\n" as *u8, 1) 420 if n <= 0 { return -1 } 421 p = p + n 422 } 423 return 0 424} 425 426// ---- self-test ---------------------------------------------------- 427 428func main() -> i64 { 429 // Hand-encoded RV64 instructions: 430 // addi a0, zero, 42 -> 0x02A00513 431 // add s0, s1, s2 -> 0x01248433 432 // ld t0, 8(sp) -> 0x00813283 433 // sd t0, 16(sp) -> 0x00513823 434 // ecall -> 0x00000073 435 let buf: *u8 = sys_mmap(64) 436 // addi a0, zero, 42 = 0x02A00513 437 buf[0] = 0x13; buf[1] = 0x05; buf[2] = 0xA0; buf[3] = 0x02 438 // add s0, s1, s2 = 0x01248433 439 buf[4] = 0x33; buf[5] = 0x84; buf[6] = 0x24; buf[7] = 0x01 440 // ld t0, 8(sp) = 0x00813283 441 buf[8] = 0x83; buf[9] = 0x32; buf[10] = 0x81; buf[11] = 0x00 442 // sd t0, 16(sp) = 0x00513823 443 buf[12] = 0x23; buf[13] = 0x38; buf[14] = 0x51; buf[15] = 0x00 444 // ecall = 0x00000073 445 buf[16] = 0x73; buf[17] = 0x00; buf[18] = 0x00; buf[19] = 0x00 446 447 // Verify decode helpers. 448 let w0: i64 = nx_dis_word_at(buf, 0) 449 if nx_dis_opcode(w0) != 0x13 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 450 if nx_dis_rd(w0) != 10 { return __syscall(93, 11, 0, 0, 0, 0, 0) } // a0 451 if nx_dis_rs1(w0) != 0 { return __syscall(93, 12, 0, 0, 0, 0, 0) } // zero 452 if nx_dis_imm_i(w0) != 42 { return __syscall(93, 13, 0, 0, 0, 0, 0) } 453 454 let w1: i64 = nx_dis_word_at(buf, 4) 455 if nx_dis_opcode(w1) != 0x33 { return __syscall(93, 20, 0, 0, 0, 0, 0) } 456 if nx_dis_rd(w1) != 8 { return __syscall(93, 21, 0, 0, 0, 0, 0) } // s0 457 if nx_dis_rs1(w1) != 9 { return __syscall(93, 22, 0, 0, 0, 0, 0) } // s1 458 if nx_dis_rs2(w1) != 18 { return __syscall(93, 23, 0, 0, 0, 0, 0) } // s2 459 460 let w2: i64 = nx_dis_word_at(buf, 8) 461 if nx_dis_opcode(w2) != 0x03 { return __syscall(93, 30, 0, 0, 0, 0, 0) } 462 if nx_dis_imm_i(w2) != 8 { return __syscall(93, 31, 0, 0, 0, 0, 0) } 463 464 let w3: i64 = nx_dis_word_at(buf, 12) 465 if nx_dis_opcode(w3) != 0x23 { return __syscall(93, 40, 0, 0, 0, 0, 0) } 466 if nx_dis_imm_s(w3) != 16 { return __syscall(93, 41, 0, 0, 0, 0, 0) } 467 468 let w4: i64 = nx_dis_word_at(buf, 16) 469 if nx_dis_opcode(w4) != 0x73 { return __syscall(93, 50, 0, 0, 0, 0, 0) } 470 471 // Register-name lookup. 472 let s_zero: *u8 = nx_dis_reg_name(0) 473 if s_zero[0] != 0x7A { return __syscall(93, 60, 0, 0, 0, 0, 0) } // 'z' 474 let s_ra: *u8 = nx_dis_reg_name(1) 475 if s_ra[0] != 0x72 { return __syscall(93, 61, 0, 0, 0, 0, 0) } // 'r' 476 let s_a0: *u8 = nx_dis_reg_name(10) 477 if s_a0[0] != 0x61 { return __syscall(93, 62, 0, 0, 0, 0, 0) } // 'a' 478 479 return 0 480}