pvbemu/web/core/Disassembler.js

547 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 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 };