diff --git a/modules/pcjs/lib/memory.js b/modules/pcjs/lib/memory.js index c62c51137..29f30dd44 100644 --- a/modules/pcjs/lib/memory.js +++ b/modules/pcjs/lib/memory.js @@ -593,7 +593,7 @@ Memory.prototype = { * @return {number} */ readShortDefault: function readShortDefault(off, addr) { - return this.readByteDirect(off, addr) | (this.readByteDirect(off + 1, addr) << 8); + return this.readByte(off, addr) | (this.readByte(off + 1, addr) << 8); }, /** * readLongDefault(off, addr) @@ -604,7 +604,7 @@ Memory.prototype = { * @return {number} */ readLongDefault: function readLongDefault(off, addr) { - return this.readByteDirect(off, addr) | (this.readByteDirect(off + 1, addr) << 8) | (this.readByteDirect(off + 2, addr) << 16) | (this.readByteDirect(off + 3, addr) << 24); + return this.readByte(off, addr) | (this.readByte(off + 1, addr) << 8) | (this.readByte(off + 2, addr) << 16) | (this.readByte(off + 3, addr) << 24); }, /** * writeShortDefault(off, w, addr) @@ -616,8 +616,8 @@ Memory.prototype = { */ writeShortDefault: function writeShortDefault(off, w, addr) { Component.assert(!(w & ~0xffff)); - this.writeByteDirect(off, w & 0xff); - this.writeByteDirect(off + 1, w >> 8); + this.writeByte(off, w & 0xff); + this.writeByte(off + 1, w >> 8); }, /** * writeLongDefault(off, w, addr) @@ -628,10 +628,10 @@ Memory.prototype = { * @param {number} addr */ writeLongDefault: function writeLongDefault(off, w, addr) { - this.writeByteDirect(off, w & 0xff); - this.writeByteDirect(off + 1, (w >> 8) & 0xff); - this.writeByteDirect(off + 2, (w >> 16) & 0xff); - this.writeByteDirect(off + 3, (w >>> 24)); + this.writeByte(off, w & 0xff); + this.writeByte(off + 1, (w >> 8) & 0xff); + this.writeByte(off + 2, (w >> 16) & 0xff); + this.writeByte(off + 3, (w >>> 24)); }, /** * readByteMemory(off, addr) diff --git a/modules/pcjs/lib/x86func.js b/modules/pcjs/lib/x86func.js index 62485067b..ce4468127 100644 --- a/modules/pcjs/lib/x86func.js +++ b/modules/pcjs/lib/x86func.js @@ -1445,9 +1445,13 @@ X86.fnLFS = function LFS(dst, src) * * op=0x0F,0x01,reg=0x2 (GRP7:LGDT) * - * The 80286 LGDT instruction expects a 40-bit operand: a 16-bit limit, followed by a 24-bit address - * (or a 32-bit address in 32-bit mode); the ModRM decoder has already supplied the first word of the - * operand (in dst), which corresponds to the limit, so we must fetch the remaining bits ourselves. + * The 80286 LGDT instruction assumes a 40-bit operand: a 16-bit limit followed by a 24-bit base address; + * the ModRM decoder has already supplied the first word of the operand (in dst), which corresponds to + * the limit, so we must fetch the remaining bits ourselves. + * + * The 80386 LGDT instruction assumes a 48-bit operand: a 16-bit limit followed by a 32-bit base address, + * but it ignores the last 8 bits of the base address if the OPERAND size is 16 bits; we interpret that to + * mean that the 24-bit base address should be zero-extended to 32 bits. * * @this {X86CPU} * @param {number} dst @@ -1460,10 +1464,15 @@ X86.fnLGDT = function LGDT(dst, src) X86.opInvalid.call(this); } else { /* - * It shouldn't hurt to always fetch 32 bits of physical memory, which we'll then - * mask with either a 24-bit or a 32-bit mask. + * Hopefully it won't hurt to always fetch a 32-bit base address (even on an 80286), which we then + * mask apppropriately. */ this.addrGDT = this.getLong(this.regEA + 2) & (this.dataMask | (this.dataMask << 8)); + /* + * An idiosyncrasy of our ModRM decoders is that, if the OPERAND size is 32 bits, then it will have + * fetched a 32-bit dst operand; we mask off those extra bits now. + */ + dst &= 0xffff; this.addrGDTLimit = this.addrGDT + dst; this.opFlags |= X86.OPFLAG.NOWRITE; this.nStepCycles -= 11; @@ -1495,9 +1504,13 @@ X86.fnLGS = function LGS(dst, src) * * op=0x0F,0x01,reg=0x3 (GRP7:LIDT) * - * The 80286 LIDT instruction expects a 40-bit operand: a 16-bit limit, followed by a 24-bit address - * (or a 32-bit address in 32-bit mode); the ModRM decoder has already supplied the first word of the - * operand (in dst), which corresponds to the limit, so we must fetch the remaining bits ourselves. + * The 80286 LIDT instruction assumes a 40-bit operand: a 16-bit limit followed by a 24-bit base address; + * the ModRM decoder has already supplied the first word of the operand (in dst), which corresponds to + * the limit, so we must fetch the remaining bits ourselves. + * + * The 80386 LIDT instruction assumes a 48-bit operand: a 16-bit limit followed by a 32-bit base address, + * but it ignores the last 8 bits of the base address if the OPERAND size is 16 bits; we interpret that to + * mean that the 24-bit base address should be zero-extended to 32 bits. * * @this {X86CPU} * @param {number} dst @@ -1510,10 +1523,15 @@ X86.fnLIDT = function LIDT(dst, src) X86.opInvalid.call(this); } else { /* - * It shouldn't hurt to always fetch 32 bits of physical memory, which we'll then - * mask with either a 24-bit or a 32-bit mask. + * Hopefully it won't hurt to always fetch a 32-bit base address (even on an 80286), which we then + * mask apppropriately. */ this.addrIDT = this.getLong(this.regEA + 2) & (this.dataMask | (this.dataMask << 8)); + /* + * An idiosyncrasy of our ModRM decoders is that, if the OPERAND size is 32 bits, then it will have + * fetched a 32-bit dst operand; we mask off those extra bits now. + */ + dst &= 0xffff; this.addrIDTLimit = this.addrIDT + dst; this.opFlags |= X86.OPFLAG.NOWRITE; this.nStepCycles -= 12; diff --git a/modules/pcjs/lib/x86ops.js b/modules/pcjs/lib/x86ops.js index cabaae027..c8a44902c 100644 --- a/modules/pcjs/lib/x86ops.js +++ b/modules/pcjs/lib/x86ops.js @@ -2098,6 +2098,10 @@ X86.opMOVwsr = function MOVwsr() } /* * Like other MOV operations, the destination does not need to be read, just written. + * + * TODO: Confirm this instruction's behavior on the 80386; ie, if a 32-bit OPERAND size is + * in effect, does it still write only 16 bits? If so, we must add a setDataSize(2) override. + * Confirm for both register and memory destinations. */ this.opFlags |= X86.OPFLAG.NOREAD; this.aOpModMemWord[bModRM].call(this, X86.fnMOVxx); diff --git a/modules/pcjs/lib/x86seg.js b/modules/pcjs/lib/x86seg.js index 52e771e9d..74f2eaf8e 100644 --- a/modules/pcjs/lib/x86seg.js +++ b/modules/pcjs/lib/x86seg.js @@ -157,12 +157,18 @@ X86Seg.prototype.loadProt = function loadProt(sel, fSuppress) */ sel &= 0xffff; + /* + * When comparing descriptor addresses, we must be mindful that any addresses above 2Gb will be negative; + * that in itself is not a problem UNLESS the descriptor table straddles the 2Gb boundary, meaning the + * starting address is positive but the ending (limit) address is negative. Although that situation is + * highly unlikely, the safest thing to do is coerce the bounding addresses to unsigned values, using ">>> 0." + */ if (!(sel & X86.SEL.LDT)) { - addrDT = cpu.addrGDT; - addrDTLimit = cpu.addrGDTLimit; + addrDT = cpu.addrGDT >>> 0; + addrDTLimit = cpu.addrGDTLimit >>> 0; } else { - addrDT = cpu.segLDT.base; - addrDTLimit = addrDT + cpu.segLDT.limit; + addrDT = cpu.segLDT.base >>> 0; + addrDTLimit = addrDT + cpu.segLDT.limit; // segment limit properties are already coerced unsigned } /* * The ROM BIOS POST executes some test code in protected-mode without properly initializing the LDT, diff --git a/tests/pc/80386/tests.nasm b/tests/pc/80386/tests.nasm index 92736a376..1bbd837c5 100644 --- a/tests/pc/80386/tests.nasm +++ b/tests/pc/80386/tests.nasm @@ -1,6 +1,15 @@ ; -; 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"). +; This file is designed to run both as a test ROM and as a DOS COM file (hence the "org 0x100"), +; which is why it has a ".com" extension instead of the more typical ".rom" extension. +; +; When used as a ROM, it should be installed at physical address 983296 (0xf0100) and aliased at +; physical address 4294902016 (0xffff0100). The jump at jmpStart should align with the CPU reset +; address (%0xfffffff0), which will transfer control to 0xf000:0x0100. +; +; The code which attempts to update myGDT and addrGDT will have no effect when installed as a ROM, +; which is fine, because those data structures are predefined with appropriate ROM-based addresses. +; +; See the machine definition file in /modules/pcjs/bin/romtests.json for a sample ROM configuration. ; cpu 386 org 0x100 @@ -8,38 +17,35 @@ bits 16 -DPL0 equ 0 -DPL1 equ 1 -DPL2 equ 2 -DPL3 equ 3 +ACC_TYPE_SEG equ 0x1000 +ACC_PRESENT equ 0x8000 +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 -ACC_TYPE_SEG equ 0x1000 -ACC_PRESENT equ 0x8000 -ACC_TYPE_CODE equ 0x0800 -ACC_TYPE_READABLE equ 0x0200 -ACC_TYPE_WRITABLE equ 0x0200 -ACC_TYPE_CODE_READABLE equ (0x1a00 | ACC_PRESENT) -ACC_TYPE_DATA_WRITABLE equ (0x1200 | ACC_PRESENT) - -EXT_NONE equ 0x0000 -EXT_BIG equ 0x0040 +EXT_NONE equ 0x0000 +EXT_BIG equ 0x0040 ; -; We build some data structures in the first page (0x0000-0x0fff) of RAM: +; If we built our data structures in RAM, we might use the first page of RAM (0x0000-0x0fff) like so: ; ; 0x0000-0x03ff Real-mode IDT (256*4) ; 0x0400-0x0bff Prot-mode IDT (256*8) -; 0x0c00-0x0cff GDT (enough room for 32 selectors) -; 0x0d00-0x0d07 IDTR -; 0x0d08-0x0d0f GDTR -; 0x0d10-0x0fff reserved +; 0x0c00-0x0cff RAM_GDT (for 32 GDT selectors) +; 0x0d00-0x0d07 RAM_IDTR +; 0x0d08-0x0d0f RAM_GDTR +; 0x0d10-0x0d13 RAM_RETF (Real-mode return address) +; 0x0d14-0x0fff reserved ; -; And in the second page (0x1000-0x1fff), we build a page directory, followed by a single page table that -; will allow us to map up to 4Mb (although we'll only create entries for the first 1Mb). +; And in the second page (0x1000-0x1fff), we might build a page directory, followed by a single page table that +; allows us to map up to 4Mb (although we'd likely only create PTEs for the first 1Mb). ; -RAM_GDT equ 0x0c00 -RAM_IDTR equ 0x0d00 -RAM_GDTR equ 0x0d08 +;RAM_GDT equ 0x0c00 +;RAM_IDTR equ 0x0d00 +;RAM_GDTR equ 0x0d08 +;RAM_RETF equ 0x0d10 CSEG_REAL equ 0xf000 CSEG_PROT equ 0x0008 @@ -48,7 +54,7 @@ DSEG_PROT equ 0x0010 CR0_MSW_PE equ 0x0001 ; -; set initializes a register to the specified value (eg, "set eax,0") +; The "set" macro initializes a register to the specified value (eg, "set eax,0") ; %macro set 2 %ifnum %2 @@ -63,49 +69,45 @@ CR0_MSW_PE equ 0x0001 %endmacro ; -; defDesc defines a descriptor, given a base (%1), limit (%2), type (%3), dpl (%4), and ext (%5) -; -%macro defDesc 1-5 0,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 - -; -; setDesc creates a descriptor, given a base (%1), limit (%2), type (%3), ext (%4), and selector (%5) +; The "defDesc" macro defines a descriptor, given a name (%1), base (%2), limit (%3), type (%4), and ext (%5) ; %assign selDesc 0 -%macro setDesc 1-5 0,0,0,0,none - set ebx,%1 - set ecx,%2 - set dx,%3 - set ax,%4 - call storeDesc - %assign %5 selDesc + +%macro defDesc 1-5 none,0,0,0,0 + %assign %1 selDesc + dw (%3 & 0x0000ffff) + dw (%2 & 0x0000ffff) + %if selDesc = 0 + dw ((%2 & 0x00ff0000) >> 16) | %4 | (0 << 13) + %else + dw ((%2 & 0x00ff0000) >> 16) | %4 | (0 << 13) | ACC_PRESENT + %endif + dw ((%3 & 0x000f0000) >> 16) | %5 | ((%2 & 0xff000000) >> 16) %assign selDesc selDesc+8 %endmacro -start: cli ; disable all interrupts - mov al,0xff ; and ensure that no hardware interrupts can sneak in - out 0x21,al ; if interrupts become enabled later - out 0xa1,al - - sub ax,ax - mov ds,ax - mov es,ax - mov ss,ax - mov sp,0x1000 +; +; The "setDesc" macro creates a descriptor, given a name (%1), base (%2), limit (%3), type (%4), and ext (%5) +; +%macro setDesc 1-5 none,0,0,0,0 + %assign %1 selDesc + set ebx,%2 + set ecx,%3 + set dx,%4 + set ax,%5 + call storeDesc + %assign selDesc selDesc+8 +%endmacro +start: nop mov eax,0x44332211 mov ebx,eax mov ecx,0x88776655 mul ecx div ecx cmp eax,ebx - je near initRAM ; apparently we have to tell NASM "near" because this is a forward reference + je near initGDT ; apparently we have to tell NASM "near" because this is a forward reference times 32768 nop ; lots of NOPs to force a 16-bit conditional jump - ; ; storeDesc(EBX=base, ECX=limit, DX=type, AX=ext, DI=address of descriptor) ; @@ -119,6 +121,7 @@ storeDesc: mov ax,dx shr ebx,16 mov al,bl + or ax,ACC_PRESENT stosw pop ax shr ecx,16 @@ -128,56 +131,78 @@ storeDesc: stosw ret -; -; The following ROM-based data structures are obsolete, because we build these data structures in RAM now. -; -addrGDT:dw romGDTEnd - romGDT - 1 ; 16-bit limit of romGDT - dw romGDT, 0xffff ; 32-bit base address of romGDT (works as long as we're aliased at 0xffff0000) +addrGDT:dw myGDTEnd - myGDT - 1 ; 16-bit limit of myGDT + dw myGDT, 0xffff ; 32-bit base address of myGDT (works as long as we're aliased at 0xffff0000) -romGDT: defDesc 0 ; the first descriptor in any descriptor table is always a dud (it corresponds to the null descriptor) - defDesc 0x000f0000,0x0000ffff,ACC_TYPE_CODE_READABLE - defDesc 0x00000000,0x000fffff,ACC_TYPE_DATA_WRITABLE -romGDTEnd: ; -; End of ROM-based data structures +; TODO: Why do I need to provide a 2nd parameter for "defDesc NULL"? Is this a NASM 0.98.x bug? ; +myGDT: defDesc NULL,0 ; the first descriptor in any descriptor table is always a dud (it corresponds to the null selector) + defDesc CSEG_PROT,0x000f0000,0x0000ffff,ACC_TYPE_CODE_READABLE,EXT_NONE + defDesc DSEG_PROT,0x00000000,0x000fffff,ACC_TYPE_DATA_WRITABLE,EXT_NONE +myGDTEnd: -initRAM: +initGDT: + %ifdef RAM_GDT set edi,RAM_GDT mov [RAM_GDTR+2],edi - setDesc 0,0,0,0,NULL - mov eax,cs + setDesc NULL + xor eax,eax + mov ax,cs shl eax,4 - setDesc eax,0x0000ffff,ACC_TYPE_CODE_READABLE,EXT_BIG,CSEG_PROT - setDesc 0x0,0x000fffff,ACC_TYPE_DATA_WRITABLE,EXT_BIG,DSEG_PROT + setDesc CSEG_PROT,eax,0x0000ffff,ACC_TYPE_CODE_READABLE,EXT_NONE + setDesc DSEG_PROT,0x0,0x000fffff,ACC_TYPE_DATA_WRITABLE,EXT_NONE sub edi,RAM_GDT dec edi mov [RAM_GDTR],di + mov word [RAM_RETF],toReal + mov word [RAM_RETF+2],cs + %else + ; + ; This code will have no effect if we're in ROM (in that case, myGDT et al should already be set correctly) + ; + xor eax,eax + mov ax,cs + shl eax,4 ; EAX = base address of the current CS + mov edx,eax ; save it in EDX + mov [cs:myGDT+CSEG_PROT+2],ax ; update the base portions of the descriptor for CSEG_PROT + shr eax,16 + mov [cs:myGDT+CSEG_PROT+4],al + mov [cs:myGDT+CSEG_PROT+7],ah + mov eax,edx ; recover the base address of the current CS + add eax,myGDT ; EAX = physical address of myGDT + mov [cs:addrGDT+2],eax ; update the 32-bit base address of myGDT in addrGDT + mov [cs:jmpReal+3],cs ; update the segment of the far jmp that returns us to real-mode + mov [cs:jmpStart+3],cs ; ditto for the far jmp that returns us to the start of the image + %endif -goProt: o32 lgdt [RAM_GDTR] +goProt: o32 lgdt [cs:addrGDT] mov eax,cr0 or eax,CR0_MSW_PE mov cr0,eax - jmp CSEG_PROT:inProt -inProt: - bits 32 + nop +jmpProt: + jmp CSEG_PROT:toProt + +toProt: ; bits 32 ; only if we define the CSEG_PROT descriptor with EXT_BIG mov ax,DSEG_PROT mov ds,ax mov es,ax - mov ss,ax - ; ; Do some protected-mode tests now... ; - goReal: mov eax,cr0 and eax,~CR0_MSW_PE mov cr0,eax - bits 16 - jmp CSEG_REAL:inReal -inReal: - or ax,1 - jnz start ; apparently we do NOT have to say "near" here since this is a backward reference + nop +jmpReal: + jmp CSEG_REAL:toReal + +toReal: ; bits 16 ; only if we define the CSEG_PROT descriptor with EXT_BIG + mov ax,cs + cmp ax,CSEG_REAL ; is CS equal to 0xf000? + je near jmpStart ; yes + int 0x20 ; no, so assume we're running under DOS and exit ; ; Fill the remaining space with NOPs until we get to target offset 0xFFF0. @@ -185,9 +210,10 @@ inReal: ; times 0xfff0-0x100-($-$$) nop +jmpStart: jmp CSEG_REAL:start db 0x20 db '04/04/15' - db 0xFC ; 0000FFFE FC (Model ID byte) - db 0x00 ; 0000FFFF 00 (location of checksum byte) + db 0xFC ; 0000FFFE FC (Model ID byte) + db 0x00 ; 0000FFFF 00 (location of checksum byte)