// Machine code to human readable text converter class Disassembler { //////////////////////////////// Constants //////////////////////////////// // Default settings static DEFAULTS = { condCL : "L", // Use C/NC or L/NL for conditions condEZ : "E", // Use E/NE or Z/NZ for conditions condNames : true, // Use condition names condUppercase: false, // Condition names uppercase hexPrefix : "0x", // Hexadecimal prefix hexSuffix : "", // Hexadecimal suffix hexUppercase : true, // Hexadecimal uppercase instUppercase: true, // Mnemonics uppercase jumpAddress : true, // Jump/branch shows target address memInside : false, // Use [reg1 + disp] notation opDestFirst : false, // Destination operand first proNames : true, // Use program register names proUppercase : false, // Program register names uppercase splitBcond : false, // BCOND condition as an operand splitSetf : true, // SETF condition as an operand sysNames : true, // Use system register names sysUppercase : false // System register names uppercase }; /////////////////////////// Disassembly Lookup //////////////////////////// // Opcode descriptors static OPDEFS = [ [ "MOV" , [ "opReg1" , "opReg2" ] ], // 000000 [ "ADD" , [ "opReg1" , "opReg2" ] ], [ "SUB" , [ "opReg1" , "opReg2" ] ], [ "CMP" , [ "opReg1" , "opReg2" ] ], [ "SHL" , [ "opReg1" , "opReg2" ] ], [ "SHR" , [ "opReg1" , "opReg2" ] ], [ "JMP" , [ "opReg1Ind" ] ], [ "SAR" , [ "opReg1" , "opReg2" ] ], [ "MUL" , [ "opReg1" , "opReg2" ] ], [ "DIV" , [ "opReg1" , "opReg2" ] ], [ "MULU" , [ "opReg1" , "opReg2" ] ], [ "DIVU" , [ "opReg1" , "opReg2" ] ], [ "OR" , [ "opReg1" , "opReg2" ] ], [ "AND" , [ "opReg1" , "opReg2" ] ], [ "XOR" , [ "opReg1" , "opReg2" ] ], [ "NOT" , [ "opReg1" , "opReg2" ] ], [ "MOV" , [ "opImm5S", "opReg2" ] ], // 010000 [ "ADD" , [ "opImm5S", "opReg2" ] ], null, // SETF: special [ "CMP" , [ "opImm5S", "opReg2" ] ], [ "SHL" , [ "opImm5U", "opReg2" ] ], [ "SHR" , [ "opImm5U", "opReg2" ] ], [ "CLI" , [ ] ], [ "SAR" , [ "opImm5U", "opReg2" ] ], [ "TRAP" , [ "opImm5U" ] ], [ "RETI" , [ ] ], [ "HALT" , [ ] ], null, // Invalid [ "LDSR" , [ "opReg2" , "opSys" ] ], [ "STSR" , [ "opSys" , "opReg2" ] ], [ "SEI" , [ ] ], null, // Bit string: special null, // BCOND: special // 100000 null, // BCOND: special null, // BCOND: special null, // BCOND: special null, // BCOND: special null, // BCOND: special null, // BCOND: special null, // BCOND: special [ "MOVEA", [ "opImm16U" , "opReg1", "opReg2" ] ], [ "ADDI" , [ "opImm16S" , "opReg1", "opReg2" ] ], [ "JR" , [ "opDisp26" ] ], [ "JAL" , [ "opDisp26" ] ], [ "ORI" , [ "opImm16U" , "opReg1", "opReg2" ] ], [ "ANDI" , [ "opImm16U" , "opReg1", "opReg2" ] ], [ "XORI" , [ "opImm16U" , "opReg1", "opReg2" ] ], [ "MOVHI", [ "opImm16U" , "opReg1", "opReg2" ] ], [ "LD.B" , [ "opReg1Disp", "opReg2" ] ], // 110000 [ "LD.H" , [ "opReg1Disp", "opReg2" ] ], null, // Invalid [ "LD.W" , [ "opReg1Disp", "opReg2" ] ], [ "ST.B" , [ "opReg2" , "opReg1Disp" ] ], [ "ST.H" , [ "opReg2" , "opReg1Disp" ] ], null, // Invalid [ "ST.W" , [ "opReg2" , "opReg1Disp" ] ], [ "IN.B" , [ "opReg1Disp", "opReg2" ] ], [ "IN.H" , [ "opReg1Disp", "opReg2" ] ], [ "CAXI" , [ "opReg1Disp", "opReg2" ] ], [ "IN.W" , [ "opReg1Disp", "opReg2" ] ], [ "OUT.B", [ "opReg2" , "opReg1Disp" ] ], [ "OUT.H", [ "opReg2" , "opReg1Disp" ] ], null, // Floating-point/Nintendo: special [ "OUT.W", [ "opReg2" , "opReg1Disp" ] ] ]; // Bit string sub-opcode descriptors static BITSTRING = [ "SCH0BSU", "SCH0BSD", "SCH1BSU", "SCH1BSD", null , null , null , null , "ORBSU" , "ANDBSU" , "XORBSU" , "MOVBSU" , "ORNBSU" , "ANDNBSU", "XORNBSU", "NOTBSU" , null , null , null , null , null , null , null , null , null , null , null , null , null , null , null , null ]; // Floating-point/Nintendo sub-opcode descriptors static FLOATENDO = [ [ "CMPF.S" , [ "opReg1", "opReg2" ] ], null, // Invalid [ "CVT.WS" , [ "opReg1", "opReg2" ] ], [ "CVT.SW" , [ "opReg1", "opReg2" ] ], [ "ADDF.S" , [ "opReg1", "opReg2" ] ], [ "SUBF.S" , [ "opReg1", "opReg2" ] ], [ "MULF.S" , [ "opReg1", "opReg2" ] ], [ "DIVF.S" , [ "opReg1", "opReg2" ] ], [ "XB" , [ "opReg2" ] ], [ "XH" , [ "opReg2" ] ], [ "REV" , [ "opReg1", "opReg2" ] ], [ "TRNC.SW", [ "opReg1", "opReg2" ] ], [ "MPYHW" , [ "opReg1", "opReg2" ] ], null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null ]; // Condition mnemonics static CONDITIONS = [ "V" , "C" , "E" , "NH", "N", "T", "LT", "LE", "NV", "NC", "NE", "H" , "P", "F", "GE", "GT" ]; // Program register names static REG_PROGRAM = [ "r0" , "r1" , "hp" , "sp" , "gp" , "tp" , "r6" , "r7" , "r8" , "r9" , "r10", "r11", "r12", "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", "r30", "lp" ]; // System register names static REG_SYSTEM = [ "EIPC", "EIPSW", "FEPC", "FEPSW", "ECR", "PSW", "PIR", "TKCW", "8" , "9" , "10" , "11" , "12" , "13" , "14" , "15" , "16" , "17" , "18" , "19" , "20" , "21" , "22" , "23" , "CHCW", "ADTRE", "26" , "27" , "28" , "29" , "30" , "31" ]; // Other register names static REG_OTHER = [ "PC", "PSW" ]; ///////////////////////////// Static Methods ////////////////////////////// // Determine the bounds of a data buffer to represent all lines of output static dataBounds(address, line, length) { let before = 10; // Number of lines before the first line of output let max = 4; // Maximum number of bytes that can appear on a line // The reference line is before the preferred earliest line if (line < -before) { length = (length - line) * max; } // The reference line is before the first line else if (line < 0) { address -= (line + before) * max; length = (length + before) * max; } // The reference line is at or after the first line else { address -= (line + before) * max; length = (Math.max(length, line) + before) * max; } return { address: (address & ~1) >>> 0, length : length }; } ///////////////////////// Initialization Methods ////////////////////////// constructor() { Object.assign(this, Disassembler.DEFAULTS); } ///////////////////////////// Public Methods ////////////////////////////// // Disassemble a region of memory disassemble(data, dataAddress, refAddress, refLine, length, pc = null) { let pcOffset = pc === null ? -1 : pc - dataAddress >>> 0; // Locate the offset of the first line of output in the buffer let offset = 0; for (let addr = dataAddress, circle = refLine > 0 ? new Array(refLine) : null, index = 0, more = [], remain = null ;;) { // Determine the size of the current line if (more.length == 0) this.more(more, data, offset); let size = more.shift(); // The current line contains the reference address if (refAddress - addr >>> 0 < size) { // The next item in the buffer is the first line of output if (refLine > 0) { offset = circle[index]; break; } // This line is the first line of output if (refLine == 0) break; // Count more lines for the first line of output remain = refLine; } // Record the offset of the current instruction if (refLine > 0) { circle[index] = offset; index = (index + 1) % circle.length; } // Advance to the next line let sizeToPC = pcOffset - offset >>> 0; if (offset != pcOffset && sizeToPC < size) { size = sizeToPC; more.splice(); } addr = addr + size >>> 0; offset += size; if (remain !== null && ++remain == 0) break; // The next line is the first line of output } // Process all lines of output let lines = new Array(length); for (let addr = dataAddress + offset, more = [], x = 0; x < length; x++ ) { // Determine the size of the current line if (more.length == 0) this.more(more, data, offset, pcOffset); let size = more.shift(); // Add the line to the response lines[x] = this.format({ rawAddress: addr, rawBytes : data.slice(offset, offset + size) }); // Advance to the next line let sizeToPC = pcOffset - offset >>> 0; if (offset != pcOffset && sizeToPC < size) { size = sizeToPC; more.splice(); } addr = addr + size >>> 0; offset += size; } return lines; } /////////////////////////// Formatting Methods //////////////////////////// // Format a line as human-readable text format(line) { let canReverse = true; let opcode = line.rawBytes[1] >>> 2; let opdef; let code = [ line.rawBytes[1] << 8 | line.rawBytes[0], line.rawBytes.length == 2 ? null : line.rawBytes[3] << 8 | line.rawBytes[2] ]; // BCOND if ((opcode & 0b111000) == 0b100000) { let cond = code[0] >>> 9 & 15; opdef = cond == 13 ? [ "NOP", [ ] ] : this.splitBcond ? [ "BCOND", [ "opBCond", "opDisp9" ] ] : [ cond == 5 ? "BR" : "B" + this.condition(cond, true), [ "opDisp9" ] ] ; canReverse = false; } // Processing by opcode else switch (opcode) { // SETF case 0b010010: opdef = !this.splitSetf ? [ "SETF" + Disassembler.CONDITIONS[code[0] & 15], [ "opReg2" ] ] : [ "SETF", [ "opCond", "opReg2" ] ] ; break; // Bit string case 0b011111: opdef = Disassembler.BITSTRING[code[0] & 31]; if (opdef != null) opdef = [ opdef, [] ]; break; // Floating-point/Nintendo case 0b111110: opdef = Disassembler.FLOATENDO[code[1] >>> 10]; break; // All others default: opdef = Disassembler.OPDEFS[opcode]; } // The opcode is undefined if (opdef == null) opdef = [ "---", [] ]; // Format the line's display text line.address = this.hex(line.rawAddress, 8, false); line.bytes = new Array(line.rawBytes.length); line.mnemonic = this.instUppercase ? opdef[0] : opdef[0].toLowerCase(); line.operands = new Array(opdef[1].length); for (let x = 0; x < line.bytes.length; x++) line.bytes[x] = this.hex(line.rawBytes[x], 2, false); for (let x = 0; x < line.operands.length; x++) line.operands[x] = this[opdef[1][x]](line, code); if (this.opDestFirst && canReverse) line.operands.reverse(); return line; } // Format a condition operand in a BCOND instruction opBCond(line, code) { return this.condition(code[0] >>> 9 & 15); } // Format a condition operand in a SETF instruction opCond(line, code) { return this.condition(code[0] & 15); } // Format a 9-bit displacement operand opDisp9(line, code) { let disp = code[0] << 23 >> 23; return this.jump(line.rawAddress, disp); } // Format a 26-bit displacement operand opDisp26(line, code) { let disp = (code[0] << 16 | code[1]) << 6 >> 6; return this.jump(line.rawAddress, disp); } // Format a 5-bit signed immediate operand opImm5S(line, code) { return (code[0] & 31) << 27 >> 27; } // Format a 5-bit unsigned immediate operand opImm5U(line, code) { return code[0] & 31; } // Format a 16-bit signed immediate operand opImm16S(line, code) { let ret = code[1] << 16 >> 16; return ( ret < -256 ? "-" + this.hex(-ret) : ret > 256 ? this.hex( ret) : ret ); } // Format a 16-bit unsigned immediate operand opImm16U(line, code) { return this.hex(code[1], 4); } // Format a Reg1 operand opReg1(line, code) { return this.programRegister(code[0] & 31); } // Format a disp[reg1] operand opReg1Disp(line, code) { let disp = code[1] << 16 >> 16; let reg1 = this.programRegister(code[0] & 31); // Do not print the displacement if (disp == 0) return "[" + reg1 + "]"; // Format the displacement amount disp = disp < -256 ? "-" + this.hex(-disp) : disp > 256 ? this.hex( disp) : disp.toString() ; // [reg1 + disp] notation if (this.memInside) { return "[" + reg1 + (disp.startsWith("-") ? " - " + disp.substring(1) : " + " + disp ) + "]"; } // disp[reg1] notation return disp + "[" + reg1 + "]"; } // Format a [Reg1] operand opReg1Ind(line, code) { return "[" + this.programRegister(code[0] & 31) + "]"; } // Format a Reg2 operand opReg2(line, code) { return this.programRegister(code[0] >> 5 & 31); } // Format a system register operand opSys(line, code) { return this.systemRegister(code[0] & 31); } ///////////////////////////// Private Methods ///////////////////////////// // Select the mnemonic for a condition condition(index, forceUppercase = false) { if (!this.condNames) return index.toString(); let ret = index == 1 ? this.condCL : index == 2 ? this.condEZ : index == 9 ? "N" + this.condCL : index == 10 ? "N" + this.condEZ : Disassembler.CONDITIONS[index] ; if (!forceUppercase && !this.condUppercase) ret = ret.toLowerCase(); return ret; } // Format a number as a hexadecimal string hex(value, digits = null, decorated = true) { value = value.toString(16); if (this.hexUppercase) value = value.toUpperCase(); if (digits != null) value = value.padStart(digits, "0"); if (decorated) { value = this.hexPrefix + value + this.hexSuffix; if (this.hexPrefix == "" && "0123456789".indexOf(value[0]) == -1) value = "0" + value; } return value; } // Format a jump or branch destination jump(address, disp) { return ( this.jumpAddress ? this.hex(address + disp >>> 0, 8, false) : disp < -256 ? "-" + this.hex(-disp) : disp > 256 ? "+" + this.hex( disp) : disp.toString() ); } // Determine the number of bytes in the next line(s) of disassembly more(more, data, offset) { // Error checking if (offset + 1 >= data.length) throw new Error("Disassembly error: Unexpected EoF"); // Determine the instruction's size from its opcode let opcode = data[offset + 1] >>> 2; more.push( opcode < 0b101000 || // 16-bit instruction opcode == 0b110010 || // Illegal opcode opcode == 0b110110 // Illegal opcode ? 2 : 4); } // Format a program register programRegister(index) { let ret = this.proNames ? Disassembler.REG_PROGRAM[index] : "r" + index; if (this.proUppercase) ret = ret.toUpperCase(); return ret; } // Format a system register systemRegister(index) { let ret = this.sysNames ? Disassembler.REG_SYSTEM[index] : index.toString(); if (!this.sysUppercase && this.sysNames) ret = ret.toLowerCase(); return ret; } } export { Disassembler };