diff --git a/modules/pcjs/bin/romtests.json b/modules/pcjs/bin/romtests.json index be600d348..6dd01f4b0 100644 --- a/modules/pcjs/bin/romtests.json +++ b/modules/pcjs/bin/romtests.json @@ -8,16 +8,16 @@ "ram": [ { "id": "pc386.ramLow", "name": "", - "addr": 0x00000, - "size": 0xa0000, + "addr": 0, + "size": 655360, "test": false } ], "rom": [ { "id": "pc386.romTests", "name": "", - "addr": 0xf0000, - "size": 0x10000, + "addr": 983296, + "size": 65280, "file": "/tests/pc/80386/tests.json", "notify": "" } diff --git a/modules/pcjs/lib/README.md b/modules/pcjs/lib/README.md index dbc1306e4..dcbe3fdb1 100644 --- a/modules/pcjs/lib/README.md +++ b/modules/pcjs/lib/README.md @@ -100,8 +100,8 @@ A BackTrack index is encoded as a 32-bit value with three parts: This represents a total of 31 bits, with bit 31 reserved. -For example, look at one of the last things a ROM does during boot: load a disk sector into RAM. It will be up to the -disk controller (or DMA controller if one is used) to create a BackTrack object representing the sector that was read, +For example, look at one of the last things a ROM does during boot: loading a disk sector into RAM. It will be up to the +disk controller (or DMA controller, if used) to create a BackTrack object representing the sector that was read, adding that object to the global BackTrack object array, and then associating the corresponding BackTrack index with the first byte of RAM where the sector was loaded. Subsequent bytes of RAM containing the rest of the sector will refer to the same BackTrack object, using BackTrack indexes containing offsets 1-511. diff --git a/modules/pcjs/lib/debugger.js b/modules/pcjs/lib/debugger.js index 46982c221..06228c0a5 100644 --- a/modules/pcjs/lib/debugger.js +++ b/modules/pcjs/lib/debugger.js @@ -1603,7 +1603,7 @@ if (DEBUGGER) { /* * When we dump the EXT word, we mask off the LIMIT1619 and BASE2431 bits, because those have already * been incorporated into the limit and base properties of the segment register; all we care about here - * are whether EXT contains any of the AVAIL (0x10), BIG (0x40) or GRANULARITY (0x80) bits. + * are whether EXT contains any of the AVAIL (0x10), BIG (0x40) or LIMITPAGES (0x80) bits. */ this.println(sDump + " dpl=" + str.toHexByte(seg.dpl) + " type=" + str.toHexByte(seg.type >> 8) + " (" + sType + ")" + " ext=" + str.toHexWord(seg.ext & ~(X86.DESC.EXT.LIMIT1619 | X86.DESC.EXT.BASE2431))); }; diff --git a/modules/pcjs/lib/x86.js b/modules/pcjs/lib/x86.js index 8dc7eb513..359b4fc34 100644 --- a/modules/pcjs/lib/x86.js +++ b/modules/pcjs/lib/x86.js @@ -103,10 +103,10 @@ var X86 = { MASK: 0xfff8 // table index }, DESC: { // Descriptor Table Entry - LIMIT: { + LIMIT: { // LIMIT bits 0-15 OFFSET: 0x0 }, - BASE: { + BASE: { // BASE bits 0-15 OFFSET: 0x2 }, ACC: { // bit definitions for the access word (offset 0x4) @@ -166,7 +166,7 @@ var X86 = { * is 0xffffffff instead of 0xffff. */ BIG: 0x0040, // clear if default operand/address size is 16-bit, set if 32-bit - GRANULARITY: 0x0080, // clear if limit is bytes, set if limit is 4Kb pages + LIMITPAGES: 0x0080, // clear if limit granularity is bytes, set if limit granularity is 4Kb pages BASE2431: 0xff00 }, INVALID: 0 // use X86.DESC.INVALID for invalid DESC values diff --git a/modules/pcjs/lib/x86cpu.js b/modules/pcjs/lib/x86cpu.js index 66ebe93a4..3e6915f7c 100644 --- a/modules/pcjs/lib/x86cpu.js +++ b/modules/pcjs/lib/x86cpu.js @@ -3197,18 +3197,6 @@ X86CPU.prototype.getIPDisp = function() return w; }; -/** - * getIPDispWord() - * - * @this {X86CPU} - * @return {number} sign-extended value from the word at the current IP; IP advanced by 2 or 4 - */ -X86CPU.prototype.getIPDispWord = function() -{ - var w = this.getIPWord(); - return (this.dataSize == 2? ((w << 16) >> 16) : w); -}; - /** * getSIBAddr(mod) * diff --git a/modules/pcjs/lib/x86op0f.js b/modules/pcjs/lib/x86op0f.js index 0c9c810b8..d2e258755 100644 --- a/modules/pcjs/lib/x86op0f.js +++ b/modules/pcjs/lib/x86op0f.js @@ -342,6 +342,23 @@ X86.opMOVcr = function MOVcr() } }; +/* + * NOTE: The following 16 new conditional jumps actually rely on the OPERAND override setting + * for determining whether a signed 16-bit or 32-bit displacement will be fetched, even though + * the ADDRESS override might seem more intuitive. Think of them as instructions that are loading + * a new operand into IP/EIP. + * + * Also, in 16-bit code, even though a signed rel16 value would seem to imply a range of -32768 + * to +32767, any location within a 64Kb code segment outside that range can be reached by choosing + * a displacement in the opposite direction, causing the 16-bit value in EIP to underflow or overflow; + * any underflow or overflow doesn't matter, because only the low 16 bits of EIP are used when a + * 16-bit OPERAND size is in effect. + * + * In fact, for 16-bit jumps, it's simpler to always think of rel16 as an UNSIGNED value added to + * the current EIP, where the result is then truncated to a 16-bit value. This is why we don't have + * to sign-extend rel16 before adding it to the current EIP. + */ + /** * opJOw() * @@ -351,7 +368,7 @@ X86.opMOVcr = function MOVcr() */ X86.opJOw = function JOw() { - var disp = this.getIPDispWord(); + var disp = this.getIPWord(); if (this.getOF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; @@ -369,7 +386,7 @@ X86.opJOw = function JOw() */ X86.opJNOw = function JNOw() { - var disp = this.getIPDispWord(); + var disp = this.getIPWord(); if (!this.getOF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; @@ -387,7 +404,7 @@ X86.opJNOw = function JNOw() */ X86.opJCw = function JCw() { - var disp = this.getIPDispWord(); + var disp = this.getIPWord(); if (this.getCF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; @@ -405,7 +422,7 @@ X86.opJCw = function JCw() */ X86.opJNCw = function JNCw() { - var disp = this.getIPDispWord(); + var disp = this.getIPWord(); if (!this.getCF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; @@ -423,7 +440,7 @@ X86.opJNCw = function JNCw() */ X86.opJZw = function JZw() { - var disp = this.getIPDispWord(); + var disp = this.getIPWord(); if (this.getZF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; @@ -441,7 +458,7 @@ X86.opJZw = function JZw() */ X86.opJNZw = function JNZw() { - var disp = this.getIPDispWord(); + var disp = this.getIPWord(); if (!this.getZF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; @@ -459,7 +476,7 @@ X86.opJNZw = function JNZw() */ X86.opJBEw = function JBEw() { - var disp = this.getIPDispWord(); + var disp = this.getIPWord(); if (this.getCF() || this.getZF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; @@ -477,7 +494,7 @@ X86.opJBEw = function JBEw() */ X86.opJNBEw = function JNBEw() { - var disp = this.getIPDispWord(); + var disp = this.getIPWord(); if (!this.getCF() && !this.getZF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; @@ -495,7 +512,7 @@ X86.opJNBEw = function JNBEw() */ X86.opJSw = function JSw() { - var disp = this.getIPDispWord(); + var disp = this.getIPWord(); if (this.getSF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; @@ -513,7 +530,7 @@ X86.opJSw = function JSw() */ X86.opJNSw = function JNSw() { - var disp = this.getIPDispWord(); + var disp = this.getIPWord(); if (!this.getSF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; @@ -531,7 +548,7 @@ X86.opJNSw = function JNSw() */ X86.opJPw = function JPw() { - var disp = this.getIPDispWord(); + var disp = this.getIPWord(); if (this.getPF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; @@ -549,7 +566,7 @@ X86.opJPw = function JPw() */ X86.opJNPw = function JNPw() { - var disp = this.getIPDispWord(); + var disp = this.getIPWord(); if (!this.getPF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; @@ -567,7 +584,7 @@ X86.opJNPw = function JNPw() */ X86.opJLw = function JLw() { - var disp = this.getIPDispWord(); + var disp = this.getIPWord(); if (!this.getSF() != !this.getOF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; @@ -585,7 +602,7 @@ X86.opJLw = function JLw() */ X86.opJNLw = function JNLw() { - var disp = this.getIPDispWord(); + var disp = this.getIPWord(); if (!this.getSF() == !this.getOF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; @@ -603,7 +620,7 @@ X86.opJNLw = function JNLw() */ X86.opJLEw = function JLEw() { - var disp = this.getIPDispWord(); + var disp = this.getIPWord(); if (this.getZF() || !this.getSF() != !this.getOF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; @@ -621,7 +638,7 @@ X86.opJLEw = function JLEw() */ X86.opJNLEw = function JNLEw() { - var disp = this.getIPDispWord(); + var disp = this.getIPWord(); if (!this.getZF() && !this.getSF() == !this.getOF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; @@ -1301,7 +1318,7 @@ X86.aOps0F[0x06] = X86.opCLTS; /* * On all processors (except the 8086/8088, of course), X86.OPCODE.UD2 (0x0F,0x0B), aka "UD2", is an - * instruction guaranteed to raise a #UD (Invalid Opcode) exception (INT 0x06) on all future x86 processors. + * instruction guaranteed to raise a #UD (Invalid Opcode) exception (INT 0x06) on all post-8086 processors. */ X86.aOps0F[0x0B] = X86.opInvalid; diff --git a/modules/pcjs/lib/x86ops.js b/modules/pcjs/lib/x86ops.js index 935f941f8..22b7974cb 100644 --- a/modules/pcjs/lib/x86ops.js +++ b/modules/pcjs/lib/x86ops.js @@ -3497,6 +3497,10 @@ X86.opESC = function ESC() /** * op=0xE0 (LOOPNZ disp) * + * NOTE: All the instructions in this group (LOOPNZ, LOOPZ, LOOP, and JCXZ) actually + * rely on the ADDRESS override setting for determining whether CX or ECX will be used, + * even though it seems counter-intuitive; ditto for the REP prefix. + * * @this {X86CPU} */ X86.opLOOPNZ = function LOOPNZ() @@ -3513,6 +3517,10 @@ X86.opLOOPNZ = function LOOPNZ() /** * op=0xE1 (LOOPZ disp) * + * NOTE: All the instructions in this group (LOOPNZ, LOOPZ, LOOP, and JCXZ) actually + * rely on the ADDRESS override setting for determining whether CX or ECX will be used, + * even though it seems counter-intuitive; ditto for the REP prefix. + * * @this {X86CPU} */ X86.opLOOPZ = function LOOPZ() @@ -3529,6 +3537,10 @@ X86.opLOOPZ = function LOOPZ() /** * op=0xE2 (LOOP disp) * + * NOTE: All the instructions in this group (LOOPNZ, LOOPZ, LOOP, and JCXZ) actually + * rely on the ADDRESS override setting for determining whether CX or ECX will be used, + * even though it seems counter-intuitive; ditto for the REP prefix. + * * @this {X86CPU} */ X86.opLOOP = function LOOP() @@ -3543,7 +3555,11 @@ X86.opLOOP = function LOOP() }; /** - * op=0xE3 (JCXZ disp) + * op=0xE3 (JCXZ/JECXZ disp) + * + * NOTE: All the instructions in this group (LOOPNZ, LOOPZ, LOOP, and JCXZ) actually + * rely on the ADDRESS override setting for determining whether CX or ECX will be used, + * even though it seems counter-intuitive; ditto for the REP prefix. * * @this {X86CPU} */ diff --git a/modules/pcjs/lib/x86seg.js b/modules/pcjs/lib/x86seg.js index 00f7596ce..122e8131c 100644 --- a/modules/pcjs/lib/x86seg.js +++ b/modules/pcjs/lib/x86seg.js @@ -588,7 +588,7 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fSuppress) if (I386 && cpu.model >= X86.MODEL_80386) { base |= (ext & X86.DESC.EXT.BASE2431) << 16; limit |= (ext & X86.DESC.EXT.LIMIT1619) << 16; - if (ext & X86.DESC.EXT.GRANULARITY) limit = (limit << 12) | 0xfff; + if (ext & X86.DESC.EXT.LIMITPAGES) limit = (limit << 12) | 0xfff; } while (true) { diff --git a/tests/pc/80386/makefile b/tests/pc/80386/makefile index 6f89190d2..ef68f7ba8 100644 --- a/tests/pc/80386/makefile +++ b/tests/pc/80386/makefile @@ -1,7 +1,7 @@ all: tests.json -tests.rom: tests.nasm - nasm -f bin tests.nasm -l tests.lst -o tests.rom +tests.com: tests.nasm + nasm -f bin tests.nasm -l tests.lst -o tests.com -tests.json: tests.rom - node ../../../modules/filedump/bin/filedump --file=tests.rom --output=tests.json --overwrite +tests.json: tests.com + node ../../../modules/filedump/bin/filedump --file=tests.com --output=tests.json --overwrite diff --git a/tests/pc/80386/tests.nasm b/tests/pc/80386/tests.nasm index 654d9bba2..b2d1107a4 100644 --- a/tests/pc/80386/tests.nasm +++ b/tests/pc/80386/tests.nasm @@ -1,21 +1,85 @@ cpu 386 - - org 0x0000 +; +; This file is designed to run as a ROM replacement, but it has a .COM extension because it's +; also designed to run as a COM file under DOS (hence the "org 0x100"). +; + org 0x100 section .text bits 16 +DPL0 equ 0 +DPL1 equ 1 +DPL2 equ 2 +DPL3 equ 3 + +ACC_TYPE_SEG equ 0x1000 +ACC_TYPE_CODE equ 0x0800 +ACC_TYPE_READABLE equ 0x0200 +ACC_TYPE_WRITABLE equ 0x0200 +ACC_TYPE_CODE_READABLE equ 0x1a00 +ACC_TYPE_DATA_WRITABLE equ 0x1200 + +EXT_BIG equ 0x0040 + +SEG_CODE_REAL equ 0xf000 +SEG_CODE_PROT equ 0x0008 +SEG_DATA_PROT equ 0x0010 + +CR0_MSW_PE equ 0x0001 + +; +; descDT defines a descriptor, given a base (%1), limit (%2), type (%3), dpl (%4), and bigness (%5) +; +%macro defDesc 1-5 0,0,0,0 + dw (%2 & 0x0000ffff) + dw (%1 & 0x0000ffff) + dw ((%1 & 0x00ff0000) >> 16) | %3 | (%4 << 13) + dw ((%2 & 0x000f0000) >> 16) | %5 | ((%1 & 0xff000000) >> 16) +%endmacro + + start: mov eax,0x44332211 mov ecx,0x88776655 mul ecx div ecx + jnz near goProt - call dword 0xf000:start + times 32768 nop - times 0xfff0-($-$$) nop +romGDT: defDesc 0 + defDesc 0x000f0000,0x0000ffff,ACC_TYPE_CODE_READABLE,0 + defDesc 0x00000000,0x000fffff,ACC_TYPE_DATA_WRITABLE,0 + +goProt: lgdt [cs:romGDT] + mov eax,cr0 + or eax,CR0_MSW_PE + mov cr0,eax + jmp dword SEG_CODE_PROT:inProt + +inProt: mov ax,SEG_DATA_PROT + mov ds,ax +; +; Do some protected-mode tests... +; + +goReal: mov eax,cr0 + and eax,~CR0_MSW_PE + mov cr0,eax + jmp dword SEG_CODE_REAL:inReal + +inReal: or eax,1 + jnz start + + ; + ; Fill the remaining space with NOPs until we get to target offset 0xFFF0. + ; + ; Note that we subtract 0x100 from the target offset because we're ORG'ed at 0x100. + ; + times 0xfff0-0x100-($-$$) nop bits 16 - jmp 0xf000:start + jmp SEG_CODE_REAL:start db 0x20 db '04/04/15'