Disassembled Compaq BIOS can now be reassembled

This commit is contained in:
Jeff Parsons 2015-04-06 16:02:27 -07:00 committed by jeffpar
commit fd323fc9c7
4 changed files with 14396 additions and 15195 deletions

File diff suppressed because it is too large Load diff

View file

@ -67,15 +67,13 @@ Producing Source Code with NDISASM
--- ---
From the current directory, I ran: From the current directory, I ran:
ndisasm -o0x8000 private/1988-01-28.rom > 1988-01-28.nasm ndisasm -o0x8000 -se105h -se05ah -se6ffh -sf025h -sf8aah private/1988-01-28.rom > 1988-01-28.nasm
Then I massaged the output with a regex: Then I ran the TextOut command, with the *--nasm* option, to clean up the code:
^([0-9A-F]+)\s+([0-9A-F]+)\s+(o32 |a32 |)([^\s]*) *(.*)$ --> \t$3$4\t$5\t\t\t; $1 $2
Then I ran custom TextOut command to clean up the code:
node ~/Sites/pcjs/modules/textout/bin/textout --file=1988-01-28.nasm --nasm > t.nasm node ~/Sites/pcjs/modules/textout/bin/textout --file=1988-01-28.nasm --nasm > t.nasm
mv t.nasm 1988-01-28.nasm mv t.nasm 1988-01-28.nasm
The result: [1988-01-28.nasm](1988-01-28.nasm). Now it needs a human touch. The result, [1988-01-28.nasm](1988-01-28.nasm), can then be reassembled:
nasm -f bin 1988-01-28.nasm -l 1988-01-28.lst -o 1988-01-28.rom

View file

@ -443,44 +443,44 @@ function genMode(a, d, w, mrm, sGroup, sRO) {
case 0: case 0:
sModAddr = "this.regEBX + this.regESI"; sModAddr = "this.regEBX + this.regESI";
sModFunc = "BXSI"; sModFunc = "BXSI";
nCycles = "this.CYCLES.nEACyclesBaseIndex"; // 8086: 7 nCycles = "this.cycleCounts.nEACyclesBaseIndex"; // 8086: 7
break; break;
case 1: case 1:
sModAddr = "this.regEBX + this.regEDI"; sModAddr = "this.regEBX + this.regEDI";
sModFunc = "BXDI"; sModFunc = "BXDI";
nCycles = "this.CYCLES.nEACyclesBaseIndexExtra"; // 8086: 8 nCycles = "this.cycleCounts.nEACyclesBaseIndexExtra"; // 8086: 8
break; break;
case 2: case 2:
sModAddr = "this.regEBP + this.regESI"; sModAddr = "this.regEBP + this.regESI";
sModFunc = "BPSI"; sModFunc = "BPSI";
sModRegSeg = "Stack"; sModRegSeg = "Stack";
nCycles = "this.CYCLES.nEACyclesBaseIndexExtra"; // 8086: 8 nCycles = "this.cycleCounts.nEACyclesBaseIndexExtra"; // 8086: 8
break; break;
case 3: case 3:
sModAddr = "this.regEBP + this.regEDI"; sModAddr = "this.regEBP + this.regEDI";
sModFunc = "BPDI"; sModFunc = "BPDI";
sModRegSeg = "Stack"; sModRegSeg = "Stack";
nCycles = "this.CYCLES.nEACyclesBaseIndex"; // 8086: 7 nCycles = "this.cycleCounts.nEACyclesBaseIndex"; // 8086: 7
break; break;
case 4: case 4:
sModAddr = "this.regESI"; sModAddr = "this.regESI";
sModFunc = "SI"; sModFunc = "SI";
nCycles = "this.CYCLES.nEACyclesBase"; // 8086: 5 nCycles = "this.cycleCounts.nEACyclesBase"; // 8086: 5
break; break;
case 5: case 5:
sModAddr = "this.regEDI"; sModAddr = "this.regEDI";
sModFunc = "DI"; sModFunc = "DI";
nCycles = "this.CYCLES.nEACyclesBase"; // 8086: 5 nCycles = "this.cycleCounts.nEACyclesBase"; // 8086: 5
break; break;
case 6: case 6:
sModAddr = "this.getIPWord()"; sModAddr = "this.getIPWord()";
sModFunc = "D16"; sModFunc = "D16";
nCycles = "this.CYCLES.nEACyclesDisp"; // 8086: 6 nCycles = "this.cycleCounts.nEACyclesDisp"; // 8086: 6
break; break;
case 7: case 7:
sModAddr = "this.regEBX"; sModAddr = "this.regEBX";
sModFunc = "BX"; sModFunc = "BX";
nCycles = "this.CYCLES.nEACyclesBase"; // 8086: 5 nCycles = "this.cycleCounts.nEACyclesBase"; // 8086: 5
break; break;
default: default:
sError = "unrecognized mod=0 r/m: " + r_m; sError = "unrecognized mod=0 r/m: " + r_m;
@ -492,45 +492,45 @@ function genMode(a, d, w, mrm, sGroup, sRO) {
case 0: case 0:
sModAddr = "this.regEBX + this.regESI + this.getIPDisp()"; sModAddr = "this.regEBX + this.regESI + this.getIPDisp()";
sModFunc = "BXSID8"; sModFunc = "BXSID8";
nCycles = "this.CYCLES.nEACyclesBaseIndexDisp"; // 8086: 11 nCycles = "this.cycleCounts.nEACyclesBaseIndexDisp"; // 8086: 11
break; break;
case 1: case 1:
sModAddr = "this.regEBX + this.regEDI + this.getIPDisp()"; sModAddr = "this.regEBX + this.regEDI + this.getIPDisp()";
sModFunc = "BXDID8"; sModFunc = "BXDID8";
nCycles = "this.CYCLES.nEACyclesBaseIndexDispExtra"; // 8086: 12 nCycles = "this.cycleCounts.nEACyclesBaseIndexDispExtra"; // 8086: 12
break; break;
case 2: case 2:
sModAddr = "this.regEBP + this.regESI + this.getIPDisp()"; sModAddr = "this.regEBP + this.regESI + this.getIPDisp()";
sModFunc = "BPSID8"; sModFunc = "BPSID8";
sModRegSeg = "Stack"; sModRegSeg = "Stack";
nCycles = "this.CYCLES.nEACyclesBaseIndexDispExtra"; // 8086: 12 nCycles = "this.cycleCounts.nEACyclesBaseIndexDispExtra"; // 8086: 12
break; break;
case 3: case 3:
sModAddr = "this.regEBP + this.regEDI + this.getIPDisp()"; sModAddr = "this.regEBP + this.regEDI + this.getIPDisp()";
sModFunc = "BPDID8"; sModFunc = "BPDID8";
sModRegSeg = "Stack"; sModRegSeg = "Stack";
nCycles = "this.CYCLES.nEACyclesBaseIndexDisp"; // 8086: 11 nCycles = "this.cycleCounts.nEACyclesBaseIndexDisp"; // 8086: 11
break; break;
case 4: case 4:
sModAddr = "this.regESI + this.getIPDisp()"; sModAddr = "this.regESI + this.getIPDisp()";
sModFunc = "SID8"; sModFunc = "SID8";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; // 8086: 9 nCycles = "this.cycleCounts.nEACyclesBaseDisp"; // 8086: 9
break; break;
case 5: case 5:
sModAddr = "this.regEDI + this.getIPDisp()"; sModAddr = "this.regEDI + this.getIPDisp()";
sModFunc = "DID8"; sModFunc = "DID8";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; // 8086: 9 nCycles = "this.cycleCounts.nEACyclesBaseDisp"; // 8086: 9
break; break;
case 6: case 6:
sModAddr = "this.regEBP + this.getIPDisp()"; sModAddr = "this.regEBP + this.getIPDisp()";
sModFunc = "BPD8"; sModFunc = "BPD8";
sModRegSeg = "Stack"; sModRegSeg = "Stack";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; // 8086: 9 nCycles = "this.cycleCounts.nEACyclesBaseDisp"; // 8086: 9
break; break;
case 7: case 7:
sModAddr = "this.regEBX + this.getIPDisp()"; sModAddr = "this.regEBX + this.getIPDisp()";
sModFunc = "BXD8"; sModFunc = "BXD8";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; // 8086: 9 nCycles = "this.cycleCounts.nEACyclesBaseDisp"; // 8086: 9
break; break;
default: default:
sError = "unrecognized mod=1 r/m: " + r_m; sError = "unrecognized mod=1 r/m: " + r_m;
@ -542,45 +542,45 @@ function genMode(a, d, w, mrm, sGroup, sRO) {
case 0: case 0:
sModAddr = "this.regEBX + this.regESI + this.getIPWord()"; sModAddr = "this.regEBX + this.regESI + this.getIPWord()";
sModFunc = "BXSID16"; sModFunc = "BXSID16";
nCycles = "this.CYCLES.nEACyclesBaseIndexDisp"; // 8086: 11 nCycles = "this.cycleCounts.nEACyclesBaseIndexDisp"; // 8086: 11
break; break;
case 1: case 1:
sModAddr = "this.regEBX + this.regEDI + this.getIPWord()"; sModAddr = "this.regEBX + this.regEDI + this.getIPWord()";
sModFunc = "BXDID16"; sModFunc = "BXDID16";
nCycles = "this.CYCLES.nEACyclesBaseIndexDispExtra"; // 8086: 12 nCycles = "this.cycleCounts.nEACyclesBaseIndexDispExtra"; // 8086: 12
break; break;
case 2: case 2:
sModAddr = "this.regEBP + this.regESI + this.getIPWord()"; sModAddr = "this.regEBP + this.regESI + this.getIPWord()";
sModFunc = "BPSID16"; sModFunc = "BPSID16";
sModRegSeg = "Stack"; sModRegSeg = "Stack";
nCycles = "this.CYCLES.nEACyclesBaseIndexDispExtra"; // 8086: 12 nCycles = "this.cycleCounts.nEACyclesBaseIndexDispExtra"; // 8086: 12
break; break;
case 3: case 3:
sModAddr = "this.regEBP + this.regEDI + this.getIPWord()"; sModAddr = "this.regEBP + this.regEDI + this.getIPWord()";
sModFunc = "BPDID16"; sModFunc = "BPDID16";
sModRegSeg = "Stack"; sModRegSeg = "Stack";
nCycles = "this.CYCLES.nEACyclesBaseIndexDisp"; // 8086: 11 nCycles = "this.cycleCounts.nEACyclesBaseIndexDisp"; // 8086: 11
break; break;
case 4: case 4:
sModAddr = "this.regESI + this.getIPWord()"; sModAddr = "this.regESI + this.getIPWord()";
sModFunc = "SID16"; sModFunc = "SID16";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; // 8086: 9 nCycles = "this.cycleCounts.nEACyclesBaseDisp"; // 8086: 9
break; break;
case 5: case 5:
sModAddr = "this.regEDI + this.getIPWord()"; sModAddr = "this.regEDI + this.getIPWord()";
sModFunc = "DID16"; sModFunc = "DID16";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; // 8086: 9 nCycles = "this.cycleCounts.nEACyclesBaseDisp"; // 8086: 9
break; break;
case 6: case 6:
sModAddr = "this.regEBP + this.getIPWord()"; sModAddr = "this.regEBP + this.getIPWord()";
sModFunc = "BPD16"; sModFunc = "BPD16";
sModRegSeg = "Stack"; sModRegSeg = "Stack";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; // 8086: 9 nCycles = "this.cycleCounts.nEACyclesBaseDisp"; // 8086: 9
break; break;
case 7: case 7:
sModAddr = "this.regEBX + this.getIPWord()"; sModAddr = "this.regEBX + this.getIPWord()";
sModFunc = "BXD16"; sModFunc = "BXD16";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; // 8086: 9 nCycles = "this.cycleCounts.nEACyclesBaseDisp"; // 8086: 9
break; break;
default: default:
sError = "unrecognized mod=2 r/m: " + r_m; sError = "unrecognized mod=2 r/m: " + r_m;
@ -706,42 +706,42 @@ function genMode(a, d, w, mrm, sGroup, sRO) {
case 0: case 0:
sModAddr = "this.regEAX"; sModAddr = "this.regEAX";
sModFunc = "EAX"; sModFunc = "EAX";
nCycles = "this.CYCLES.nEACyclesBase"; nCycles = "this.cycleCounts.nEACyclesBase";
break; break;
case 1: case 1:
sModAddr = "this.regECX"; sModAddr = "this.regECX";
sModFunc = "ECX"; sModFunc = "ECX";
nCycles = "this.CYCLES.nEACyclesBase"; nCycles = "this.cycleCounts.nEACyclesBase";
break; break;
case 2: case 2:
sModAddr = "this.regEDX"; sModAddr = "this.regEDX";
sModFunc = "EDX"; sModFunc = "EDX";
nCycles = "this.CYCLES.nEACyclesBase"; nCycles = "this.cycleCounts.nEACyclesBase";
break; break;
case 3: case 3:
sModAddr = "this.regEBX"; sModAddr = "this.regEBX";
sModFunc = "EBX"; sModFunc = "EBX";
nCycles = "this.CYCLES.nEACyclesBase"; nCycles = "this.cycleCounts.nEACyclesBase";
break; break;
case 4: case 4:
sModAddr = "this.getSIBAddr(0)"; sModAddr = "this.getSIBAddr(0)";
sModFunc = "SIB"; sModFunc = "SIB";
nCycles = "this.CYCLES.nEACyclesBase"; nCycles = "this.cycleCounts.nEACyclesBase";
break; break;
case 5: case 5:
sModAddr = "this.getIPWord()"; sModAddr = "this.getIPWord()";
sModFunc = "D32"; sModFunc = "D32";
nCycles = "this.CYCLES.nEACyclesDisp"; nCycles = "this.cycleCounts.nEACyclesDisp";
break; break;
case 6: case 6:
sModAddr = "this.regESI"; sModAddr = "this.regESI";
sModFunc = "ESI"; sModFunc = "ESI";
nCycles = "this.CYCLES.nEACyclesBase"; nCycles = "this.cycleCounts.nEACyclesBase";
break; break;
case 7: case 7:
sModAddr = "this.regEDI"; sModAddr = "this.regEDI";
sModFunc = "EDI"; sModFunc = "EDI";
nCycles = "this.CYCLES.nEACyclesBase"; nCycles = "this.cycleCounts.nEACyclesBase";
break; break;
default: default:
sError = "unrecognized mod=0 r/m: " + r_m; sError = "unrecognized mod=0 r/m: " + r_m;
@ -753,43 +753,43 @@ function genMode(a, d, w, mrm, sGroup, sRO) {
case 0: case 0:
sModAddr = "this.regEAX + this.getIPDisp()"; sModAddr = "this.regEAX + this.getIPDisp()";
sModFunc = "EAXD8"; sModFunc = "EAXD8";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break; break;
case 1: case 1:
sModAddr = "this.regECX + this.getIPDisp()"; sModAddr = "this.regECX + this.getIPDisp()";
sModFunc = "ECXD8"; sModFunc = "ECXD8";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break; break;
case 2: case 2:
sModAddr = "this.regEDX + this.getIPDisp()"; sModAddr = "this.regEDX + this.getIPDisp()";
sModFunc = "EDXD8"; sModFunc = "EDXD8";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break; break;
case 3: case 3:
sModAddr = "this.regEBX + this.getIPDisp()"; sModAddr = "this.regEBX + this.getIPDisp()";
sModFunc = "EBXD8"; sModFunc = "EBXD8";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break; break;
case 4: case 4:
sModAddr = "this.getSIBAddr(1) + this.getIPDisp()"; sModAddr = "this.getSIBAddr(1) + this.getIPDisp()";
sModFunc = "SIBD8"; sModFunc = "SIBD8";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break; break;
case 5: case 5:
sModAddr = "this.regEBP + this.getIPDisp()"; sModAddr = "this.regEBP + this.getIPDisp()";
sModFunc = "EBPD8"; sModFunc = "EBPD8";
sModRegSeg = "Stack"; sModRegSeg = "Stack";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break; break;
case 6: case 6:
sModAddr = "this.regESI + this.getIPDisp()"; sModAddr = "this.regESI + this.getIPDisp()";
sModFunc = "ESID8"; sModFunc = "ESID8";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break; break;
case 7: case 7:
sModAddr = "this.regEDI + this.getIPDisp()"; sModAddr = "this.regEDI + this.getIPDisp()";
sModFunc = "EDID8"; sModFunc = "EDID8";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break; break;
default: default:
sError = "unrecognized mod=1 r/m: " + r_m; sError = "unrecognized mod=1 r/m: " + r_m;
@ -801,43 +801,43 @@ function genMode(a, d, w, mrm, sGroup, sRO) {
case 0: case 0:
sModAddr = "this.regEAX + this.getIPWord()"; sModAddr = "this.regEAX + this.getIPWord()";
sModFunc = "EAXD32"; sModFunc = "EAXD32";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break; break;
case 1: case 1:
sModAddr = "this.regECX + this.getIPWord()"; sModAddr = "this.regECX + this.getIPWord()";
sModFunc = "ECXD32"; sModFunc = "ECXD32";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break; break;
case 2: case 2:
sModAddr = "this.regEDX + this.getIPWord()"; sModAddr = "this.regEDX + this.getIPWord()";
sModFunc = "EDXD32"; sModFunc = "EDXD32";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break; break;
case 3: case 3:
sModAddr = "this.regEBX + this.getIPWord()"; sModAddr = "this.regEBX + this.getIPWord()";
sModFunc = "EBXD32"; sModFunc = "EBXD32";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break; break;
case 4: case 4:
sModAddr = "this.getSIBAddr(2) + this.getIPWord()"; sModAddr = "this.getSIBAddr(2) + this.getIPWord()";
sModFunc = "SIBD32"; sModFunc = "SIBD32";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break; break;
case 5: case 5:
sModAddr = "this.regEBP + this.getIPWord()"; sModAddr = "this.regEBP + this.getIPWord()";
sModFunc = "EBPD32"; sModFunc = "EBPD32";
sModRegSeg = "Stack"; sModRegSeg = "Stack";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break; break;
case 6: case 6:
sModAddr = "this.regESI + this.getIPWord()"; sModAddr = "this.regESI + this.getIPWord()";
sModFunc = "ESID32"; sModFunc = "ESID32";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break; break;
case 7: case 7:
sModAddr = "this.regEDI + this.getIPWord()"; sModAddr = "this.regEDI + this.getIPWord()";
sModFunc = "EDID32"; sModFunc = "EDID32";
nCycles = "this.CYCLES.nEACyclesBaseDisp"; nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break; break;
default: default:
sError = "unrecognized mod=2 r/m: " + r_m; sError = "unrecognized mod=2 r/m: " + r_m;

View file

@ -47,13 +47,14 @@ var str = require("../../shared/lib/strlib");
function TextOut() function TextOut()
{ {
this.fDebug = false; this.fDebug = false;
this.fASCII = true;
this.sServerRoot = process.cwd(); this.sServerRoot = process.cwd();
this.asLines = []; this.asLines = [];
this.nTabWidth = 8; this.nTabWidth = 8;
this.sTarget = "; "; this.sTarget = "; ";
} }
TextOut.asTargetRefs = ["call", "jmp", "jz", "jnz", "jc", "jnc", "ja", "jna", "js", "jns", "jo", "jno", "jl", "jnl", "jg", "jng", "jpo", "jpe"]; TextOut.asTargetRefs = ["call", "jmp", "jz", "jnz", "jc", "jnc", "ja", "jna", "js", "jns", "jo", "jno", "jl", "jnl", "jg", "jng", "jpo", "jpe", "loop", "loope", "loopne", "jcxz", "jecxz"];
/* /*
* Class methods * Class methods
@ -72,13 +73,15 @@ TextOut.asTargetRefs = ["call", "jmp", "jz", "jnz", "jc", "jnc", "ja", "jna", "j
* --- * ---
* --nasm performs a variety of NASM-related processing, including: * --nasm performs a variety of NASM-related processing, including:
* *
* collapseRepeated(): looks for series of lines containing nothing more than a "db", * massageLines(): reorders basic elements of every line to make them "assemble-able"
* "dw", or something equivalent, and collapses them into a single repetition.
* *
* labelTargets(): looks for all JMP and CALL targets and labels them. * collapseLines(): looks for series of lines containing nothing more than a "db",
* "dw", or something equivalent, and collapses them into a single repetition
*
* labelTargets(): looks for all JMP and CALL targets and labels them
* *
* alignVertical(): looks for a predefined target string (eg, '; ') in the first line, * alignVertical(): looks for a predefined target string (eg, '; ') in the first line,
* and ensures that the same sequence in all subsequent lines starts at the same column. * and ensures that the same sequence in all subsequent lines starts at the same column
* *
* For now, all output is written to stdout only. * For now, all output is written to stdout only.
* *
@ -106,7 +109,8 @@ TextOut.CLI = function()
text.loadFile(sFile, function(err) { text.loadFile(sFile, function(err) {
if (!err) { if (!err) {
if (argv['nasm']) { if (argv['nasm']) {
text.collapseRepeated(); text.massageLines();
text.collapseLines();
text.labelTargets(); text.labelTargets();
text.alignVertical(); text.alignVertical();
} }
@ -198,26 +202,131 @@ TextOut.prototype.setText = function(buf)
}; };
/** /**
* collapseRepeated() * massageLines()
* *
* @this {TextOut} * @this {TextOut}
*/ */
TextOut.prototype.collapseRepeated = function() TextOut.prototype.massageLines = function()
{
var iLineASCII = -1, sASCII = "";
for (var iLine = 0; iLine < this.asLines.length; iLine++) {
/*
* NDISASM sometimes inserts spurious lines containing a hyphen immediately followed by one or more hex bytes;
* this seems to happen whenever an instruction longer than 8 bytes is encountered, and it appears that these
* extra lines can be completely removed.
*/
var sLine = this.asLines[iLine];
if (sLine.match(/^\s*-[0-9A-F]+$/)) {
this.asLines.splice(iLine--, 1);
continue;
}
sLine = sLine.replace(/^([0-9A-F]+)\s+([0-9A-F]+)\s+(o32 |a32 |)([^\s]*) *(.*)$/, "\t$3$4\t$5\t\t\t; $1 $2");
var fASCII = false;
var match = sLine.match(/([0-9A-F]+)$/);
if (match) {
fASCII = this.fASCII;
var cBytes = 0, c;
var sBytes = match[1], sASCIILine = "";
for (var i = 0; i < sBytes.length; i += 2) {
c = str.parseInt(sBytes.substr(i, 2));
if (c != 0x0D && c != 0x0A && (c < 0x20 || c >= 0x7F)) {
c = 0x2E;
fASCII = false;
}
sASCIILine += String.fromCharCode(c);
cBytes++;
}
/*
* Don't interpret single-byte opcodes within the ASCII range as potential ASCII?
*
* if (cBytes == 1 && (c >= 0x40 && c <= 0x5F)) fASCII = false;
*/
sLine += " " + this.encodeASCII(sASCIILine);
}
this.asLines[iLine] = sLine;
if (fASCII) {
if (iLineASCII < 0) iLineASCII = iLine;
sASCII += sASCIILine;
continue;
}
if (iLineASCII >= 0) {
if (iLine - iLineASCII >= 4) {
/*
* This seems a better way of discriminating between single-byte opcodes and ASCII:
* output ASCII only when the number of ASCII bytes exceeds the number of opcode bytes.
*/
if (sASCII.length > iLine - iLineASCII + 1) {
this.asLines[iLineASCII] = "\tdb\t" + this.encodeASCII(sASCII);
this.asLines.splice(iLineASCII+1, iLine - iLineASCII - 1);
iLine = iLineASCII;
}
}
iLineASCII = -1; sASCII = "";
}
}
};
/**
* encodeASCII(s)
*
* @param s
* @return {string}
*/
TextOut.prototype.encodeASCII = function(s)
{
var sNew = "", fInQuotes = false;
for (var i = 0; i < s.length; i++) {
var c = s.charCodeAt(i);
if (c < 0x20 || c == 0x27) {
if (fInQuotes) {
sNew += "'";
fInQuotes = false;
}
if (sNew) sNew += ',';
sNew += str.toHexByte(c);
continue;
}
if (!fInQuotes) {
if (sNew) sNew += ',';
sNew += "'";
fInQuotes = true;
}
var ch = String.fromCharCode(c);
if (ch == "'") ch = "\\'";
sNew += ch;
}
if (fInQuotes) sNew += "'";
return sNew;
};
/**
* collapseLines()
*
* @this {TextOut}
*/
TextOut.prototype.collapseLines = function()
{ {
for (var i = 0; i < this.asLines.length; i++) { for (var i = 0; i < this.asLines.length; i++) {
var as = this.getLineParts(i); var as = this.getLineParts(i, true);
if (!as) continue; if (!as) continue;
if (as[2] != "db" && as[2] != "dw") continue; if (as[2] != "db" && as[2] != "dw") continue;
var cCombine = 0, asLast; var cCombine = 0, asLast;
for (var j = i + 1; j < this.asLines.length; j++) { for (var j = i + 1; j < this.asLines.length; j++) {
var asNext = this.getLineParts(j); var asNext = this.getLineParts(j, true);
if (!asNext) break; if (!asNext) break;
if (as[2] != asNext[2] || as[2] != asNext[2]) break; if (as[2] != asNext[2] || as[2] != asNext[2]) break;
asLast = asNext; asLast = asNext;
cCombine++; cCombine++;
} }
if (cCombine > 2) { if (cCombine > 2) {
this.asLines[i] = "\n\ttimes\t" + (cCombine + 1) + ' ' + as[2] + ' ' + as[3] + "\t\t; " + as[4] + " - " + asLast[4] + '\n'; this.asLines[i] = "\n\ttimes\t" + (cCombine + 1) + ' ' + as[2] + ' ' + as[3] + "\t\t; " + as[4] + " - " + asLast[4];
this.asLines.splice(i + 1, cCombine); this.asLines.splice(i + 1, cCombine);
} }
} }
@ -233,6 +342,7 @@ TextOut.prototype.labelTargets = function()
/* /*
* First pass: find all target references (eg, JMP and CALL instructions) * First pass: find all target references (eg, JMP and CALL instructions)
*/ */
var chPrefix = 'x';
var i, j, as, target, aTargets = [], aHardTargets = []; var i, j, as, target, aTargets = [], aHardTargets = [];
for (i = 0; i < this.asLines.length; i++) { for (i = 0; i < this.asLines.length; i++) {
@ -240,14 +350,25 @@ TextOut.prototype.labelTargets = function()
if (!as) continue; if (!as) continue;
var iTarget = TextOut.asTargetRefs.indexOf(as[2]); var iTarget = TextOut.asTargetRefs.indexOf(as[2]);
if (iTarget < 0) continue; if (iTarget < 0) continue;
target = str.parseInt(as[3]); var sTarget = as[3];
var sShort = "short ";
if (sTarget.indexOf(sShort) !== 0) {
sShort = "";
} else {
sTarget = sTarget.substr(sShort.length);
}
target = str.parseInt(sTarget);
if (target == undefined) continue; if (target == undefined) continue;
if (aTargets.indexOf(target) < 0) { if (aTargets.indexOf(target) < 0) {
aTargets.push(target); aTargets.push(target);
if (iTarget < 2) aHardTargets.push(target); /*
* For now, we're classifying only "call" targets as "hard" targets, and thus worthy of extra whitespace
*/
if (iTarget < 1) aHardTargets.push(target);
} }
this.asLines[i] = this.asLines[i].replace(as[3], 'l' + target.toString(16)); this.asLines[i] = this.asLines[i].replace(as[3], sShort + chPrefix + target.toString(16));
} }
/* /*
* Second pass: label all targets * Second pass: label all targets
*/ */
@ -260,48 +381,41 @@ TextOut.prototype.labelTargets = function()
j = aTargets.indexOf(addr); j = aTargets.indexOf(addr);
if (j >= 0) { if (j >= 0) {
var fHard = (aHardTargets.indexOf(addr) >= 0); var fHard = (aHardTargets.indexOf(addr) >= 0);
this.asLines[i] = (fHard || fPrevHard? "\nl" : 'l') + addr.toString(16) + ':' + this.asLines[i]; this.asLines[i] = (fHard || fPrevHard? '\n' : '') + chPrefix + addr.toString(16) + ':' + this.asLines[i];
aTargets.splice(j, 1); aTargets.splice(j, 1);
} else { } else {
if (fPrevHard) this.asLines[i] = '\n' + this.asLines[i]; if (fPrevHard) this.asLines[i] = '\n' + this.asLines[i];
} }
fPrevHard = (as[2] == "jmp" || as[2] == "ret" || as[2] == "retf" || as[2] == "iret"); fPrevHard = (as[2] == "jmp" || as[2] == "ret" || as[2] == "retf" || as[2] == "iret");
} }
/* /*
* Third pass: for all targets that turned out to NOT be targets, fix all references * Third pass: for all targets that turned out to NOT be targets, fix all references
*/ */
var aMissingTargets = []; var aRepairs = [];
if (aTargets.length) { if (aTargets.length) {
for (i = 0; i < this.asLines.length; i++) { for (i = 0; i < this.asLines.length; i++) {
as = this.getLineParts(i); as = this.getLineParts(i);
if (!as) continue; if (!as) continue;
if (as[3].charAt(0) == 'l') { if (as[3].charAt(0) == chPrefix) {
addr = str.parseInt(as[3].substr(1)); addr = str.parseInt(as[3].substr(1));
if (aTargets.indexOf(addr) >= 0) { if (aTargets.indexOf(addr) >= 0) {
this.asLines[i] = this.asLines[i].replace(as[3], str.toHexWord(addr)); /*
if (aMissingTargets.indexOf(addr) < 0) aMissingTargets.push(addr); * Instead of putting back the original target address, let's just convert the line to a "db"
*/
this.asLines[i] = as[1] + "\tdb\t";
for (j = 0; j < as[5].length; j += 2) {
this.asLines[i] += (j > 0? ',' : '') + "0x" + as[5].substr(j, 2);
}
// this.asLines[i] = this.asLines[i].replace(as[3], str.toHexWord(addr));
if (aRepairs.indexOf(addr) < 0) aRepairs.push(addr);
} }
} }
if (as[5]) {
var sASCII = "";
var sBytes = as[5];
for (j = 0; j < sBytes.length-1; j+=2) {
var k = str.parseInt(sBytes.substr(j, 2));
sASCII += (k < 0x20 || k >= 0x7f)? '.' : String.fromCharCode(k);
}
sBytes = " " + sBytes;
/*
* TODO: Determine why replace() fails when there's a trailing '$' in the replacement string;
* I'm working around it by appending a space.
*/
if (sASCII.slice(-1) == '$') sASCII += ' ';
this.asLines[i] = this.asLines[i].replace(sBytes, sBytes + " '" + sASCII + "'");
}
} }
if (aTargets.length != aMissingTargets.length) { if (aTargets.length != aRepairs.length) {
console.log("; warning: " + aTargets.length + " unprocessed targets (" + aMissingTargets.length + " repaired):"); console.log("; warning: " + aTargets.length + " unprocessed targets (" + aRepairs.length + " repaired):");
for (j = 0; j < aTargets.length; j++) { for (j = 0; j < aTargets.length; j++) {
if (aMissingTargets.indexOf(aTargets[j]) < 0) console.log(';\t' + str.toHexWord(aTargets[j])); if (aRepairs.indexOf(aTargets[j]) < 0) console.log(';\t' + str.toHexWord(aTargets[j]));
} }
} }
} }
@ -378,7 +492,7 @@ TextOut.prototype.findTarget = function(sSrc, fDebug)
}; };
/** /**
* getLineParts(iLine) * getLineParts(iLine, fBogus)
* *
* Returns the following array: * Returns the following array:
* *
@ -387,17 +501,22 @@ TextOut.prototype.findTarget = function(sSrc, fDebug)
* asParts[3]: operand(s) * asParts[3]: operand(s)
* asParts[4]: offset * asParts[4]: offset
* asParts[5]: byte sequence * asParts[5]: byte sequence
* asParts[6]: ASCII sequence, if any
* *
* or null if the line does not contain all of the above. * or null if the line does not contain all of the above.
* *
* @this {TextOut} * @this {TextOut}
* @param {number} iLine * @param {number} iLine
* @param {boolean} [fBogus]
* @return {Array.<string>} * @return {Array.<string>}
*/ */
TextOut.prototype.getLineParts = function(iLine) TextOut.prototype.getLineParts = function(iLine, fBogus)
{ {
var as = this.asLines[iLine].match(/^\n?([^\s:]+:|)\s*([^\s;]+)\s*([^;]*?)\s*;\s*([0-9A-F]+)\s*([0-9A-F]+)\s*$/); var as = this.asLines[iLine].match(/^\n?([^\s:]+:|)\s*([^\s;]+)\s*([^;]*?)\s*;\s*([0-9A-F]+)\s*([0-9A-F]+)\s*([^\s]+|)$/);
if (as) { if (as && fBogus) {
/*
* In most (but not all) cases, an "add [bx+si],al" instruction is bogus (TODO: Come up with an actual bogosity test)
*/
if (as[2] == "add" && as[3] == "[bx+si],al") { if (as[2] == "add" && as[3] == "[bx+si],al") {
as[2] = "dw"; as[2] = "dw";
as[3] = "0x0000"; as[3] = "0x0000";