543 lines
18 KiB
JavaScript
543 lines
18 KiB
JavaScript
|
// 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 PRONAMES = [
|
||
|
"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 SYSNAMES = [
|
||
|
"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"
|
||
|
];
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////// 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.PRONAMES[index] : "r" + index;
|
||
|
if (this.proUppercase)
|
||
|
ret = ret.toUpperCase();
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
// Format a system register
|
||
|
systemRegister(index) {
|
||
|
let ret = this.sysNames ?
|
||
|
Disassembler.SYSNAMES[index] : index.toString();
|
||
|
if (!this.sysUppercase && this.sysNames)
|
||
|
ret = ret.toLowerCase();
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
export { Disassembler };
|