From b0ada4229d9dbaafcd56ac762c2729719ab53766 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Sun, 18 Oct 2015 17:40:29 -0700 Subject: [PATCH] Make far calls restartable when they trigger a fault pushing the return address onto the stack --- modules/pcjs/lib/x86cpu.js | 1 + modules/pcjs/lib/x86func.js | 25 +++++++------ tests/pc/80386/test386.nasm | 70 +++++++++++++++++++++++++++---------- tests/pc/inc/x86.inc | 3 +- 4 files changed, 68 insertions(+), 31 deletions(-) diff --git a/modules/pcjs/lib/x86cpu.js b/modules/pcjs/lib/x86cpu.js index ea6018efa..a14a00533 100644 --- a/modules/pcjs/lib/x86cpu.js +++ b/modules/pcjs/lib/x86cpu.js @@ -1397,6 +1397,7 @@ X86CPU.prototype.resetRegs = function() * currently opLIP is updated prior to every instruction, but opLSP is updated only for instructions * that read/write the stack (eg, RETF) and should otherwise remain set to X86.ADDR_INVALID. */ + this.opCS = -1; this.opLIP = this.opLSP = X86.ADDR_INVALID; /* diff --git a/modules/pcjs/lib/x86func.js b/modules/pcjs/lib/x86func.js index d2fedc544..348682074 100644 --- a/modules/pcjs/lib/x86func.js +++ b/modules/pcjs/lib/x86func.js @@ -558,21 +558,20 @@ X86.fnCALLw = function(dst, src) X86.fnCALLF = function(off, sel) { /* - * Originally, we would snapshot regLSP into opLSP because setCSIP() could trigger a segment fault, - * but additionally, the stack segment could trigger either a segment fault or a page fault; indeed, - * any operation that performs multiple stack modifications must take this precaution and snapshot regLSP. + * Since we always push the return address AFTER calling setCSIP(), and since either push could trigger + * fault (eg, segment fault, page fault, etc), we must not only snapshot regLSP into opLSP, but also the + * current CS into opCS, so that fnFault() can make this CALL restartable. */ + this.opCS = this.getCS(); this.opLSP = this.regLSP; - - var oldCS = this.getCS(); var oldIP = this.getIP(); var oldSize = (I386? this.sizeData : 2); if (this.setCSIP(off, sel, true) != null) { - this.pushData(oldCS, oldSize); + this.pushData(this.opCS, oldSize); this.pushData(oldIP, oldSize); } - this.opLSP = X86.ADDR_INVALID; + this.opCS = -1; }; /** @@ -768,7 +767,7 @@ X86.fnShr64 = function(dst) * * This sets regMDLo to dstHi:dstLo / src, and regMDHi to dstHi:dstLo % src; all inputs are treated as unsigned. * - * If fMDset is not set, however, then there was a divide exception (ie, the divisor was either zero or too small). + * If fMDSet is not set, however, then there was a divide exception (ie, the divisor was either zero or too small). * * Refer to: http://lxr.linux.no/linux+v2.6.22/lib/div64.c * @@ -799,8 +798,8 @@ X86.fnDIV32 = function(dstLo, dstHi, src) result += bit; } X86.fnShr64(div); - bit >>>= 1; - } while (bit); + bit /= 2; + } while (bit >= 1); this.assert(result <= 0xffffffff && !rem[1]); @@ -814,7 +813,7 @@ X86.fnDIV32 = function(dstLo, dstHi, src) * * This sets regMDLo to dstHi:dstLo / src, and regMDHi to dstHi:dstLo % src; all inputs are treated as signed. * - * If fMDset is not set, however, then there was a divide exception (ie, the divisor was either zero or too small). + * If fMDSet is not set, however, then there was a divide exception (ie, the divisor was either zero or too small). * * Refer to: http://lxr.linux.no/linux+v2.6.22/lib/div64.c * @@ -3980,6 +3979,10 @@ X86.fnFault = function(nFault, nError, nCycles, fHalt) * the current instruction contains an OPERAND size override). */ this.resetSizes(); + if (this.opCS != -1) { + this.setCS(this.opCS); + this.opCS = -1; + } this.setIP(this.opLIP - this.segCS.base); if (this.opLSP != X86.ADDR_INVALID) { this.setSP((this.regESP & ~this.segSS.maskAddr) | (this.opLSP - this.segSS.base)); diff --git a/tests/pc/80386/test386.nasm b/tests/pc/80386/test386.nasm index 5c1618938..792c7022e 100644 --- a/tests/pc/80386/test386.nasm +++ b/tests/pc/80386/test386.nasm @@ -107,6 +107,9 @@ DSEG_PROT16 equ 0x0018 DSEG_PROT32 equ 0x0020 SSEG_PROT32 equ 0x0028 +; +; We set our exception handlers at fixed addresses to simplify interrupt gate descriptor initialization. +; OFF_INTDIVERR equ 0xe000 ; @@ -223,6 +226,10 @@ addrIDT:dw myIDTEnd - myIDT - 1 ; 16-bit limit of myIDT myIDT: defGate CSEG_PROT32,OFF_INTDIVERR myIDTEnd: +addrIDTReal: + dw 0x3FF ; 16-bit limit of real-mode IDT + dd 0x00000000 ; 32-bit base address of real-mode IDT + initGDT: %ifdef RAM_GDT set edi,RAM_GDT @@ -746,7 +753,8 @@ printVal: TYPE_ARITH equ 0 TYPE_ARITH1 equ 1 TYPE_LOGIC equ 2 -TYPE_MULDIV equ 3 +TYPE_MULTIPLY equ 3 +TYPE_DIVIDE equ 4 SIZE_BYTE equ 0 SIZE_SHORT equ 1 @@ -816,18 +824,32 @@ tableOps: defOp "DEC",dec,al,none,none,TYPE_ARITH1 defOp "DEC",dec,ax,none,none,TYPE_ARITH1 defOp "DEC",dec,eax,none,none,TYPE_ARITH1 - defOp "IMULA",imul,dl,none,none,TYPE_MULDIV - defOp "IMULA",imul,dx,none,none,TYPE_MULDIV - defOp "IMULA",imul,edx,none,none,TYPE_MULDIV - defOp "IMUL",imul,ax,dx,none,TYPE_MULDIV - defOp "IMUL",imul,eax,edx,none,TYPE_MULDIV - defOp "IMUL8",imul,ax,dx,0x77,TYPE_ARITH1 - defOp "IMUL8",imul,ax,dx,-0x77,TYPE_ARITH1 - defOp "IMUL8",imul,eax,edx,0x77,TYPE_ARITH1 - defOp "IMUL8",imul,eax,edx,-0x77,TYPE_ARITH1 - defOp "IMUL16",imul,ax,0x777,none,TYPE_ARITH1 - defOp "IMUL32",imul,eax,0x777777,none,TYPE_ARITH1 - defOp "IDIVA",idiv,dl,none,none,TYPE_MULDIV + defOp "MULA",mul,dl,none,none,TYPE_MULTIPLY + defOp "MULA",mul,dx,none,none,TYPE_MULTIPLY + defOp "MULA",mul,edx,none,none,TYPE_MULTIPLY + defOp "IMULA",imul,dl,none,none,TYPE_MULTIPLY + defOp "IMULA",imul,dx,none,none,TYPE_MULTIPLY + defOp "IMULA",imul,edx,none,none,TYPE_MULTIPLY + defOp "IMUL",imul,ax,dx,none,TYPE_MULTIPLY + defOp "IMUL",imul,eax,edx,none,TYPE_MULTIPLY + defOp "IMUL8",imul,ax,dx,0x77,TYPE_MULTIPLY + defOp "IMUL8",imul,ax,dx,-0x77,TYPE_MULTIPLY + defOp "IMUL8",imul,eax,edx,0x77,TYPE_MULTIPLY + defOp "IMUL8",imul,eax,edx,-0x77,TYPE_MULTIPLY + defOp "IMUL16",imul,ax,0x777,none,TYPE_MULTIPLY + defOp "IMUL32",imul,eax,0x777777,none,TYPE_MULTIPLY + defOp "DIVDL",div,dl,none,none,TYPE_DIVIDE + defOp "DIVDX",div,dx,none,none,TYPE_DIVIDE + defOp "DIVEDX",div,edx,none,none,TYPE_DIVIDE + defOp "DIVAL",div,al,none,none,TYPE_DIVIDE + defOp "DIVAX",div,ax,none,none,TYPE_DIVIDE + defOp "DIVEAX",div,eax,none,none,TYPE_DIVIDE + defOp "IDIVDL",idiv,dl,none,none,TYPE_DIVIDE + defOp "IDIVDX",idiv,dx,none,none,TYPE_DIVIDE + defOp "IDIVEDX",idiv,edx,none,none,TYPE_DIVIDE + defOp "IDIVAL",idiv,al,none,none,TYPE_DIVIDE + defOp "IDIVAX",idiv,ax,none,none,TYPE_DIVIDE + defOp "IDIVEAX",idiv,eax,none,none,TYPE_DIVIDE db 0 align 4 @@ -836,7 +858,8 @@ typeMasks: dd PS_ARITH dd PS_ARITH dd PS_LOGIC - dd PS_MULDIV + dd PS_MULTIPLY + dd PS_DIVIDE arithValues: .bvals: dd 0x00,0x01,0x02,0x7E,0x7F,0x80,0x81,0xFE,0xFF @@ -881,7 +904,14 @@ typeValues: dd ARITH_BYTES+ARITH_WORDS+ARITH_DWORDS,arithValues,ARITH_BYTES+ARITH_WORDS+ARITH_DWORDS,arithValues dd 0,0,0,0 ; - ; Values for TYPE_MULDIV (a superset of ARITH values) + ; Values for TYPE_MULTIPLY (a superset of ARITH values) + ; + dd MULDIV_BYTES,muldivValues,MULDIV_BYTES,muldivValues + dd MULDIV_BYTES+MULDIV_WORDS,muldivValues,MULDIV_BYTES+MULDIV_WORDS,muldivValues + dd MULDIV_BYTES+MULDIV_WORDS+MULDIV_DWORDS,muldivValues,MULDIV_BYTES+MULDIV_WORDS+MULDIV_DWORDS,muldivValues + dd 0,0,0,0 + ; + ; Values for TYPE_DIVIDE ; dd MULDIV_BYTES,muldivValues,MULDIV_BYTES,muldivValues dd MULDIV_BYTES+MULDIV_WORDS,muldivValues,MULDIV_BYTES+MULDIV_WORDS,muldivValues @@ -899,8 +929,8 @@ intDivErr: pop esi ; ; It's rather annoying that the 80386 treats #DE as a fault rather than a trap, leaving CS:EIP pointing to the -; faulting instruction. So we must "patch" the EIP on the stack to point to a RET; it's easier to use our own RET -; rather than figuring out how long the DIV instruction is. +; faulting instruction instead of the RET we conveniently placed after it. So, instead of trying to calculate where +; that RET is, we simply set EIP on the stack to point to our own RET. ; mov dword [esp],intDivRet iretd @@ -914,14 +944,16 @@ doneProt: %ifndef REAL32 ; -; Return to real-mode now, after first loading CS with a 16-bit code segment +; Return to real-mode, after first resetting the IDTR and loading CS with a 16-bit code segment ; + o32 lidt [cs:addrIDTReal] jmp CSEG_PROT16:toProt16 toProt16: bits 16 %endif -goReal: mov eax,cr0 +goReal: + mov eax,cr0 and eax,~(CR0_MSW_PE | CR0_PG) & 0xffffffff mov cr0,eax jmpReal: diff --git a/tests/pc/inc/x86.inc b/tests/pc/inc/x86.inc index c397d15ce..b747bd58d 100644 --- a/tests/pc/inc/x86.inc +++ b/tests/pc/inc/x86.inc @@ -12,7 +12,8 @@ PS_DF equ 0x0400 PS_OF equ 0x0800 PS_ARITH equ (PS_CF | PS_PF | PS_AF | PS_ZF | PS_SF | PS_OF) PS_LOGIC equ (PS_CF | PS_PF | PS_ZF | PS_SF | PS_OF) -PS_MULDIV equ (PS_CF | PS_OF) +PS_MULTIPLY equ (PS_CF | PS_OF) ; only CF and OF are "defined" following MUL or IMUL +PS_DIVIDE equ 0 ; none of the Processor Status flags are "defined" following DIV or IDIV CR0_MSW_PE equ 0x0001 CR0_PG equ 0x80000000 ; set if paging enabled