/** * @fileoverview Implements PCx86 8086 opcode decoding. * @author Jeff Parsons * @copyright © Jeff Parsons 2012-2017 * * This file is part of PCjs, a computer emulation software project at . * * PCjs is free software: you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with PCjs. If not, * see . * * You are required to include the above copyright notice in every modified copy of this work * and to display that copyright notice when the software starts running; see COPYRIGHT in * . * * Some PCjs files also attempt to load external resource files, such as character-image files, * ROM files, and disk image files. Those external resource files are not considered part of PCjs * for purposes of the GNU General Public License, and the author does not claim any copyright * as to their contents. */ "use strict"; if (NODE) { var Str = require("../../shared/lib/strlib"); var Messages = require("./messages"); var X86 = require("./x86"); } /** * op=0x00 (ADD byte,reg) * * @this {X86CPU} */ X86.opADDmb = function() { this.decodeModMemByte.call(this, X86.fnADDb); /* * Opcode bytes 0x00 0x00 are sufficiently uncommon that it's more likely we've started * executing in the weeds, so if you're in DEBUG mode, we'll print a warning and stop the * CPU if a Debugger is available. * * Notice that we also test fRunning: this allows the Debugger to step over the instruction, * because its trace ("t") command doesn't "run" the CPU; it merely "steps" the CPU. */ if (DEBUG && !this.bModRM && this.flags.running) { this.printMessage("suspicious opcode: 0x00 0x00", DEBUGGER || this.bitsMessage); if (DEBUGGER && this.dbg) this.dbg.stopCPU(); } }; /** * op=0x01 (ADD word,reg) * * @this {X86CPU} */ X86.opADDmw = function() { this.decodeModMemWord.call(this, X86.fnADDw); }; /** * op=0x02 (ADD reg,byte) * * @this {X86CPU} */ X86.opADDrb = function() { this.decodeModRegByte.call(this, X86.fnADDb); }; /** * op=0x03 (ADD reg,word) * * @this {X86CPU} */ X86.opADDrw = function() { this.decodeModRegWord.call(this, X86.fnADDw); }; /** * op=0x04 (ADD AL,imm8) * * @this {X86CPU} */ X86.opADDALb = function() { this.regEAX = (this.regEAX & ~0xff) | X86.fnADDb.call(this, this.regEAX & 0xff, this.getIPByte()); /* * NOTE: Whenever the result is "blended" value (eg, of btiAL and btiMem0), a new bti should be * allocated to reflect that fact; however, I'm leaving "perfect" BACKTRACK support for another day. */ if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiMem0; this.nStepCycles--; // in the absence of any EA calculations, we need deduct only one more cycle }; /** * op=0x05 (ADD AX,imm16 or ADD EAX,imm32) * * @this {X86CPU} */ X86.opADDAX = function() { this.regEAX = (this.regEAX & ~this.maskData) | X86.fnADDw.call(this, this.regEAX & this.maskData, this.getIPWord()); if (BACKTRACK) { this.backTrack.btiAL = this.backTrack.btiMem0; this.backTrack.btiAH = this.backTrack.btiMem1; } this.nStepCycles--; // in the absence of any EA calculations, we need deduct only one more cycle }; /** * op=0x06 (PUSH ES) * * @this {X86CPU} */ X86.opPUSHES = function() { /* * When the OPERAND size is 32 bits, the 80386 will decrement the stack pointer by 4, write the selector * into the 2 lower bytes, and leave the 2 upper bytes untouched; to properly emulate that, we must use the * more generic pushData() instead of pushWord(). */ if (!I386) { this.pushWord(this.segES.sel); } else { this.pushData(this.segES.sel, this.sizeData, 2); } this.nStepCycles -= this.cycleCounts.nOpCyclesPushSeg; }; /** * op=0x07 (POP ES) * * @this {X86CPU} */ X86.opPOPES = function() { /* * Any operation that modifies the stack before loading a new segment must snapshot regLSP first. */ this.opLSP = this.regLSP; this.setES(this.popWord()); this.nStepCycles -= this.cycleCounts.nOpCyclesPopReg; this.opLSP = X86.ADDR_INVALID; }; /** * op=0x08 (OR byte,reg) * * @this {X86CPU} */ X86.opORmb = function() { this.decodeModMemByte.call(this, X86.fnORb); }; /** * op=0x09 (OR word,reg) * * @this {X86CPU} */ X86.opORmw = function() { this.decodeModMemWord.call(this, X86.fnORw); }; /** * op=0x0A (OR reg,byte) * * @this {X86CPU} */ X86.opORrb = function() { this.decodeModRegByte.call(this, X86.fnORb); }; /** * op=0x0B (OR reg,word) * * @this {X86CPU} */ X86.opORrw = function() { this.decodeModRegWord.call(this, X86.fnORw); }; /** * op=0x0C (OR AL,imm8) * * @this {X86CPU} */ X86.opORALb = function() { this.regEAX = (this.regEAX & ~0xff) | X86.fnORb.call(this, this.regEAX & 0xff, this.getIPByte()); if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiMem0; this.nStepCycles--; // in the absence of any EA calculations, we need deduct only one more cycle }; /** * op=0x0D (OR AX,imm16 or OR EAX,imm32) * * @this {X86CPU} */ X86.opORAX = function() { this.regEAX = (this.regEAX & ~this.maskData) | X86.fnORw.call(this, this.regEAX & this.maskData, this.getIPWord()); if (BACKTRACK) { this.backTrack.btiAL = this.backTrack.btiMem0; this.backTrack.btiAH = this.backTrack.btiMem1; } this.nStepCycles--; // in the absence of any EA calculations, we need deduct only one more cycle }; /** * op=0x0E (PUSH CS) * * @this {X86CPU} */ X86.opPUSHCS = function() { /* * When the OPERAND size is 32 bits, the 80386 will decrement the stack pointer by 4, write the selector * into the 2 lower bytes, and leave the 2 upper bytes untouched; to properly emulate that, we must use the * more generic pushData() instead of pushWord(). */ if (!I386) { this.pushWord(this.segCS.sel); } else { this.pushData(this.segCS.sel, this.sizeData, 2); } this.nStepCycles -= this.cycleCounts.nOpCyclesPushSeg; }; /** * op=0x0F (POP CS) (undocumented on 8086/8088; replaced with opInvalid() on 80186/80188, and op0F() on 80286 and up) * * @this {X86CPU} */ X86.opPOPCS = function() { /* * Because this is an 8088-only operation, we don't have to worry about taking a snapshot of regLSP first. */ this.setCS(this.popWord()); this.nStepCycles -= this.cycleCounts.nOpCyclesPopReg; }; /** * op=0x0F (handler for two-byte opcodes; 80286 and up) * * @this {X86CPU} */ X86.op0F = function() { this.aOps0F[this.getIPByte()].call(this); }; /** * op=0x10 (ADC byte,reg) * * @this {X86CPU} */ X86.opADCmb = function() { this.decodeModMemByte.call(this, X86.fnADCb); }; /** * op=0x11 (ADC word,reg) * * @this {X86CPU} */ X86.opADCmw = function() { this.decodeModMemWord.call(this, X86.fnADCw); }; /** * op=0x12 (ADC reg,byte) * * @this {X86CPU} */ X86.opADCrb = function() { this.decodeModRegByte.call(this, X86.fnADCb); }; /** * op=0x13 (ADC reg,word) * * @this {X86CPU} */ X86.opADCrw = function() { this.decodeModRegWord.call(this, X86.fnADCw); }; /** * op=0x14 (ADC AL,imm8) * * @this {X86CPU} */ X86.opADCALb = function() { this.regEAX = (this.regEAX & ~0xff) | X86.fnADCb.call(this, this.regEAX & 0xff, this.getIPByte()); if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiMem0; this.nStepCycles--; // in the absence of any EA calculations, we need deduct only one more cycle }; /** * op=0x15 (ADC AX,imm16 or ADC EAX,imm32) * * @this {X86CPU} */ X86.opADCAX = function() { this.regEAX = (this.regEAX & ~this.maskData) | X86.fnADCw.call(this, this.regEAX & this.maskData, this.getIPWord()); if (BACKTRACK) { this.backTrack.btiAL = this.backTrack.btiMem0; this.backTrack.btiAH = this.backTrack.btiMem1; } this.nStepCycles--; // in the absence of any EA calculations, we need deduct only one more cycle }; /** * op=0x16 (PUSH SS) * * @this {X86CPU} */ X86.opPUSHSS = function() { /* * When the OPERAND size is 32 bits, the 80386 will decrement the stack pointer by 4, write the selector * into the 2 lower bytes, and leave the 2 upper bytes untouched; to properly emulate that, we must use the * more generic pushData() instead of pushWord(). */ if (!I386) { this.pushWord(this.segSS.sel); } else { this.pushData(this.segSS.sel, this.sizeData, 2); } this.nStepCycles -= this.cycleCounts.nOpCyclesPushSeg; }; /** * op=0x17 (POP SS) * * @this {X86CPU} */ X86.opPOPSS = function() { /* * Any operation that modifies the stack before loading a new segment must snapshot regLSP first. */ this.opLSP = this.regLSP; this.setSS(this.popWord()); this.nStepCycles -= this.cycleCounts.nOpCyclesPopReg; this.opLSP = X86.ADDR_INVALID; }; /** * op=0x18 (SBB byte,reg) * * @this {X86CPU} */ X86.opSBBmb = function() { this.decodeModMemByte.call(this, X86.fnSBBb); }; /** * op=0x19 (SBB word,reg) * * @this {X86CPU} */ X86.opSBBmw = function() { this.decodeModMemWord.call(this, X86.fnSBBw); }; /** * op=0x1A (SBB reg,byte) * * @this {X86CPU} */ X86.opSBBrb = function() { this.decodeModRegByte.call(this, X86.fnSBBb); }; /** * op=0x1B (SBB reg,word) * * @this {X86CPU} */ X86.opSBBrw = function() { this.decodeModRegWord.call(this, X86.fnSBBw); }; /** * op=0x1C (SBB AL,imm8) * * @this {X86CPU} */ X86.opSBBALb = function() { this.regEAX = (this.regEAX & ~0xff) | X86.fnSBBb.call(this, this.regEAX & 0xff, this.getIPByte()); if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiMem0; this.nStepCycles--; // in the absence of any EA calculations, we need deduct only one more cycle }; /** * op=0x1D (SBB AX,imm16 or SBB EAX,imm32) * * @this {X86CPU} */ X86.opSBBAX = function() { this.regEAX = (this.regEAX & ~this.maskData) | X86.fnSBBw.call(this, this.regEAX & this.maskData, this.getIPWord()); if (BACKTRACK) { this.backTrack.btiAL = this.backTrack.btiMem0; this.backTrack.btiAH = this.backTrack.btiMem1; } this.nStepCycles--; // in the absence of any EA calculations, we need deduct only one more cycle }; /** * op=0x1E (PUSH DS) * * @this {X86CPU} */ X86.opPUSHDS = function() { /* * When the OPERAND size is 32 bits, the 80386 will decrement the stack pointer by 4, write the selector * into the 2 lower bytes, and leave the 2 upper bytes untouched; to properly emulate that, we must use the * more generic pushData() instead of pushWord(). */ if (!I386) { this.pushWord(this.segDS.sel); } else { this.pushData(this.segDS.sel, this.sizeData, 2); } this.nStepCycles -= this.cycleCounts.nOpCyclesPushSeg; }; /** * op=0x1F (POP DS) * * @this {X86CPU} */ X86.opPOPDS = function() { /* * Any operation that modifies the stack before loading a new segment must snapshot regLSP first. */ this.opLSP = this.regLSP; this.setDS(this.popWord()); this.nStepCycles -= this.cycleCounts.nOpCyclesPopReg; this.opLSP = X86.ADDR_INVALID; }; /** * op=0x20 (AND byte,reg) * * @this {X86CPU} */ X86.opANDmb = function() { this.decodeModMemByte.call(this, X86.fnANDb); }; /** * op=0x21 (AND word,reg) * * @this {X86CPU} */ X86.opANDmw = function() { this.decodeModMemWord.call(this, X86.fnANDw); }; /** * op=0x22 (AND reg,byte) * * @this {X86CPU} */ X86.opANDrb = function() { this.decodeModRegByte.call(this, X86.fnANDb); }; /** * op=0x23 (AND reg,word) * * @this {X86CPU} */ X86.opANDrw = function() { this.decodeModRegWord.call(this, X86.fnANDw); }; /** * op=0x24 (AND AL,imm8) * * @this {X86CPU} */ X86.opANDAL = function() { this.regEAX = (this.regEAX & ~0xff) | X86.fnANDb.call(this, this.regEAX & 0xff, this.getIPByte()); if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiMem0; this.nStepCycles--; // in the absence of any EA calculations, we need deduct only one more cycle }; /** * op=0x25 (AND AX,imm16 or AND EAX,imm32) * * @this {X86CPU} */ X86.opANDAX = function() { this.regEAX = (this.regEAX & ~this.maskData) | X86.fnANDw.call(this, this.regEAX & this.maskData, this.getIPWord()); if (BACKTRACK) { this.backTrack.btiAL = this.backTrack.btiMem0; this.backTrack.btiAH = this.backTrack.btiMem1; } this.nStepCycles--; // in the absence of any EA calculations, we need deduct only one more cycle }; /** * op=0x26 (ES:) * * @this {X86CPU} */ X86.opES = function() { this.opFlags |= X86.OPFLAG.SEG | X86.OPFLAG.NOINTR; this.segData = this.segStack = this.segES; this.nStepCycles -= this.cycleCounts.nOpCyclesPrefix; }; /** * op=0x27 (DAA) * * @this {X86CPU} */ X86.opDAA = function() { var AL = this.regEAX & 0xff; var AF = this.getAF(); var CF = this.getCF(); if ((AL & 0xf) > 9 || AF) { AL += 0x6; AF = X86.PS.AF; } else { AF = 0; } if (AL > 0x9f || CF) { AL += 0x60; CF = X86.PS.CF; } else { CF = 0; } var b = (AL & 0xff); this.regEAX = (this.regEAX & ~0xff) | b; this.setLogicResult(b, X86.RESULT.BYTE); if (CF) this.setCF(); else this.clearCF(); if (AF) this.setAF(); else this.clearAF(); this.nStepCycles -= this.cycleCounts.nOpCyclesAAA; // AAA and DAA have the same cycle times }; /** * op=0x28 (SUB byte,reg) * * @this {X86CPU} */ X86.opSUBmb = function() { this.decodeModMemByte.call(this, X86.fnSUBb); }; /** * op=0x29 (SUB word,reg) * * @this {X86CPU} */ X86.opSUBmw = function() { this.decodeModMemWord.call(this, X86.fnSUBw); }; /** * op=0x2A (SUB reg,byte) * * @this {X86CPU} */ X86.opSUBrb = function() { this.decodeModRegByte.call(this, X86.fnSUBb); }; /** * op=0x2B (SUB reg,word) * * @this {X86CPU} */ X86.opSUBrw = function() { this.decodeModRegWord.call(this, X86.fnSUBw); }; /** * op=0x2C (SUB AL,imm8) * * @this {X86CPU} */ X86.opSUBALb = function() { this.regEAX = (this.regEAX & ~0xff) | X86.fnSUBb.call(this, this.regEAX & 0xff, this.getIPByte()); if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiMem0; this.nStepCycles--; // in the absence of any EA calculations, we need deduct only one more cycle }; /** * op=0x2D (SUB AX,imm16 or SUB EAX,imm32) * * @this {X86CPU} */ X86.opSUBAX = function() { this.regEAX = (this.regEAX & ~this.maskData) | X86.fnSUBw.call(this, this.regEAX & this.maskData, this.getIPWord()); if (BACKTRACK) { this.backTrack.btiAL = this.backTrack.btiMem0; this.backTrack.btiAH = this.backTrack.btiMem1; } this.nStepCycles--; // in the absence of any EA calculations, we need deduct only one more cycle }; /** * op=0x2E (CS:) * * @this {X86CPU} */ X86.opCS = function() { this.opFlags |= X86.OPFLAG.SEG | X86.OPFLAG.NOINTR; this.segData = this.segStack = this.segCS; this.nStepCycles -= this.cycleCounts.nOpCyclesPrefix; }; /** * op=0x2F (DAS) * * @this {X86CPU} */ X86.opDAS = function() { var AL = this.regEAX & 0xff; var AF = this.getAF(); var CF = this.getCF(); if ((AL & 0xf) > 9 || AF) { AL -= 0x6; AF = X86.PS.AF; } else { AF = 0; } if (AL > 0x9f || CF) { AL -= 0x60; CF = X86.PS.CF; } else { CF = 0; } var b = (AL & 0xff); this.regEAX = (this.regEAX & ~0xff) | b; this.setLogicResult(b, X86.RESULT.BYTE); if (CF) this.setCF(); else this.clearCF(); if (AF) this.setAF(); else this.clearAF(); this.nStepCycles -= this.cycleCounts.nOpCyclesAAA; // AAA and DAS have the same cycle times }; /** * op=0x30 (XOR byte,reg) * * @this {X86CPU} */ X86.opXORmb = function() { this.decodeModMemByte.call(this, X86.fnXORb); }; /** * op=0x31 (XOR word,reg) * * @this {X86CPU} */ X86.opXORmw = function() { this.decodeModMemWord.call(this, X86.fnXORw); }; /** * op=0x32 (XOR reg,byte) * * @this {X86CPU} */ X86.opXORrb = function() { this.decodeModRegByte.call(this, X86.fnXORb); }; /** * op=0x33 (XOR reg,word) * * @this {X86CPU} */ X86.opXORrw = function() { this.decodeModRegWord.call(this, X86.fnXORw); }; /** * op=0x34 (XOR AL,imm8) * * @this {X86CPU} */ X86.opXORALb = function() { this.regEAX = (this.regEAX & ~0xff) | X86.fnXORb.call(this, this.regEAX & 0xff, this.getIPByte()); if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiMem0; this.nStepCycles--; // in the absence of any EA calculations, we need deduct only one more cycle }; /** * op=0x35 (XOR AX,imm16 or XOR EAX,imm32) * * @this {X86CPU} */ X86.opXORAX = function() { this.regEAX = (this.regEAX & ~this.maskData) | X86.fnXORw.call(this, this.regEAX & this.maskData, this.getIPWord()); if (BACKTRACK) { this.backTrack.btiAL = this.backTrack.btiMem0; this.backTrack.btiAH = this.backTrack.btiMem1; } this.nStepCycles--; // in the absence of any EA calculations, we need deduct only one more cycle }; /** * op=0x36 (SS:) * * @this {X86CPU} */ X86.opSS = function() { this.opFlags |= X86.OPFLAG.SEG | X86.OPFLAG.NOINTR; this.segData = this.segStack = this.segSS; // QUESTION: Is there a case where segStack would not already be segSS? (eg, multiple segment overrides?) this.nStepCycles -= this.cycleCounts.nOpCyclesPrefix; }; /** * op=0x37 (AAA) * * @this {X86CPU} */ X86.opAAA = function() { var CF, AF; var AL = this.regEAX & 0xff; var AH = (this.regEAX >> 8) & 0xff; if ((AL & 0xf) > 9 || this.getAF()) { AL += 6; /* * Simulate the fact that the 80286 and higher add 6 to AX rather than AL. */ if (this.model >= X86.MODEL_80286 && AL > 0xff) AH++; AH++; CF = AF = 1; } else { CF = AF = 0; } this.regEAX = (this.regEAX & ~0xffff) | (((AH << 8) | AL) & 0xff0f); if (CF) this.setCF(); else this.clearCF(); if (AF) this.setAF(); else this.clearAF(); this.nStepCycles -= this.cycleCounts.nOpCyclesAAA; }; /** * op=0x38 (CMP byte,reg) * * @this {X86CPU} */ X86.opCMPmb = function() { this.decodeModMemByte.call(this, X86.fnCMPb); }; /** * op=0x39 (CMP word,reg) * * @this {X86CPU} */ X86.opCMPmw = function() { this.decodeModMemWord.call(this, X86.fnCMPw); }; /** * op=0x3A (CMP reg,byte) * * @this {X86CPU} */ X86.opCMPrb = function() { this.decodeModRegByte.call(this, X86.fnCMPb); }; /** * op=0x3B (CMP reg,word) * * @this {X86CPU} */ X86.opCMPrw = function() { this.decodeModRegWord.call(this, X86.fnCMPw); }; /** * op=0x3C (CMP AL,imm8) * * @this {X86CPU} */ X86.opCMPALb = function() { X86.fnCMPb.call(this, this.regEAX & 0xff, this.getIPByte()); this.nStepCycles--; // in the absence of any EA calculations, we need deduct only one more cycle }; /** * op=0x3D (CMP AX,imm16 or CMP EAX,imm32) * * @this {X86CPU} */ X86.opCMPAX = function() { X86.fnCMPw.call(this, this.regEAX & this.maskData, this.getIPWord()); this.nStepCycles--; // in the absence of any EA calculations, we need deduct only one more cycle }; /** * op=0x3E (DS:) * * @this {X86CPU} */ X86.opDS = function() { this.opFlags |= X86.OPFLAG.SEG | X86.OPFLAG.NOINTR; this.segData = this.segStack = this.segDS; // QUESTION: Is there a case where segData would not already be segDS? (eg, multiple segment overrides?) this.nStepCycles -= this.cycleCounts.nOpCyclesPrefix; }; /** * op=0x3D (AAS) * * @this {X86CPU} */ X86.opAAS = function() { var CF, AF; var AL = this.regEAX & 0xff; var AH = (this.regEAX >> 8) & 0xff; if ((AL & 0xf) > 9 || this.getAF()) { AL = (AL - 0x6) & 0xf; AH = (AH - 1) & 0xff; CF = AF = 1; } else { CF = AF = 0; } this.regEAX = (this.regEAX & ~0xffff) | ((AH << 8) | AL); if (CF) this.setCF(); else this.clearCF(); if (AF) this.setAF(); else this.clearAF(); this.nStepCycles -= this.cycleCounts.nOpCyclesAAA; // AAA and AAS have the same cycle times }; /** * op=0x40 (INC [E]AX) * * @this {X86CPU} */ X86.opINCAX = function() { this.regEAX = X86.helpINCreg.call(this, this.regEAX); }; /** * op=0x41 (INC [E]CX) * * @this {X86CPU} */ X86.opINCCX = function() { this.regECX = X86.helpINCreg.call(this, this.regECX); }; /** * op=0x42 (INC [E]DX) * * @this {X86CPU} */ X86.opINCDX = function() { this.regEDX = X86.helpINCreg.call(this, this.regEDX); }; /** * op=0x43 (INC [E]BX) * * @this {X86CPU} */ X86.opINCBX = function() { this.regEBX = X86.helpINCreg.call(this, this.regEBX); }; /** * op=0x44 (INC [E]SP) * * @this {X86CPU} */ X86.opINCSP = function() { this.setSP(X86.helpINCreg.call(this, this.getSP())); }; /** * op=0x45 (INC [E]BP) * * @this {X86CPU} */ X86.opINCBP = function() { this.regEBP = X86.helpINCreg.call(this, this.regEBP); }; /** * op=0x46 (INC [E]SI) * * @this {X86CPU} */ X86.opINCSI = function() { this.regESI = X86.helpINCreg.call(this, this.regESI); }; /** * op=0x47 (INC [E]DI) * * @this {X86CPU} */ X86.opINCDI = function() { this.regEDI = X86.helpINCreg.call(this, this.regEDI); }; /** * op=0x48 (DEC [E]AX) * * @this {X86CPU} */ X86.opDECAX = function() { this.regEAX = X86.helpDECreg.call(this, this.regEAX); }; /** * op=0x49 (DEC [E]CX) * * @this {X86CPU} */ X86.opDECCX = function() { this.regECX = X86.helpDECreg.call(this, this.regECX); }; /** * op=0x4A (DEC [E]DX) * * @this {X86CPU} */ X86.opDECDX = function() { this.regEDX = X86.helpDECreg.call(this, this.regEDX); }; /** * op=0x4B (DEC [E]BX) * * @this {X86CPU} */ X86.opDECBX = function() { this.regEBX = X86.helpDECreg.call(this, this.regEBX); }; /** * op=0x4C (DEC [E]SP) * * @this {X86CPU} */ X86.opDECSP = function() { this.setSP(X86.helpDECreg.call(this, this.getSP())); }; /** * op=0x4D (DEC [E]BP) * * @this {X86CPU} */ X86.opDECBP = function() { this.regEBP = X86.helpDECreg.call(this, this.regEBP); }; /** * op=0x4E (DEC [E]SI) * * @this {X86CPU} */ X86.opDECSI = function() { this.regESI = X86.helpDECreg.call(this, this.regESI); }; /**` * op=0x4F (DEC [E]DI) * * @this {X86CPU} */ X86.opDECDI = function() { this.regEDI = X86.helpDECreg.call(this, this.regEDI); }; /** * op=0x50 (PUSH [E]AX) * * @this {X86CPU} */ X86.opPUSHAX = function() { if (BACKTRACK) { this.backTrack.btiMem0 = this.backTrack.btiAL; this.backTrack.btiMem1 = this.backTrack.btiAH; } this.pushWord(this.regEAX & this.maskData); this.nStepCycles -= this.cycleCounts.nOpCyclesPushReg; }; /** * op=0x51 (PUSH [E]CX) * * @this {X86CPU} */ X86.opPUSHCX = function() { if (BACKTRACK) { this.backTrack.btiMem0 = this.backTrack.btiCL; this.backTrack.btiMem1 = this.backTrack.btiCH; } this.pushWord(this.regECX & this.maskData); this.nStepCycles -= this.cycleCounts.nOpCyclesPushReg; }; /** * op=0x52 (PUSH [E]DX) * * @this {X86CPU} */ X86.opPUSHDX = function() { if (BACKTRACK) { this.backTrack.btiMem0 = this.backTrack.btiDL; this.backTrack.btiMem1 = this.backTrack.btiDH; } this.pushWord(this.regEDX & this.maskData); this.nStepCycles -= this.cycleCounts.nOpCyclesPushReg; }; /** * op=0x53 (PUSH [E]BX) * * @this {X86CPU} */ X86.opPUSHBX = function() { if (BACKTRACK) { this.backTrack.btiMem0 = this.backTrack.btiBL; this.backTrack.btiMem1 = this.backTrack.btiBH; } this.pushWord(this.regEBX & this.maskData); this.nStepCycles -= this.cycleCounts.nOpCyclesPushReg; }; /** * op=0x54 (PUSH SP) * * NOTE: Having an accurate implementation of "PUSH SP" for the 8086/8088 isn't just a nice idea, it affects real * code. Case in point: early Microsoft C floating-point libraries relied on "PUSH SP" behavior to quickly determine * whether an 8088 (and therefore presumably an 8087) or an 80286 (and presumably an 80287) was being used; eg: * * &0910:1E82 D93E1709 FSTCW WORD [0917] * &0910:1E86 CD3D INT 3D * &0E4E:06D3 50 PUSH AX * &0E4E:06D4 B83DA2 MOV AX,A23D * &0E4E:06D7 EB04 JMP 06DD * &0E4E:06DD 55 PUSH BP * &0E4E:06DE 1E PUSH DS * &0E4E:06DF 56 PUSH SI * &0E4E:06E0 8BEC MOV BP,SP * &0E4E:06E2 C57608 LDS SI,[BP+08] * &0E4E:06E5 4E DEC SI * &0E4E:06E6 4E DEC SI * &0E4E:06E7 897608 MOV [BP+08],SI * &0E4E:06EA 2904 SUB [SI],AX * &0E4E:06EC 53 PUSH BX * &0E4E:06ED 33DB XOR BX,BX * &0E4E:06EF 54 PUSH SP ; beginning of processor check * &0E4E:06F0 58 POP AX * &0E4E:06F1 3BC4 CMP AX,SP * &0E4E:06F3 7528 JNZ 071D ; jump if 8086/8088/80186/80188, no jump if 80286 or later * &0E4E:06F5 8B4001 MOV AX,[BX+SI+01] * &0E4E:06F8 25FB30 AND AX,30FB * &0E4E:06FB 3DD930 CMP AX,30D9 * &0E4E:06FE 7507 JNZ 0707 * &0E4E:0700 8A4002 MOV AL,[BX+SI+02] * &0E4E:0703 3CF0 CMP AL,F0 * &0E4E:0705 7216 JC 071D * &0E4E:0707 8B4001 MOV AX,[BX+SI+01] * &0E4E:070A 25FFFE AND AX,FEFF * &0E4E:070D 3DDBE2 CMP AX,E2DB * &0E4E:0710 740B JZ 071D * &0E4E:0712 8B4001 MOV AX,[BX+SI+01] * &0E4E:0715 3DDFE0 CMP AX,E0DF * &0E4E:0718 7403 JZ 071D * &0E4E:071A C60490 MOV [SI],90 * &0E4E:071D 5B POP BX * &0E4E:071E 5E POP SI * &0E4E:071F 1F POP DS * &0E4E:0720 5D POP BP * &0E4E:0721 58 POP AX * &0E4E:0722 CF IRET * * @this {X86CPU} */ X86.opPUSHSP_8086 = function() { var w = (this.getSP() - 2) & 0xffff; this.pushWord(w); this.nStepCycles -= this.cycleCounts.nOpCyclesPushReg; }; /** * op=0x54 (PUSH [E]SP) * * @this {X86CPU} */ X86.opPUSHSP = function() { this.pushWord(this.getSP() & this.maskData); this.nStepCycles -= this.cycleCounts.nOpCyclesPushReg; }; /** * op=0x55 (PUSH [E]BP) * * @this {X86CPU} */ X86.opPUSHBP = function() { if (BACKTRACK) { this.backTrack.btiMem0 = this.backTrack.btiBPLo; this.backTrack.btiMem1 = this.backTrack.btiBPHi; } this.pushWord(this.regEBP & this.maskData); this.nStepCycles -= this.cycleCounts.nOpCyclesPushReg; }; /** * op=0x56 (PUSH [E]SI) * * @this {X86CPU} */ X86.opPUSHSI = function() { if (BACKTRACK) { this.backTrack.btiMem0 = this.backTrack.btiSILo; this.backTrack.btiMem1 = this.backTrack.btiSIHi; } this.pushWord(this.regESI & this.maskData); this.nStepCycles -= this.cycleCounts.nOpCyclesPushReg; }; /** * op=0x57 (PUSH [E]DI) * * @this {X86CPU} */ X86.opPUSHDI = function() { if (BACKTRACK) { this.backTrack.btiMem0 = this.backTrack.btiDILo; this.backTrack.btiMem1 = this.backTrack.btiDIHi; } this.pushWord(this.regEDI & this.maskData); this.nStepCycles -= this.cycleCounts.nOpCyclesPushReg; }; /** * op=0x58 (POP [E]AX) * * @this {X86CPU} */ X86.opPOPAX = function() { this.regEAX = (this.regEAX & ~this.maskData) | this.popWord(); if (BACKTRACK) { this.backTrack.btiAL = this.backTrack.btiMem0; this.backTrack.btiAH = this.backTrack.btiMem1; } this.nStepCycles -= this.cycleCounts.nOpCyclesPopReg; }; /** * op=0x59 (POP [E]CX) * * @this {X86CPU} */ X86.opPOPCX = function() { this.regECX = (this.regECX & ~this.maskData) | this.popWord(); if (BACKTRACK) { this.backTrack.btiCL = this.backTrack.btiMem0; this.backTrack.btiCH = this.backTrack.btiMem1; } this.nStepCycles -= this.cycleCounts.nOpCyclesPopReg; }; /** * op=0x5A (POP [E]DX) * * @this {X86CPU} */ X86.opPOPDX = function() { this.regEDX = (this.regEDX & ~this.maskData) | this.popWord(); if (BACKTRACK) { this.backTrack.btiDL = this.backTrack.btiMem0; this.backTrack.btiDH = this.backTrack.btiMem1; } this.nStepCycles -= this.cycleCounts.nOpCyclesPopReg; }; /** * op=0x5B (POP [E]BX) * * @this {X86CPU} */ X86.opPOPBX = function() { this.regEBX = (this.regEBX & ~this.maskData) | this.popWord(); if (BACKTRACK) { this.backTrack.btiBL = this.backTrack.btiMem0; this.backTrack.btiBH = this.backTrack.btiMem1; } this.nStepCycles -= this.cycleCounts.nOpCyclesPopReg; }; /** * op=0x5C (POP [E]SP) * * @this {X86CPU} */ X86.opPOPSP = function() { this.setSP((this.getSP() & ~this.maskData) | this.popWord()); this.nStepCycles -= this.cycleCounts.nOpCyclesPopReg; }; /** * op=0x5D (POP [E]BP) * * @this {X86CPU} */ X86.opPOPBP = function() { this.regEBP = (this.regEBP & ~this.maskData) | this.popWord(); if (BACKTRACK) { this.backTrack.btiBPLo = this.backTrack.btiMem0; this.backTrack.btiBPHi = this.backTrack.btiMem1; } this.nStepCycles -= this.cycleCounts.nOpCyclesPopReg; }; /** * op=0x5E (POP [E]SI) * * @this {X86CPU} */ X86.opPOPSI = function() { this.regESI = (this.regESI & ~this.maskData) | this.popWord(); if (BACKTRACK) { this.backTrack.btiSILo = this.backTrack.btiMem0; this.backTrack.btiSIHi = this.backTrack.btiMem1; } this.nStepCycles -= this.cycleCounts.nOpCyclesPopReg; }; /** * op=0x5F (POP [E]DI) * * @this {X86CPU} */ X86.opPOPDI = function() { this.regEDI = (this.regEDI & ~this.maskData) | this.popWord(); if (BACKTRACK) { this.backTrack.btiDILo = this.backTrack.btiMem0; this.backTrack.btiDIHi = this.backTrack.btiMem1; } this.nStepCycles -= this.cycleCounts.nOpCyclesPopReg; }; /** * op=0x60 (PUSHA) (80186/80188 and up) * * @this {X86CPU} */ X86.opPUSHA = function() { /* * Any operation that performs multiple stack modifications must snapshot regLSP first. */ this.opLSP = this.regLSP; /* * TODO: regLSP needs to be pre-bounds-checked against regLSPLimitLow */ var temp = this.getSP() & this.maskData; if (BACKTRACK) { this.backTrack.btiMem0 = this.backTrack.btiAL; this.backTrack.btiMem1 = this.backTrack.btiAH; } this.pushWord(this.regEAX & this.maskData); if (BACKTRACK) { this.backTrack.btiMem0 = this.backTrack.btiCL; this.backTrack.btiMem1 = this.backTrack.btiCH; } this.pushWord(this.regECX & this.maskData); if (BACKTRACK) { this.backTrack.btiMem0 = this.backTrack.btiDL; this.backTrack.btiMem1 = this.backTrack.btiDH; } this.pushWord(this.regEDX & this.maskData); if (BACKTRACK) { this.backTrack.btiMem0 = this.backTrack.btiBL; this.backTrack.btiMem1 = this.backTrack.btiBH; } this.pushWord(this.regEBX & this.maskData); this.pushWord(temp); if (BACKTRACK) { this.backTrack.btiMem0 = this.backTrack.btiBPLo; this.backTrack.btiMem1 = this.backTrack.btiBPHi; } this.pushWord(this.regEBP & this.maskData); if (BACKTRACK) { this.backTrack.btiMem0 = this.backTrack.btiSILo; this.backTrack.btiMem1 = this.backTrack.btiSIHi; } this.pushWord(this.regESI & this.maskData); if (BACKTRACK) { this.backTrack.btiMem0 = this.backTrack.btiDILo; this.backTrack.btiMem1 = this.backTrack.btiDIHi; } this.pushWord(this.regEDI & this.maskData); this.nStepCycles -= this.cycleCounts.nOpCyclesPushAll; this.opLSP = X86.ADDR_INVALID; }; /** * op=0x61 (POPA) (80186/80188 and up) * * @this {X86CPU} */ X86.opPOPA = function() { /* * Any operation that performs multiple stack modifications must snapshot regLSP first. */ this.opLSP = this.regLSP; this.regEDI = (this.regEDI & ~this.maskData) | this.popWord(); if (BACKTRACK) { this.backTrack.btiDILo = this.backTrack.btiMem0; this.backTrack.btiDIHi = this.backTrack.btiMem1; } this.regESI = (this.regESI & ~this.maskData) | this.popWord(); if (BACKTRACK) { this.backTrack.btiSILo = this.backTrack.btiMem0; this.backTrack.btiSIHi = this.backTrack.btiMem1; } this.regEBP = (this.regEBP & ~this.maskData) | this.popWord(); if (BACKTRACK) { this.backTrack.btiBPLo = this.backTrack.btiMem0; this.backTrack.btiBPHi = this.backTrack.btiMem1; } /* * TODO: regLSP needs to be pre-bounds-checked against regLSPLimit at the start */ this.setSP(this.getSP() + this.sizeData); // this.regLSP += (I386? this.sizeData : 2); this.regEBX = (this.regEBX & ~this.maskData) | this.popWord(); if (BACKTRACK) { this.backTrack.btiBL = this.backTrack.btiMem0; this.backTrack.btiBH = this.backTrack.btiMem1; } this.regEDX = (this.regEDX & ~this.maskData) | this.popWord(); if (BACKTRACK) { this.backTrack.btiDL = this.backTrack.btiMem0; this.backTrack.btiDH = this.backTrack.btiMem1; } this.regECX = (this.regECX & ~this.maskData) | this.popWord(); if (BACKTRACK) { this.backTrack.btiCL = this.backTrack.btiMem0; this.backTrack.btiCH = this.backTrack.btiMem1; } this.regEAX = (this.regEAX & ~this.maskData) | this.popWord(); if (BACKTRACK) { this.backTrack.btiAL = this.backTrack.btiMem0; this.backTrack.btiAH = this.backTrack.btiMem1; } this.nStepCycles -= this.cycleCounts.nOpCyclesPopAll; this.opLSP = X86.ADDR_INVALID; }; /** * op=0x62 (BOUND reg,word) (80186/80188 and up) * * @this {X86CPU} */ X86.opBOUND = function() { this.decodeModRegWord.call(this, X86.fnBOUND); }; /** * op=0x63 (ARPL word,reg) (80286 and up) * * @this {X86CPU} */ X86.opARPL = function() { /* * ARPL is one of several protected-mode instructions that are meaningless and not allowed in either real-mode * or V86-mode; others include LAR, LSL, VERR and VERW. More meaningful but potentially harmful protected-mode * instructions that ARE allowed in real-mode but NOT in V86-mode include LIDT, LGDT, LMSW, CLTS, HLT, and * control register MOV instructions. * * ARPL is somewhat more noteworthy because enhanced-mode Windows (going back to at least Windows 3.00, and * possibly even the earliest versions of Windows/386) selected the ARPL opcode as a controlled means of exiting * V86-mode via the UD_FAULT exception. Windows would use the same ARPL for all controlled exits, using different * segment:offset pointers to the ARPL to differentiate them. ARPL was probably chosen because it could trigger * a UD_FAULT with a single byte (0x63); any subsequent address bytes would be irrelevant. * * Which is WHY we must perform the CPU mode tests below rather than in the fnARPL() worker; otherwise we could * generate additional (bogus) faults, based on the address of the first operand. * * TODO: You may have noticed that setProtMode() already swaps out a 0x0F opcode dispatch table for another based * on the mode, because none of the "GRP6" 0x0F opcodes (eg, SLDT, STR, LLDT, LTR, VERR and VERW) are allowed in * real-mode, and it was easy to swap all those handlers in/out with a single update. We've extended that particular * swap to include V86-mode as well, but we might want to consider swapping out more opcode handlers in a similar * fashion, instead of using these in-line mode tests. */ if (!(this.regCR0 & X86.CR0.MSW.PE) || I386 && (this.regPS & X86.PS.VM)) { X86.opInvalid.call(this); return; } this.decodeModMemWord.call(this, X86.fnARPL); }; /** * op=0x64 (FS:) * * @this {X86CPU} */ X86.opFS = function() { this.opFlags |= X86.OPFLAG.SEG | X86.OPFLAG.NOINTR; this.segData = this.segStack = this.segFS; this.nStepCycles -= this.cycleCounts.nOpCyclesPrefix; }; /** * op=0x65 (GS:) * * @this {X86CPU} */ X86.opGS = function() { this.opFlags |= X86.OPFLAG.SEG | X86.OPFLAG.NOINTR; this.segData = this.segStack = this.segGS; this.nStepCycles -= this.cycleCounts.nOpCyclesPrefix; }; /** * op=0x66 (OS:) (80386 and up) * * TODO: Review other effective operand-size criteria, cycle count, etc. * * @this {X86CPU} */ X86.opOS = function() { if (I386) { /* * See opAS() for a discussion of multiple prefixes, which applies equally to both * operand-size and address-size prefixes. * * The simple fix here is to skip the bulk of the operation if the prefix is redundant. */ this.opFlags |= X86.OPFLAG.DATASIZE; if (!(this.opPrefixes & X86.OPFLAG.DATASIZE)) { this.sizeData ^= 0x6; // that which is 2 shall become 4, and vice versa this.maskData ^= (0xffff0000|0); // that which is 0x0000ffff shall become 0xffffffff, and vice versa this.updateDataSize(); } this.nStepCycles -= this.cycleCounts.nOpCyclesPrefix; } }; /** * op=0x67 (AS:) (80386 and up) * * TODO: Review other effective address-size criteria, cycle count, etc. * * @this {X86CPU} */ X86.opAS = function() { if (I386) { /* * Live and learn: multiple address-size prefixes can and do occur on a single instruction, * and contrary to my original assumption that the prefixes act independently, they do not. * During Windows 95 SETUP, the following instruction is executed: * * 06AF:1B4D 67672E CS: * 06AF:1B50 FFA25A1B JMP [BP+SI+1B5A] * * which is in fact: * * 06AF:1B4D 67672E CS: * 06AF:1B50 FFA25A1B0000 JMP [EDX+00001B5A] * * The other interesting question is: why/how did this instruction get encoded that way? * All I can say is, there were no explicit prefixes in the source (BSG.ASM), so we'll chalk * it up to a glitch in MASM. * * The simple fix here is to skip the bulk of the operation if the prefix is redundant. */ this.opFlags |= X86.OPFLAG.ADDRSIZE; if (!(this.opPrefixes & X86.OPFLAG.ADDRSIZE)) { this.sizeAddr ^= 0x06; // that which is 2 shall become 4, and vice versa this.maskAddr ^= (0xffff0000|0); // that which is 0x0000ffff shall become 0xffffffff, and vice versa this.updateAddrSize(); } this.nStepCycles -= this.cycleCounts.nOpCyclesPrefix; } }; /** * op=0x68 (PUSH imm) (80186/80188 and up) * * @this {X86CPU} */ X86.opPUSHn = function() { this.pushWord(this.getIPWord()); this.nStepCycles -= this.cycleCounts.nOpCyclesPushReg; }; /** * op=0x69 (IMUL reg,word,imm) (80186/80188 and up) * * @this {X86CPU} */ X86.opIMULn = function() { this.decodeModRegWord.call(this, X86.fnIMULn); }; /** * op=0x6A (PUSH imm8) (80186/80188 and up) * * @this {X86CPU} */ X86.opPUSH8 = function() { if (BACKTRACK) this.backTrack.btiMem1 = 0; this.pushWord(this.getIPDisp()); this.nStepCycles -= this.cycleCounts.nOpCyclesPushReg; }; /** * op=0x6B (IMUL reg,word,imm8) (80186/80188 and up) * * @this {X86CPU} */ X86.opIMUL8 = function() { this.decodeModRegWord.call(this, X86.fnIMUL8); }; /** * op=0x6C (INSB) (80186/80188 and up) * * NOTE: Segment overrides are ignored for this instruction, so we must use segES instead of segData. * * @this {X86CPU} */ X86.opINSb = function() { var nReps = 1; var nDelta = 0; var maskAddr = this.maskAddr; /* * NOTE: 5 + 4n is the cycle time for the 80286; the 80186/80188 has different values: 14 cycles for * an unrepeated INS, and 8 + 8n for a repeated INS. However, accurate cycle times for the 80186/80188 is * low priority. */ var nCycles = 5; /* * The (normal) REP prefix, if used, is REPNZ (0xf2), but either one works.... */ if (this.opPrefixes & (X86.OPFLAG.REPZ | X86.OPFLAG.REPNZ)) { nReps = this.regECX & maskAddr; nDelta = 1; if (this.opPrefixes & X86.OPFLAG.REPEAT) nCycles = 4; } if (nReps--) { var port = this.regEDX & 0xffff; if (!this.checkIOPM(port, 1, true)) return; var b = this.bus.checkPortInputNotify(port, 1, this.regLIP - nDelta - 1); this.setSOByte(this.segES, this.regEDI & maskAddr, b); /* * helpFault() throws exceptions now, so inline checks of X86.OPFLAG.FAULT should no longer be necessary. * * if (this.opFlags & X86.OPFLAG.FAULT) return; */ if (BACKTRACK) this.backTrack.btiMem0 = this.backTrack.btiIO; this.regEDI = (this.regEDI & ~maskAddr) | ((this.regEDI + ((this.regPS & X86.PS.DF)? -1 : 1)) & maskAddr); this.regECX = (this.regECX & ~maskAddr) | ((this.regECX - nDelta) & maskAddr); this.nStepCycles -= nCycles; if (nReps) { this.resetIP(-2); this.opFlags |= X86.OPFLAG.REPEAT; } } }; /** * op=0x6D (INSW) (80186/80188 and up) * * NOTE: Segment overrides are ignored for this instruction, so we must use segDS instead of segData. * * @this {X86CPU} */ X86.opINSw = function() { var nReps = 1; var nDelta = 0; var maskAddr = this.maskAddr; /* * NOTE: 5 + 4n is the cycle time for the 80286; the 80186/80188 has different values: 14 cycles for * an unrepeated INS, and 8 + 8n for a repeated INS. However, accurate cycle times for the 80186/80188 is * low priority. */ var nCycles = 5; /* * The (normal) REP prefix, if used, is REPNZ (0xf2), but either one works.... */ if (this.opPrefixes & (X86.OPFLAG.REPZ | X86.OPFLAG.REPNZ)) { nReps = this.regECX & maskAddr; nDelta = 1; if (this.opPrefixes & X86.OPFLAG.REPEAT) nCycles = 4; } if (nReps--) { var port = this.regEDX & 0xffff; if (!this.checkIOPM(port, this.sizeData, true)) return; var w = this.bus.checkPortInputNotify(port, this.sizeData, this.regLIP - nDelta - 1); if (BACKTRACK) { this.backTrack.btiMem0 = this.backTrack.btiIO; this.backTrack.btiMem1 = this.backTrack.btiIO; } this.setSOWord(this.segES, this.regEDI & maskAddr, w); /* * helpFault() throws exceptions now, so inline checks of X86.OPFLAG.FAULT should no longer be necessary. * * if (this.opFlags & X86.OPFLAG.FAULT) return; */ this.regEDI = (this.regEDI & ~maskAddr) | ((this.regEDI + ((this.regPS & X86.PS.DF)? -this.sizeData : this.sizeData)) & maskAddr); this.regECX = (this.regECX & ~maskAddr) | ((this.regECX - nDelta) & maskAddr); this.nStepCycles -= nCycles; if (nReps) { this.resetIP(-2); this.opFlags |= X86.OPFLAG.REPEAT; } } }; /** * op=0x6E (OUTSB) (80186/80188 and up) * * NOTE: Segment overrides are ignored for this instruction, so we must use segDS instead of segData. * * @this {X86CPU} */ X86.opOUTSb = function() { var nReps = 1; var nDelta = 0; var maskAddr = this.maskAddr; /* * NOTE: 5 + 4n is the cycle time for the 80286; the 80186/80188 has different values: 14 cycles for * an unrepeated INS, and 8 + 8n for a repeated INS. TODO: Fix this someday. */ var nCycles = 5; /* * The (normal) REP prefix, if used, is REPNZ (0xf2), but either one works.... */ if (this.opPrefixes & (X86.OPFLAG.REPZ | X86.OPFLAG.REPNZ)) { nReps = this.regECX & maskAddr; nDelta = 1; if (this.opPrefixes & X86.OPFLAG.REPEAT) nCycles = 4; } if (nReps--) { var port = this.regEDX & 0xffff; if (!this.checkIOPM(port, 1, false)) return; var b = this.getSOByte(this.segDS, this.regESI & maskAddr); /* * helpFault() throws exceptions now, so inline checks of X86.OPFLAG.FAULT should no longer be necessary. * * if (this.opFlags & X86.OPFLAG.FAULT) return; */ if (BACKTRACK) this.backTrack.btiIO = this.backTrack.btiMem0; this.bus.checkPortOutputNotify(port, 1, b, this.regLIP - nDelta - 1); this.regESI = (this.regESI & ~maskAddr) | ((this.regESI + ((this.regPS & X86.PS.DF)? -1 : 1)) & maskAddr); this.regECX = (this.regECX & ~maskAddr) | ((this.regECX - nDelta) & maskAddr); this.nStepCycles -= nCycles; if (nReps) { this.resetIP(-2); this.opFlags |= X86.OPFLAG.REPEAT; } } }; /** * op=0x6F (OUTSW) (80186/80188 and up) * * NOTE: Segment overrides are ignored for this instruction, so we must use segDS instead of segData. * * @this {X86CPU} */ X86.opOUTSw = function() { var nReps = 1; var nDelta = 0; var maskAddr = this.maskAddr; /* * NOTE: 5 + 4n is the cycle time for the 80286; the 80186/80188 has different values: 14 cycles for * an unrepeated INS, and 8 + 8n for a repeated INS. TODO: Fix this someday. */ var nCycles = 5; /* * The (normal) REP prefix, if used, is REPNZ (0xf2), but either one works.... */ if (this.opPrefixes & (X86.OPFLAG.REPZ | X86.OPFLAG.REPNZ)) { nReps = this.regECX & maskAddr; nDelta = 1; if (this.opPrefixes & X86.OPFLAG.REPEAT) nCycles = 4; } if (nReps--) { var w = this.getSOWord(this.segDS, this.regESI & maskAddr); /* * helpFault() throws exceptions now, so inline checks of X86.OPFLAG.FAULT should no longer be necessary. * * if (this.opFlags & X86.OPFLAG.FAULT) return; */ var port = this.regEDX & 0xffff; if (!this.checkIOPM(port, this.sizeData, false)) return; if (BACKTRACK) { this.backTrack.btiIO = this.backTrack.btiMem0; this.backTrack.btiIO = this.backTrack.btiMem1; } this.bus.checkPortOutputNotify(port, this.sizeData, w, this.regLIP - nDelta - 1); this.regESI = (this.regESI & ~maskAddr) | ((this.regESI + ((this.regPS & X86.PS.DF)? -this.sizeData : this.sizeData)) & maskAddr); this.regECX = (this.regECX & ~maskAddr) | ((this.regECX - nDelta) & maskAddr); this.nStepCycles -= nCycles; if (nReps) { this.resetIP(-2); this.opFlags |= X86.OPFLAG.REPEAT; } } }; /** * op=0x70 (JO disp) * * @this {X86CPU} */ X86.opJO = function() { var disp = this.getIPDisp(); if (this.getOF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; return; } this.nStepCycles -= this.cycleCounts.nOpCyclesJmpCFall; }; /** * op=0x71 (JNO disp) * * @this {X86CPU} */ X86.opJNO = function() { var disp = this.getIPDisp(); if (!this.getOF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; return; } this.nStepCycles -= this.cycleCounts.nOpCyclesJmpCFall; }; /** * op=0x72 (JC disp, aka JB disp) * * @this {X86CPU} */ X86.opJC = function() { var disp = this.getIPDisp(); if (this.getCF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; return; } this.nStepCycles -= this.cycleCounts.nOpCyclesJmpCFall; }; /** * op=0x73 (JNC disp, aka JAE disp) * * @this {X86CPU} */ X86.opJNC = function() { var disp = this.getIPDisp(); if (!this.getCF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; return; } this.nStepCycles -= this.cycleCounts.nOpCyclesJmpCFall; }; /** * op=0x74 (JZ disp) * * @this {X86CPU} */ X86.opJZ = function() { var disp = this.getIPDisp(); if (this.getZF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; return; } this.nStepCycles -= this.cycleCounts.nOpCyclesJmpCFall; }; /** * op=0x75 (JNZ disp) * * @this {X86CPU} */ X86.opJNZ = function() { var disp = this.getIPDisp(); if (!this.getZF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; return; } this.nStepCycles -= this.cycleCounts.nOpCyclesJmpCFall; }; /** * op=0x76 (JBE disp) * * @this {X86CPU} */ X86.opJBE = function() { var disp = this.getIPDisp(); if (this.getCF() || this.getZF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; return; } this.nStepCycles -= this.cycleCounts.nOpCyclesJmpCFall; }; /** * op=0x77 (JNBE disp, JA disp) * * @this {X86CPU} */ X86.opJNBE = function() { var disp = this.getIPDisp(); if (!this.getCF() && !this.getZF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; return; } this.nStepCycles -= this.cycleCounts.nOpCyclesJmpCFall; }; /** * op=0x78 (JS disp) * * @this {X86CPU} */ X86.opJS = function() { var disp = this.getIPDisp(); if (this.getSF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; return; } this.nStepCycles -= this.cycleCounts.nOpCyclesJmpCFall; }; /** * op=0x79 (JNS disp) * * @this {X86CPU} */ X86.opJNS = function() { var disp = this.getIPDisp(); if (!this.getSF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; return; } this.nStepCycles -= this.cycleCounts.nOpCyclesJmpCFall; }; /** * op=0x7A (JP disp) * * @this {X86CPU} */ X86.opJP = function() { var disp = this.getIPDisp(); if (this.getPF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; return; } this.nStepCycles -= this.cycleCounts.nOpCyclesJmpCFall; }; /** * op=0x7B (JNP disp) * * @this {X86CPU} */ X86.opJNP = function() { var disp = this.getIPDisp(); if (!this.getPF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; return; } this.nStepCycles -= this.cycleCounts.nOpCyclesJmpCFall; }; /** * op=0x7C (JL disp) * * @this {X86CPU} */ X86.opJL = function() { var disp = this.getIPDisp(); if (!this.getSF() != !this.getOF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; return; } this.nStepCycles -= this.cycleCounts.nOpCyclesJmpCFall; }; /** * op=0x7D (JNL disp, aka JGE disp) * * @this {X86CPU} */ X86.opJNL = function() { var disp = this.getIPDisp(); if (!this.getSF() == !this.getOF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; return; } this.nStepCycles -= this.cycleCounts.nOpCyclesJmpCFall; }; /** * op=0x7E (JLE disp) * * @this {X86CPU} */ X86.opJLE = function() { var disp = this.getIPDisp(); if (this.getZF() || !this.getSF() != !this.getOF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; return; } this.nStepCycles -= this.cycleCounts.nOpCyclesJmpCFall; }; /** * op=0x7F (JNLE disp, aka JG disp) * * @this {X86CPU} */ X86.opJNLE = function() { var disp = this.getIPDisp(); if (!this.getZF() && !this.getSF() == !this.getOF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpC; return; } this.nStepCycles -= this.cycleCounts.nOpCyclesJmpCFall; }; /** * op=0x80/0x82 (GRP1 byte,imm8) * * @this {X86CPU} */ X86.opGRP1b = function() { this.decodeModGrpByte.call(this, X86.aOpGrp1b, this.getIPByte); this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? 1 : this.cycleCounts.nOpCyclesArithMID); }; /** * op=0x81 (GRP1 word,imm) * * @this {X86CPU} */ X86.opGRP1w = function() { this.decodeModGrpWord.call(this, X86.aOpGrp1w, this.getIPWord); this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? 1 : this.cycleCounts.nOpCyclesArithMID); }; /** * op=0x83 (GRP1 word,disp) * * WARNING: This passes getIPDisp() as the fnSrc parameter, which returns a 32-bit signed value, * so the worker functions (ie, the functions listed in aOpGrp1w[]) MUST mask their result with maskData, * to avoid setting bits beyond the current operand size. * * @this {X86CPU} */ X86.opGRP1sw = function() { this.decodeModGrpWord.call(this, X86.aOpGrp1w, this.getIPDisp); this.nStepCycles -= (this.regEAWrite === X86.ADDR_INVALID? 1 : this.cycleCounts.nOpCyclesArithMID); }; /** * op=0x84 (TEST reg,byte) * * @this {X86CPU} */ X86.opTESTrb = function() { this.decodeModMemByte.call(this, X86.fnTESTb); }; /** * op=0x85 (TEST reg,word) * * @this {X86CPU} */ X86.opTESTrw = function() { this.decodeModMemWord.call(this, X86.fnTESTw); }; /** * op=0x86 (XCHG reg,byte) * * NOTE: The XCHG instruction is unique in that both src and dst are both read and written; * see fnXCHGrb() for how we deal with this special case. * * @this {X86CPU} */ X86.opXCHGrb = function() { /* * If the second operand is a register, then the ModRegByte decoder must use separate "get" and * "set" assignments, otherwise instructions like "XCHG DH,DL" will end up using a stale DL instead of * the updated DL. * * To be clear, a single assignment like this will fail: * * opModRegByteF2: function(fn) * { * this.regEDX = (this.regEDX & 0xff) | (fn.call(this, this.regEDX >> 8, this.regEDX & 0xff) << 8); * } * * which is why all affected decoders now use separate assignments; eg: * * opModRegByteF2: function(fn) * { * var b = fn.call(this, this.regEDX >> 8, this.regEDX & 0xff); * this.regEDX = (this.regEDX & 0xff) | (b << 8); * } */ this.decodeModRegByte.call(this, X86.fnXCHGrb); }; /** * op=0x87 (XCHG reg,word) * * NOTE: The XCHG instruction is unique in that both src and dst are both read and written; * see fnXCHGrw() for how we deal with this special case. * * @this {X86CPU} */ X86.opXCHGrw = function() { this.decodeModRegWord.call(this, X86.fnXCHGrw); }; /** * op=0x88 (MOV byte,reg) * * @this {X86CPU} */ X86.opMOVmb = function() { /* * Like other MOV operations, the destination does not need to be read, just written. */ this.opFlags |= X86.OPFLAG.NOREAD; this.decodeModMemByte.call(this, X86.fnMOV); }; /** * op=0x89 (MOV word,reg) * * @this {X86CPU} */ X86.opMOVmw = function() { /* * Like other MOV operations, the destination does not need to be read, just written. */ this.opFlags |= X86.OPFLAG.NOREAD; this.decodeModMemWord.call(this, X86.fnMOV); }; /** * op=0x8A (MOV reg,byte) * * @this {X86CPU} */ X86.opMOVrb = function() { this.decodeModRegByte.call(this, X86.fnMOV); }; /** * op=0x8B (MOV reg,word) * * @this {X86CPU} */ X86.opMOVrw = function() { this.decodeModRegWord.call(this, X86.fnMOV); }; /** * op=0x8C (MOV word,sreg) * * NOTE: Since the ModRM decoders deal only with general-purpose registers, we rely on our helper * function (fnMOVwsr) to select the appropriate segment register and replace the decoder's src operand. * * @this {X86CPU} */ X86.opMOVwsr = function() { /* * Like other MOV operations, the destination does not need to be read, just written. */ this.opFlags |= X86.OPFLAG.NOREAD; this.decodeModMemWord.call(this, X86.fnMOVwsr); }; /** * op=0x8D (LEA reg,word) * * @this {X86CPU} */ X86.opLEA = function() { this.opFlags |= X86.OPFLAG.NOREAD; this.segData = this.segStack = this.segNULL; // we can't have the EA calculation, if any, "polluted" by segment arithmetic this.decodeModRegWord.call(this, X86.fnLEA); }; /** * op=0x8E (MOV sreg,word) * * NOTE: Since the ModRM decoders deal only with general-purpose registers, we rely on our * helper function (fnMOVsrw) to make a note of which general-purpose register will be overwritten, * so that we can restore it after moving the updated value to the correct segment register. * * @this {X86CPU} */ X86.opMOVsrw = function() { var sel; this.decodeModRegWord.call(this, X86.fnMOVsrw); switch ((this.bModRM >> 3) & 0x7) { case 0x0: sel = this.regEAX; this.regEAX = this.regXX; this.setES(sel); break; case 0x1: sel = this.regECX; this.regECX = this.regXX; this.setCS(sel); break; case 0x2: sel = this.regEDX; this.regEDX = this.regXX; this.setSS(sel); break; case 0x3: sel = this.regEBX; this.regEBX = this.regXX; this.setDS(sel); break; case 0x4: sel = this.getSP(); this.setSP(this.regXX); if (I386 && this.model >= X86.MODEL_80386) { this.setFS(sel); } else { this.setES(sel); } break; case 0x5: sel = this.regEBP; this.regEBP = this.regXX; if (I386 && this.model >= X86.MODEL_80386) { this.setGS(sel); } else { this.setCS(sel); } break; case 0x6: sel = this.regESI; this.regESI = this.regXX; this.setSS(sel); break; case 0x7: sel = this.regEDI; this.regEDI = this.regXX; this.setDS(sel); break; } }; /** * op=0x8F (POP word) * * @this {X86CPU} */ X86.opPOPmw = function() { /* * Like other MOV operations, the destination does not need to be read, just written. */ this.opFlags |= X86.OPFLAG.NOREAD; /* * If the word we're about to pop FROM the stack gets popped INTO a not-present page, this * instruction will not be restartable unless we snapshot regLSP first. */ this.opLSP = this.regLSP; /* * A "clever" instruction like this: * * #0117:651C 67668F442408 POP DWORD [ESP+08] * * pops the DWORD from the top of the stack and places it at ESP+08, where ESP is the value * AFTER the pop, not before. We used to (incorrectly) pass "popWord" as the fnSrc parameter * below; we now pop the word first, saving it in regXX, and then pass "helpSRCxx" as fnSrc, * which simply returns the contents of regXX. * * Also, in case you're wondering, fnPUSHw() (in aOpGrp4w) is the complement to this instruction, * but it doesn't require a similar work-around, because a push from memory accesses that memory * BEFORE the push, which occurs through our normal ModRM processing. */ this.regXX = this.popWord(); this.decodeModGrpWord.call(this, X86.aOpGrpPOPw, X86.helpSRCxx); this.opLSP = X86.ADDR_INVALID; }; /** * op=0x90 (NOP, aka XCHG AX,AX) * * @this {X86CPU} */ X86.opNOP = function() { this.nStepCycles -= 3; // this form of XCHG takes 3 cycles on all CPUs }; /** * op=0x91 (XCHG AX,CX) * * @this {X86CPU} */ X86.opXCHGCX = function() { var temp = this.regEAX; this.regEAX = (I386? (this.regEAX & ~this.maskData) | (this.regECX & this.maskData) : this.regECX); this.regECX = (I386? (this.regECX & ~this.maskData) | (temp & this.maskData) : temp); if (BACKTRACK) { temp = this.backTrack.btiAL; this.backTrack.btiAL = this.backTrack.btiCL; this.backTrack.btiCL = temp; temp = this.backTrack.btiAH; this.backTrack.btiAH = this.backTrack.btiCH; this.backTrack.btiCH = temp; } this.nStepCycles -= 3; // this form of XCHG takes 3 cycles on all CPUs }; /** * op=0x92 (XCHG AX,DX) * * @this {X86CPU} */ X86.opXCHGDX = function() { var temp = this.regEAX; this.regEAX = (I386? (this.regEAX & ~this.maskData) | (this.regEDX & this.maskData) : this.regEDX); this.regEDX = (I386? (this.regEDX & ~this.maskData) | (temp & this.maskData) : temp); if (BACKTRACK) { temp = this.backTrack.btiAL; this.backTrack.btiAL = this.backTrack.btiDL; this.backTrack.btiDL = temp; temp = this.backTrack.btiAH; this.backTrack.btiAH = this.backTrack.btiDH; this.backTrack.btiDH = temp; } this.nStepCycles -= 3; // this form of XCHG takes 3 cycles on all CPUs }; /** * op=0x93 (XCHG AX,BX) * * @this {X86CPU} */ X86.opXCHGBX = function() { var temp = this.regEAX; this.regEAX = (I386? (this.regEAX & ~this.maskData) | (this.regEBX & this.maskData) : this.regEBX); this.regEBX = (I386? (this.regEBX & ~this.maskData) | (temp & this.maskData) : temp); if (BACKTRACK) { temp = this.backTrack.btiAL; this.backTrack.btiAL = this.backTrack.btiBL; this.backTrack.btiBL = temp; temp = this.backTrack.btiAH; this.backTrack.btiAH = this.backTrack.btiBH; this.backTrack.btiBH = temp; } this.nStepCycles -= 3; // this form of XCHG takes 3 cycles on all CPUs }; /** * op=0x94 (XCHG AX,SP) * * @this {X86CPU} */ X86.opXCHGSP = function() { var temp = this.regEAX; var regESP = this.getSP(); this.regEAX = (I386? (this.regEAX & ~this.maskData) | (regESP & this.maskData) : regESP); this.setSP((I386? (regESP & ~this.maskData) | (temp & this.maskData) : temp)); if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiAH = 0; this.nStepCycles -= 3; // this form of XCHG takes 3 cycles on all CPUs }; /** * op=0x95 (XCHG AX,BP) * * @this {X86CPU} */ X86.opXCHGBP = function() { var temp = this.regEAX; this.regEAX = (I386? (this.regEAX & ~this.maskData) | (this.regEBP & this.maskData) : this.regEBP); this.regEBP = (I386? (this.regEBP & ~this.maskData) | (temp & this.maskData) : temp); if (BACKTRACK) { temp = this.backTrack.btiAL; this.backTrack.btiAL = this.backTrack.btiBPLo; this.backTrack.btiBPLo = temp; temp = this.backTrack.btiAH; this.backTrack.btiAH = this.backTrack.btiBPHi; this.backTrack.btiBPHi = temp; } this.nStepCycles -= 3; // this form of XCHG takes 3 cycles on all CPUs }; /** * op=0x96 (XCHG AX,SI) * * @this {X86CPU} */ X86.opXCHGSI = function() { var temp = this.regEAX; this.regEAX = (I386? (this.regEAX & ~this.maskData) | (this.regESI & this.maskData) : this.regESI); this.regESI = (I386? (this.regESI & ~this.maskData) | (temp & this.maskData) : temp); if (BACKTRACK) { temp = this.backTrack.btiAL; this.backTrack.btiAL = this.backTrack.btiSILo; this.backTrack.btiSILo = temp; temp = this.backTrack.btiAH; this.backTrack.btiAH = this.backTrack.btiSIHi; this.backTrack.btiSIHi = temp; } this.nStepCycles -= 3; // this form of XCHG takes 3 cycles on all CPUs }; /** * op=0x97 (XCHG AX,DI) * * @this {X86CPU} */ X86.opXCHGDI = function() { var temp = this.regEAX; this.regEAX = (I386? (this.regEAX & ~this.maskData) | (this.regEDI & this.maskData) : this.regEDI); this.regEDI = (I386? (this.regEDI & ~this.maskData) | (temp & this.maskData) : temp); if (BACKTRACK) { temp = this.backTrack.btiAL; this.backTrack.btiAL = this.backTrack.btiDILo; this.backTrack.btiDILo = temp; temp = this.backTrack.btiAH; this.backTrack.btiAH = this.backTrack.btiDIHi; this.backTrack.btiDIHi = temp; } this.nStepCycles -= 3; // this form of XCHG takes 3 cycles on all CPUs }; /** * op=0x98 (CBW/CWDE) * * NOTE: The 16-bit form (CBW) sign-extends AL into AX, whereas the 32-bit form (CWDE) sign-extends AX into EAX; * CWDE is similar to CWD, except that the destination is EAX rather than DX:AX. * * @this {X86CPU} */ X86.opCBW = function() { if (this.sizeData == 2) { // CBW this.regEAX = (this.regEAX & ~0xffff) | (((this.regEAX << 24) >> 24) & 0xffff); if (BACKTRACK) this.backTrack.btiAH = this.backTrack.btiAL; } else { // CWDE this.regEAX = ((this.regEAX << 16) >> 16); } this.nStepCycles -= 2; // CBW takes 2 cycles on all CPUs through 80286 }; /** * op=0x99 (CWD/CDQ) * * NOTE: The 16-bit form (CWD) sign-extends AX, producing a 32-bit result in DX:AX, while the 32-bit form (CDQ) * sign-extends EAX, producing a 64-bit result in EDX:EAX. * * @this {X86CPU} */ X86.opCWD = function() { if (this.sizeData == 2) { // CWD this.regEDX = (this.regEDX & ~0xffff) | ((this.regEAX & 0x8000)? 0xffff : 0); if (BACKTRACK) this.backTrack.btiDL = this.backTrack.btiDH = this.backTrack.btiAH; } else { // CDQ this.regEDX = (this.regEAX & (0x80000000|0))? -1 : 0; } this.nStepCycles -= this.cycleCounts.nOpCyclesCWD; }; /** * op=0x9A (CALL seg:off) * * @this {X86CPU} */ X86.opCALLF = function() { X86.helpCALLF.call(this, this.getIPWord(), this.getIPShort()); this.nStepCycles -= this.cycleCounts.nOpCyclesCallF; }; /** * op=0x9B (WAIT) * * @this {X86CPU} */ X86.opWAIT = function() { if (!this.fpu || !this.fpu.opWAIT()) { this.nStepCycles -= 3; // X86FPU.opWAIT() is required to charge some number of cycles if it returns true } }; /** * op=0x9C (PUSHF/PUSHFD) * * @this {X86CPU} */ X86.opPUSHF = function() { /* * TODO: Consider swapping out this function whenever setProtMode() changes the mode to V86-mode. */ var regPS = this.getPS(); if (I386) { if ((regPS & X86.PS.VM) && this.nIOPL < 3) { if (DEBUG) this.printMessage("PUSHF in v86-mode (IOPL < 3)", this.bitsMessage, true); X86.helpFault.call(this, X86.EXCEPTION.GP_FAULT, 0); return; } /* * It doesn't matter whether this is PUSHF or PUSHFD: the VM and RF flags are never pushed, so * we should always clear them. NOTE: This contradicts what the "INTEL 80386 PROGRAMMER'S REFERENCE * MANUAL 1986" says on page 81 (which we assume is wrong): * * SYSTEMS FLAGS (INCLUDING THE IOPL FIELD, AND THE VM, RF, AND IF FLAGS) ARE PUSHED AND ARE * VISIBLE TO APPLICATIONS PROGRAMS. HOWEVER, WHEN AN APPLICATIONS PROGRAM POPS THE FLAGS, * THESE ITEMS ARE NOT CHANGED, REGARDLESS OF THE VALUES POPPED INTO THEM. * * This does, however, beg the question: how does code running in V86-mode detect that's in V86-mode * and not real-mode? By using the SMSW instruction and checking the PE (protected-mode enabled) bit. * The SMSW instruction returns a subset of the CR0 bits, and unlike the MOV reg,CR0 instruction, is * allowed in V86-mode. See fnSMSW() for more information. */ regPS &= ~(X86.PS.VM | X86.PS.RF); } this.pushWord(regPS); this.nStepCycles -= this.cycleCounts.nOpCyclesPushReg; }; /** * op=0x9D (POPF/POPFD) * * @this {X86CPU} */ X86.opPOPF = function() { /* * TODO: Consider swapping out this function whenever setProtMode() changes the mode to V86-mode. */ if (I386 && (this.regPS & X86.PS.VM) && this.nIOPL < 3) { if (DEBUG) this.printMessage("POPF in v86-mode (IOPL < 3)", this.bitsMessage, true); X86.helpFault.call(this, X86.EXCEPTION.GP_FAULT, 0); return; } /* * Regardless of mode, VM and RF (the only defined EFLAGS bit above bit 15) are never changed by POPFD. */ var newPS = this.popWord(); if (I386) newPS = (newPS & 0xffff) | (this.regPS & ~0xffff); this.setPS(newPS); /* * NOTE: I'm assuming that neither POPF nor IRET are required to set NOINTR like STI does. */ this.nStepCycles -= this.cycleCounts.nOpCyclesPopReg; }; /** * op=0x9E (SAHF) * * @this {X86CPU} */ X86.opSAHF = function() { /* * NOTE: While it make seem more efficient to do this: * * this.setPS((this.getPS() & ~X86.PS_SAHF) | ((this.regEAX >> 8) & X86.PS_SAHF)); * * getPS() forces any "cached" flags to be resolved first, and setPS() must do extra work above * and beyond setting the arithmetic and logical flags, so on balance, the code below may be more * efficient, and may also avoid unexpected side-effects of updating the entire PS register. */ var ah = (this.regEAX >> 8) & 0xff; if (ah & X86.PS.CF) this.setCF(); else this.clearCF(); if (ah & X86.PS.PF) this.setPF(); else this.clearPF(); if (ah & X86.PS.AF) this.setAF(); else this.clearAF(); if (ah & X86.PS.ZF) this.setZF(); else this.clearZF(); if (ah & X86.PS.SF) this.setSF(); else this.clearSF(); this.nStepCycles -= this.cycleCounts.nOpCyclesLAHF; this.assert((this.getPS() & X86.PS_SAHF) == (ah & X86.PS_SAHF)); }; /** * op=0x9F (LAHF) * * @this {X86CPU} */ X86.opLAHF = function() { this.regEAX = (this.regEAX & ~0xff00) | (this.getPS() & X86.PS_SAHF) << 8; this.nStepCycles -= this.cycleCounts.nOpCyclesLAHF; }; /** * op=0xA0 (MOV AL,mem) * * @this {X86CPU} */ X86.opMOVALm = function() { this.regEAX = (this.regEAX & ~0xff) | this.getSOByte(this.segData, this.getIPAddr()); if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiMem0; this.nStepCycles -= this.cycleCounts.nOpCyclesMovAM; }; /** * op=0xA1 (MOV [E]AX,mem) * * @this {X86CPU} */ X86.opMOVAXm = function() { this.regEAX = (this.regEAX & ~this.maskData) | this.getSOWord(this.segData, this.getIPAddr()); if (BACKTRACK) { this.backTrack.btiAL = this.backTrack.btiMem0; this.backTrack.btiAH = this.backTrack.btiMem1; } this.nStepCycles -= this.cycleCounts.nOpCyclesMovAM; }; /** * op=0xA2 (MOV mem,AL) * * @this {X86CPU} */ X86.opMOVmAL = function() { if (BACKTRACK) this.backTrack.btiMem0 = this.backTrack.btiAL; /* * setSOByte() truncates the value as appropriate */ this.setSOByte(this.segData, this.getIPAddr(), this.regEAX); this.nStepCycles -= this.cycleCounts.nOpCyclesMovMA; }; /** * op=0xA3 (MOV mem,AX) * * @this {X86CPU} */ X86.opMOVmAX = function() { if (BACKTRACK) { this.backTrack.btiMem0 = this.backTrack.btiAL; this.backTrack.btiMem1 = this.backTrack.btiAH; } /* * setSOWord() truncates the value as appropriate */ this.setSOWord(this.segData, this.getIPAddr(), this.regEAX); this.nStepCycles -= this.cycleCounts.nOpCyclesMovMA; }; /** * op=0xA4 (MOVSB) * * @this {X86CPU} */ X86.opMOVSb = function() { var nReps = 1; var nDelta = 0; var maskAddr = this.maskAddr; var nCycles = this.cycleCounts.nOpCyclesMovS; if (this.opPrefixes & (X86.OPFLAG.REPZ | X86.OPFLAG.REPNZ)) { nReps = this.regECX & maskAddr; nDelta = 1; nCycles = this.cycleCounts.nOpCyclesMovSrn; if (!(this.opPrefixes & X86.OPFLAG.REPEAT)) this.nStepCycles -= this.cycleCounts.nOpCyclesMovSr0; } if (nReps--) { this.setSOByte(this.segES, this.regEDI & maskAddr, this.getSOByte(this.segData, this.regESI & maskAddr)); /* * helpFault() throws exceptions now, so inline checks of X86.OPFLAG.FAULT should no longer be necessary. * * if (this.opFlags & X86.OPFLAG.FAULT) return; */ var nInc = ((this.regPS & X86.PS.DF)? -1 : 1); this.regESI = (this.regESI & ~maskAddr) | ((this.regESI + nInc) & maskAddr); this.regEDI = (this.regEDI & ~maskAddr) | ((this.regEDI + nInc) & maskAddr); this.nStepCycles -= nCycles; this.regECX = (this.regECX & ~maskAddr) | ((this.regECX - nDelta) & maskAddr); if (nReps) { this.resetIP(((this.opPrefixes & X86.OPFLAG.SEG)? -3 : -2)); this.opFlags |= X86.OPFLAG.REPEAT; } } }; /** * op=0xA5 (MOVSW) * * @this {X86CPU} */ X86.opMOVSw = function() { var nReps = 1; var nDelta = 0; var maskAddr = this.maskAddr; var nCycles = this.cycleCounts.nOpCyclesMovS; if (this.opPrefixes & (X86.OPFLAG.REPZ | X86.OPFLAG.REPNZ)) { nReps = this.regECX & maskAddr; nDelta = 1; nCycles = this.cycleCounts.nOpCyclesMovSrn; if (!(this.opPrefixes & X86.OPFLAG.REPEAT)) this.nStepCycles -= this.cycleCounts.nOpCyclesMovSr0; } if (nReps--) { this.setSOWord(this.segES, this.regEDI & maskAddr, this.getSOWord(this.segData, this.regESI & maskAddr)); /* * helpFault() throws exceptions now, so inline checks of X86.OPFLAG.FAULT should no longer be necessary. * * if (this.opFlags & X86.OPFLAG.FAULT) return; */ var nInc = ((this.regPS & X86.PS.DF)? -this.sizeData : this.sizeData); this.regESI = (this.regESI & ~maskAddr) | ((this.regESI + nInc) & maskAddr); this.regEDI = (this.regEDI & ~maskAddr) | ((this.regEDI + nInc) & maskAddr); this.nStepCycles -= nCycles; this.regECX = (this.regECX & ~maskAddr) | ((this.regECX - nDelta) & maskAddr); if (nReps) { this.resetIP(((this.opPrefixes & X86.OPFLAG.SEG)? -3 : -2)); this.opFlags |= X86.OPFLAG.REPEAT; } } }; /** * op=0xA6 (CMPSB) * * @this {X86CPU} */ X86.opCMPSb = function() { var nReps = 1; var nDelta = 0; var maskAddr = this.maskAddr; var nCycles = this.cycleCounts.nOpCyclesCmpS; if (this.opPrefixes & (X86.OPFLAG.REPZ | X86.OPFLAG.REPNZ)) { nReps = this.regECX & maskAddr; nDelta = 1; nCycles = this.cycleCounts.nOpCyclesCmpSrn; if (!(this.opPrefixes & X86.OPFLAG.REPEAT)) this.nStepCycles -= this.cycleCounts.nOpCyclesCmpSr0; } if (nReps--) { var bDst = this.getEAByte(this.segData, this.regESI); var bSrc = this.getEAByte(this.segES, this.regEDI); this.regEAWrite = this.regEA; // TODO: Is this necessary? /* * helpFault() throws exceptions now, so inline checks of X86.OPFLAG.FAULT should no longer be necessary. * * if (this.opFlags & X86.OPFLAG.FAULT) return; */ X86.fnCMPb.call(this, bDst, bSrc); var nInc = ((this.regPS & X86.PS.DF)? -1 : 1); this.regESI = (this.regESI & ~maskAddr) | ((this.regESI + nInc) & maskAddr); this.regEDI = (this.regEDI & ~maskAddr) | ((this.regEDI + nInc) & maskAddr); this.regECX = (this.regECX & ~maskAddr) | ((this.regECX - nDelta) & maskAddr); /* * NOTE: As long as we're calling fnCMPb(), all our cycle times must be reduced by nOpCyclesArithRM */ this.nStepCycles -= nCycles - this.cycleCounts.nOpCyclesArithRM; /* * Repetition continues while ZF matches bit 0 of the REP prefix. getZF() returns 0x40 if ZF is * set, and OP_REPZ (which represents the REP prefix whose bit 0 is set) is 0x40 as well, so when those * two values are equal, we must continue. */ if (nReps && this.getZF() == (this.opPrefixes & X86.OPFLAG.REPZ)) { this.resetIP(((this.opPrefixes & X86.OPFLAG.SEG)? -3 : -2)); this.opFlags |= X86.OPFLAG.REPEAT; } } }; /** * op=0xA7 (CMPSW) * * @this {X86CPU} */ X86.opCMPSw = function() { var nReps = 1; var nDelta = 0; var maskAddr = this.maskAddr; var nCycles = this.cycleCounts.nOpCyclesCmpS; if (this.opPrefixes & (X86.OPFLAG.REPZ | X86.OPFLAG.REPNZ)) { nReps = this.regECX & maskAddr; nDelta = 1; nCycles = this.cycleCounts.nOpCyclesCmpSrn; if (!(this.opPrefixes & X86.OPFLAG.REPEAT)) this.nStepCycles -= this.cycleCounts.nOpCyclesCmpSr0; } if (nReps--) { var wDst = this.getEAWord(this.segData, this.regESI & maskAddr); var wSrc = this.getEAWord(this.segES, this.regEDI & maskAddr); this.regEAWrite = this.regEA; // TODO: Is this necessary? /* * helpFault() throws exceptions now, so inline checks of X86.OPFLAG.FAULT should no longer be necessary. * * if (this.opFlags & X86.OPFLAG.FAULT) return; */ X86.fnCMPw.call(this, wDst, wSrc); var nInc = ((this.regPS & X86.PS.DF)? -this.sizeData : this.sizeData); this.regESI = (this.regESI & ~maskAddr) | ((this.regESI + nInc) & maskAddr); this.regEDI = (this.regEDI & ~maskAddr) | ((this.regEDI + nInc) & maskAddr); this.regECX = (this.regECX & ~maskAddr) | ((this.regECX - nDelta) & maskAddr); /* * NOTE: As long as we're calling fnCMPw(), all our cycle times must be reduced by nOpCyclesArithRM */ this.nStepCycles -= nCycles - this.cycleCounts.nOpCyclesArithRM; /* * Repetition continues while ZF matches bit 0 of the REP prefix. getZF() returns 0x40 if ZF is * set, and OP_REPZ (which represents the REP prefix whose bit 0 is set) is 0x40 as well, so when those * two values are equal, we must continue. */ if (nReps && this.getZF() == (this.opPrefixes & X86.OPFLAG.REPZ)) { this.resetIP(((this.opPrefixes & X86.OPFLAG.SEG)? -3 : -2)); this.opFlags |= X86.OPFLAG.REPEAT; } } }; /** * op=0xA8 (TEST AL,imm8) * * @this {X86CPU} */ X86.opTESTALb = function() { this.setLogicResult(this.regEAX & this.getIPByte(), X86.RESULT.BYTE); this.nStepCycles -= this.cycleCounts.nOpCyclesAAA; }; /** * op=0xA9 (TEST [E]AX,imm) * * @this {X86CPU} */ X86.opTESTAX = function() { this.setLogicResult(this.regEAX & this.getIPWord(), this.typeData); this.nStepCycles -= this.cycleCounts.nOpCyclesAAA; }; /** * op=0xAA (STOSB) * * NOTES: Segment overrides are ignored for this instruction, so we must use segES instead of segData. * * @this {X86CPU} */ X86.opSTOSb = function() { var nReps = 1; var nDelta = 0; var maskAddr = this.maskAddr; var nCycles = this.cycleCounts.nOpCyclesStoS; if (this.opPrefixes & (X86.OPFLAG.REPZ | X86.OPFLAG.REPNZ)) { nReps = this.regECX & maskAddr; nDelta = 1; nCycles = this.cycleCounts.nOpCyclesStoSrn; if (!(this.opPrefixes & X86.OPFLAG.REPEAT)) this.nStepCycles -= this.cycleCounts.nOpCyclesStoSr0; } if (nReps--) { this.setSOByte(this.segES, this.regEDI & maskAddr, this.regEAX); /* * helpFault() throws exceptions now, so inline checks of X86.OPFLAG.FAULT should no longer be necessary. * * if (this.opFlags & X86.OPFLAG.FAULT) return; */ if (BACKTRACK) this.backTrack.btiMem0 = this.backTrack.btiAL; this.regECX = (this.regECX & ~maskAddr) | ((this.regECX - nDelta) & maskAddr); /* * Implement 80386 B1 Errata #7 (to the extent that Windows 95 checks for the errata). This * isn't a rock-solid implementation of the errata (for example, the ADDRESS override on the next * instruction, if it exists, may or may not be the first prefix byte), but it's close enough. * * Note that we carefully alter with maskAddr only AFTER updating ECX, because this errata affects * only EDI in the case of STOS. The other instructions mentioned below trash different registers, * so read the errata carefully. * * TODO: Extend this errata to STOSW, as well as MOVSB, MOVSW, INSB, and INSW. Also, verify the * extent to which this errata existed on earlier 80386 steppings (I'm currently assuming A0-B1). */ if (this.stepping >= X86.STEPPING_80386_A0 && this.stepping <= X86.STEPPING_80386_B2) { if (!(this.opPrefixes & X86.OPFLAG.ADDRSIZE) != (this.getByte(this.regLIP) != X86.OPCODE.AS)) { maskAddr ^= (0xffff0000|0); } } this.regEDI = (this.regEDI & ~maskAddr) | ((this.regEDI + ((this.regPS & X86.PS.DF)? -1 : 1)) & maskAddr); this.nStepCycles -= nCycles; if (nReps) { this.resetIP(-2); this.opFlags |= X86.OPFLAG.REPEAT; } } }; /** * op=0xAB (STOSW) * * NOTES: Segment overrides are ignored for this instruction, so we must use segES instead of segData. * * @this {X86CPU} */ X86.opSTOSw = function() { var nReps = 1; var nDelta = 0; var maskAddr = this.maskAddr; var nCycles = this.cycleCounts.nOpCyclesStoS; if (this.opPrefixes & (X86.OPFLAG.REPZ | X86.OPFLAG.REPNZ)) { nReps = this.regECX & maskAddr; nDelta = 1; nCycles = this.cycleCounts.nOpCyclesStoSrn; if (!(this.opPrefixes & X86.OPFLAG.REPEAT)) this.nStepCycles -= this.cycleCounts.nOpCyclesStoSr0; } if (nReps--) { this.setSOWord(this.segES, this.regEDI & maskAddr, this.regEAX); /* * helpFault() throws exceptions now, so inline checks of X86.OPFLAG.FAULT should no longer be necessary. * * if (this.opFlags & X86.OPFLAG.FAULT) return; */ if (BACKTRACK) { this.backTrack.btiMem0 = this.backTrack.btiAL; this.backTrack.btiMem1 = this.backTrack.btiAH; } this.regEDI = (this.regEDI & ~maskAddr) | ((this.regEDI + ((this.regPS & X86.PS.DF)? -this.sizeData : this.sizeData)) & maskAddr); this.regECX = (this.regECX & ~maskAddr) | ((this.regECX - nDelta) & maskAddr); this.nStepCycles -= nCycles; if (nReps) { this.resetIP(-2); this.opFlags |= X86.OPFLAG.REPEAT; } } }; /** * op=0xAC (LODSB) * * @this {X86CPU} */ X86.opLODSb = function() { var nReps = 1; var nDelta = 0; var maskAddr = this.maskAddr; var nCycles = this.cycleCounts.nOpCyclesLodS; if (this.opPrefixes & (X86.OPFLAG.REPZ | X86.OPFLAG.REPNZ)) { nReps = this.regECX & maskAddr; nDelta = 1; nCycles = this.cycleCounts.nOpCyclesLodSrn; if (!(this.opPrefixes & X86.OPFLAG.REPEAT)) this.nStepCycles -= this.cycleCounts.nOpCyclesLodSr0; } if (nReps--) { var b = this.getSOByte(this.segData, this.regESI & maskAddr); /* * helpFault() throws exceptions now, so inline checks of X86.OPFLAG.FAULT should no longer be necessary. * * if (this.opFlags & X86.OPFLAG.FAULT) return; */ this.regEAX = (this.regEAX & ~0xff) | b; if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiMem0; this.regESI = (this.regESI & ~maskAddr) | ((this.regESI + ((this.regPS & X86.PS.DF)? -1 : 1)) & maskAddr); this.regECX = (this.regECX & ~maskAddr) | ((this.regECX - nDelta) & maskAddr); this.nStepCycles -= nCycles; if (nReps) { this.resetIP(((this.opPrefixes & X86.OPFLAG.SEG)? -3 : -2)); this.opFlags |= X86.OPFLAG.REPEAT; } } }; /** * op=0xAD (LODSW) * * @this {X86CPU} */ X86.opLODSw = function() { var nReps = 1; var nDelta = 0; var maskAddr = this.maskAddr; var nCycles = this.cycleCounts.nOpCyclesLodS; if (this.opPrefixes & (X86.OPFLAG.REPZ | X86.OPFLAG.REPNZ)) { nReps = this.regECX & maskAddr; nDelta = 1; nCycles = this.cycleCounts.nOpCyclesLodSrn; if (!(this.opPrefixes & X86.OPFLAG.REPEAT)) this.nStepCycles -= this.cycleCounts.nOpCyclesLodSr0; } if (nReps--) { var w = this.getSOWord(this.segData, this.regESI & maskAddr); /* * helpFault() throws exceptions now, so inline checks of X86.OPFLAG.FAULT should no longer be necessary. * * if (this.opFlags & X86.OPFLAG.FAULT) return; */ this.regEAX = (this.regEAX & ~this.maskData) | w; if (BACKTRACK) { this.backTrack.btiAL = this.backTrack.btiMem0; this.backTrack.btiAH = this.backTrack.btiMem1; } this.regESI = (this.regESI & ~maskAddr) | ((this.regESI + ((this.regPS & X86.PS.DF)? -this.sizeData : this.sizeData)) & maskAddr); this.regECX = (this.regECX & ~maskAddr) | ((this.regECX - nDelta) & maskAddr); this.nStepCycles -= nCycles; if (nReps) { this.resetIP(((this.opPrefixes & X86.OPFLAG.SEG)? -3 : -2)); this.opFlags |= X86.OPFLAG.REPEAT; } } }; /** * op=0xAE (SCASB) * * @this {X86CPU} */ X86.opSCASb = function() { var nReps = 1; var nDelta = 0; var maskAddr = this.maskAddr; var nCycles = this.cycleCounts.nOpCyclesScaS; if (this.opPrefixes & (X86.OPFLAG.REPZ | X86.OPFLAG.REPNZ)) { nReps = this.regECX & maskAddr; nDelta = 1; nCycles = this.cycleCounts.nOpCyclesScaSrn; if (!(this.opPrefixes & X86.OPFLAG.REPEAT)) this.nStepCycles -= this.cycleCounts.nOpCyclesScaSr0; } if (nReps--) { var bDst = this.regEAX & 0xff; var bSrc = this.getEAByte(this.segES, this.regEDI); this.regEAWrite = this.regEA; // TODO: Is this necessary? X86.fnCMPb.call(this, bDst, bSrc); /* * helpFault() throws exceptions now, so inline checks of X86.OPFLAG.FAULT should no longer be necessary. * * if (this.opFlags & X86.OPFLAG.FAULT) return; */ this.regEDI = (this.regEDI & ~maskAddr) | ((this.regEDI + ((this.regPS & X86.PS.DF)? -1 : 1)) & maskAddr); this.regECX = (this.regECX & ~maskAddr) | ((this.regECX - nDelta) & maskAddr); /* * NOTE: As long as we're calling fnCMPb(), all our cycle times must be reduced by nOpCyclesArithRM */ this.nStepCycles -= nCycles - this.cycleCounts.nOpCyclesArithRM; /* * Repetition continues while ZF matches bit 0 of the REP prefix. getZF() returns 0x40 if ZF is * set, and OP_REPZ (which represents the REP prefix whose bit 0 is set) is 0x40 as well, so when those * two values are equal, we must continue. */ if (nReps && this.getZF() == (this.opPrefixes & X86.OPFLAG.REPZ)) { this.resetIP(-2); this.opFlags |= X86.OPFLAG.REPEAT; } } }; /** * op=0xAF (SCASW) * * @this {X86CPU} */ X86.opSCASw = function() { var nReps = 1; var nDelta = 0; var maskAddr = this.maskAddr; var nCycles = this.cycleCounts.nOpCyclesScaS; if (this.opPrefixes & (X86.OPFLAG.REPZ | X86.OPFLAG.REPNZ)) { nReps = this.regECX & maskAddr; nDelta = 1; nCycles = this.cycleCounts.nOpCyclesScaSrn; if (!(this.opPrefixes & X86.OPFLAG.REPEAT)) this.nStepCycles -= this.cycleCounts.nOpCyclesScaSr0; } if (nReps--) { var wDst = this.regEAX & this.maskData; var wSrc = this.getEAWord(this.segES, this.regEDI & maskAddr); this.regEAWrite = this.regEA; // TODO: Is this necessary? X86.fnCMPw.call(this, wDst, wSrc); /* * helpFault() throws exceptions now, so inline checks of X86.OPFLAG.FAULT should no longer be necessary. * * if (this.opFlags & X86.OPFLAG.FAULT) return; */ this.regEDI = (this.regEDI & ~maskAddr) | ((this.regEDI + ((this.regPS & X86.PS.DF)? -this.sizeData : this.sizeData)) & maskAddr); this.regECX = (this.regECX & ~maskAddr) | ((this.regECX - nDelta) & maskAddr); /* * NOTE: As long as we're calling fnCMPw(), all our cycle times must be reduced by nOpCyclesArithRM */ this.nStepCycles -= nCycles - this.cycleCounts.nOpCyclesArithRM; /* * Repetition continues while ZF matches bit 0 of the REP prefix. getZF() returns 0x40 if ZF is * set, and OP_REPZ (which represents the REP prefix whose bit 0 is set) is 0x40 as well, so when those * two values are equal, we must continue. */ if (nReps && this.getZF() == (this.opPrefixes & X86.OPFLAG.REPZ)) { this.resetIP(-2); this.opFlags |= X86.OPFLAG.REPEAT; } } }; /** * op=0xB0 (MOV AL,imm8) * * @this {X86CPU} */ X86.opMOVALb = function() { this.regEAX = (this.regEAX & ~0xff) | this.getIPByte(); if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiMem0; this.nStepCycles -= this.cycleCounts.nOpCyclesLAHF; }; /** * op=0xB1 (MOV CL,imm8) * * @this {X86CPU} */ X86.opMOVCLb = function() { this.regECX = (this.regECX & ~0xff) | this.getIPByte(); if (BACKTRACK) this.backTrack.btiCL = this.backTrack.btiMem0; this.nStepCycles -= this.cycleCounts.nOpCyclesLAHF; }; /** * op=0xB2 (MOV DL,imm8) * * @this {X86CPU} */ X86.opMOVDLb = function() { this.regEDX = (this.regEDX & ~0xff) | this.getIPByte(); if (BACKTRACK) this.backTrack.btiDL = this.backTrack.btiMem0; this.nStepCycles -= this.cycleCounts.nOpCyclesLAHF; }; /** * op=0xB3 (MOV BL,imm8) * * @this {X86CPU} */ X86.opMOVBLb = function() { this.regEBX = (this.regEBX & ~0xff) | this.getIPByte(); if (BACKTRACK) this.backTrack.btiBL = this.backTrack.btiMem0; this.nStepCycles -= this.cycleCounts.nOpCyclesLAHF; }; /** * op=0xB4 (MOV AH,imm8) * * @this {X86CPU} */ X86.opMOVAHb = function() { this.regEAX = (this.regEAX & ~0xff00) | (this.getIPByte() << 8); if (BACKTRACK) this.backTrack.btiAH = this.backTrack.btiMem0; this.nStepCycles -= this.cycleCounts.nOpCyclesLAHF; }; /** * op=0xB5 (MOV CH,imm8) * * @this {X86CPU} */ X86.opMOVCHb = function() { this.regECX = (this.regECX & ~0xff00) | (this.getIPByte() << 8); if (BACKTRACK) this.backTrack.btiCH = this.backTrack.btiMem0; this.nStepCycles -= this.cycleCounts.nOpCyclesLAHF; }; /** * op=0xB6 (MOV DH,imm8) * * @this {X86CPU} */ X86.opMOVDHb = function() { this.regEDX = (this.regEDX & ~0xff00) | (this.getIPByte() << 8); if (BACKTRACK) this.backTrack.btiDH = this.backTrack.btiMem0; this.nStepCycles -= this.cycleCounts.nOpCyclesLAHF; }; /** * op=0xB7 (MOV BH,imm8) * * @this {X86CPU} */ X86.opMOVBHb = function() { this.regEBX = (this.regEBX & ~0xff00) | (this.getIPByte() << 8); if (BACKTRACK) this.backTrack.btiBH = this.backTrack.btiMem0; this.nStepCycles -= this.cycleCounts.nOpCyclesLAHF; }; /** * op=0xB8 (MOV [E]AX,imm) * * @this {X86CPU} */ X86.opMOVAX = function() { this.regEAX = (this.regEAX & ~this.maskData) | this.getIPWord(); if (BACKTRACK) { this.backTrack.btiAL = this.backTrack.btiMem0; this.backTrack.btiAH = this.backTrack.btiMem1; } this.nStepCycles -= this.cycleCounts.nOpCyclesLAHF; }; /** * op=0xB9 (MOV [E]CX,imm) * * @this {X86CPU} */ X86.opMOVCX = function() { this.regECX = (this.regECX & ~this.maskData) | this.getIPWord(); if (BACKTRACK) { this.backTrack.btiCL = this.backTrack.btiMem0; this.backTrack.btiCH = this.backTrack.btiMem1; } this.nStepCycles -= this.cycleCounts.nOpCyclesLAHF; }; /** * op=0xBA (MOV [E]DX,imm) * * @this {X86CPU} */ X86.opMOVDX = function() { this.regEDX = (this.regEDX & ~this.maskData) | this.getIPWord(); if (BACKTRACK) { this.backTrack.btiDL = this.backTrack.btiMem0; this.backTrack.btiDH = this.backTrack.btiMem1; } this.nStepCycles -= this.cycleCounts.nOpCyclesLAHF; }; /** * op=0xBB (MOV [E]BX,imm) * * @this {X86CPU} */ X86.opMOVBX = function() { this.regEBX = (this.regEBX & ~this.maskData) | this.getIPWord(); if (BACKTRACK) { this.backTrack.btiBL = this.backTrack.btiMem0; this.backTrack.btiBH = this.backTrack.btiMem1; } this.nStepCycles -= this.cycleCounts.nOpCyclesLAHF; }; /** * op=0xBC (MOV [E]SP,imm) * * @this {X86CPU} */ X86.opMOVSP = function() { this.setSP((this.getSP() & ~this.maskData) | this.getIPWord()); this.nStepCycles -= this.cycleCounts.nOpCyclesLAHF; }; /** * op=0xBD (MOV [E]BP,imm) * * @this {X86CPU} */ X86.opMOVBP = function() { this.regEBP = (this.regEBP & ~this.maskData) | this.getIPWord(); if (BACKTRACK) { this.backTrack.btiBPLo = this.backTrack.btiMem0; this.backTrack.btiBPHi = this.backTrack.btiMem1; } this.nStepCycles -= this.cycleCounts.nOpCyclesLAHF; }; /** * op=0xBE (MOV [E]SI,imm) * * @this {X86CPU} */ X86.opMOVSI = function() { this.regESI = (this.regESI & ~this.maskData) | this.getIPWord(); if (BACKTRACK) { this.backTrack.btiSILo = this.backTrack.btiMem0; this.backTrack.btiSIHi = this.backTrack.btiMem1; } this.nStepCycles -= this.cycleCounts.nOpCyclesLAHF; }; /** * op=0xBF (MOV [E]DI,imm) * * @this {X86CPU} */ X86.opMOVDI = function() { this.regEDI = (this.regEDI & ~this.maskData) | this.getIPWord(); if (BACKTRACK) { this.backTrack.btiDILo = this.backTrack.btiMem0; this.backTrack.btiDIHi = this.backTrack.btiMem1; } this.nStepCycles -= this.cycleCounts.nOpCyclesLAHF; }; /** * op=0xC0 (GRP2 byte,imm8) (80186/80188 and up) * * @this {X86CPU} */ X86.opGRP2bn = function() { this.decodeModGrpByte.call(this, X86.aOpGrp2b, X86.helpSRCByte); }; /** * op=0xC1 (GRP2 word,imm) (80186/80188 and up) * * @this {X86CPU} */ X86.opGRP2wn = function() { this.decodeModGrpWord.call(this, this.sizeData == 2? X86.aOpGrp2w : X86.aOpGrp2d, X86.helpSRCByte); }; /** * op=0xC2 (RET n) * * @this {X86CPU} */ X86.opRETn = function() { var n = this.getIPShort(); var newIP = this.popWord(); this.setIP(newIP); if (n) this.setSP(this.getSP() + n); // TODO: optimize this.nStepCycles -= this.cycleCounts.nOpCyclesRetn; }; /** * op=0xC3 (RET) * * @this {X86CPU} */ X86.opRET = function() { var newIP = this.popWord(); this.setIP(newIP); this.nStepCycles -= this.cycleCounts.nOpCyclesRet; }; /** * op=0xC4 (LES reg,word) * * This is like a "MOV reg,rm" operation, but it also loads ES from the next word. * * @this {X86CPU} */ X86.opLES = function() { this.decodeModRegWord.call(this, X86.fnLES); }; /** * op=0xC5 (LDS reg,word) * * This is like a "MOV reg,rm" operation, but it also loads DS from the next word. * * @this {X86CPU} */ X86.opLDS = function() { this.decodeModRegWord.call(this, X86.fnLDS); }; /** * op=0xC6 (MOV byte,imm8) * * @this {X86CPU} */ X86.opMOVb = function() { /* * Like other MOV operations, the destination does not need to be read, just written. */ this.opFlags |= X86.OPFLAG.NOREAD; this.decodeModGrpByte.call(this, X86.aOpGrpMOVn, this.getIPByte); }; /** * op=0xC7 (MOV word,imm) * * @this {X86CPU} */ X86.opMOVw = function() { /* * Like other MOV operations, the destination does not need to be read, just written. */ this.opFlags |= X86.OPFLAG.NOREAD; this.decodeModGrpWord.call(this, X86.aOpGrpMOVn, this.getIPWord); }; /** * op=0xC8 (ENTER imm16,imm8) (80186/80188 and up) * * @this {X86CPU} */ X86.opENTER = function() { /* * Any operation that performs multiple stack modifications must snapshot regLSP first. */ this.opLSP = this.regLSP; var wLocal = this.getIPShort(); var bLevel = this.getIPByte() & 0x1f; /* * NOTE: 11 is the minimum cycle time for the 80286; the 80186/80188 has different cycle times: 15, 25 and * 22 + 16 * (bLevel - 1) for bLevel 0, 1 and > 1, respectively. TODO: Fix this someday. */ this.nStepCycles -= 11; this.pushWord(this.regEBP); var wFrame = this.getSP() & this.maskData; if (bLevel > 0) { this.nStepCycles -= (bLevel << 2) + (bLevel > 1? 1 : 0); while (--bLevel) { this.regEBP = (this.regEBP & ~this.maskData) | ((this.regEBP - this.sizeData) & this.maskData); this.pushWord(this.getSOWord(this.segSS, this.regEBP & this.maskData)); } this.pushWord(wFrame); } this.regEBP = (this.regEBP & ~this.maskData) | wFrame; this.setSP((this.getSP() & ~this.segSS.maskAddr) | ((this.getSP() - wLocal) & this.segSS.maskAddr)); this.opLSP = X86.ADDR_INVALID; }; /** * op=0xC9 (LEAVE) (80186/80188 and up) * * @this {X86CPU} */ X86.opLEAVE = function() { /* * Any operation that performs multiple stack modifications must snapshot regLSP first. */ this.opLSP = this.regLSP; this.setSP((this.getSP() & ~this.segSS.maskAddr) | (this.regEBP & this.segSS.maskAddr)); this.regEBP = (this.regEBP & ~this.maskData) | (this.popWord() & this.maskData); /* * NOTE: 5 is the cycle time for the 80286; the 80186/80188 has a cycle time of 8. TODO: Fix this someday. */ this.nStepCycles -= 5; this.opLSP = X86.ADDR_INVALID; }; /** * op=0xCA (RETF n) * * @this {X86CPU} */ X86.opRETFn = function() { X86.helpRETF.call(this, this.getIPShort()); this.nStepCycles -= this.cycleCounts.nOpCyclesRetFn; }; /** * op=0xCB (RETF) * * @this {X86CPU} */ X86.opRETF = function() { X86.helpRETF.call(this, 0); this.nStepCycles -= this.cycleCounts.nOpCyclesRetF; }; /** * op=0xCC (INT 3) * * @this {X86CPU} */ X86.opINT3 = function() { /* * TODO: Consider swapping out this function whenever setProtMode() changes the mode to V86-mode. */ if (I386 && (this.regPS & X86.PS.VM) && this.nIOPL < 3) { if (DEBUG) this.printMessage("INT 0x03 in v86-mode (IOPL < 3)", this.bitsMessage, true); X86.helpFault.call(this, X86.EXCEPTION.GP_FAULT, 0); return; } /* * Because INT3 is a trap, not a fault, we must use helpTrap() rather than helpFault(). Unfortunately, that * means you can't rely on the Debugger logic instead helpFault() to conditionally stop execution on an INT3, * so I've changed the Debugger's checkBreakpoint() function to stop execution on INT3 whenever both the * INT and HALT message bits are set; a simple "g" command allows you to continue. */ X86.helpTrap.call(this, X86.EXCEPTION.BP_TRAP, this.cycleCounts.nOpCyclesInt3D); }; /** * op=0xCD (INT n) * * @this {X86CPU} */ X86.opINTn = function() { var nInt = this.getIPByte(); /* * TODO: Consider swapping out this function whenever setProtMode() changes the mode to V86-mode. */ if (I386 && (this.regPS & X86.PS.VM) && this.nIOPL < 3) { if (DEBUG && this.messageEnabled()) this.printMessage("INT " + Str.toHexByte(nInt) + " in v86-mode (IOPL < 3)", true, true); X86.helpFault.call(this, X86.EXCEPTION.GP_FAULT, 0); return; } /* * checkIntNotify() checks for any notification handlers registered via addIntNotify(), calls them, * and returns false ONLY if a notification handler returned false (ie, requesting the interrupt be skipped). */ if (this.checkIntNotify(nInt)) { X86.helpTrap.call(this, nInt, 0); return; } this.nStepCycles--; // we don't need to assess the full cost of nOpCyclesInt, but we need to assess something... }; /** * op=0xCE (INTO: INT 4 if OF set) * * @this {X86CPU} */ X86.opINTO = function() { if (this.getOF()) { /* * TODO: Consider swapping out this function whenever setProtMode() changes the mode to V86-mode. */ if (I386 && (this.regPS & X86.PS.VM) && this.nIOPL < 3) { if (DEBUG) this.printMessage("INTO in v86-mode (IOPL < 3)", this.bitsMessage, true); X86.helpFault.call(this, X86.EXCEPTION.GP_FAULT, 0); return; } X86.helpTrap.call(this, X86.EXCEPTION.OF_TRAP, this.cycleCounts.nOpCyclesIntOD); return; } this.nStepCycles -= this.cycleCounts.nOpCyclesIntOFall; }; /** * op=0xCF (IRET) * * @this {X86CPU} */ X86.opIRET = function() { /* * TODO: Consider swapping out this function whenever setProtMode() changes the mode to V86-mode. */ if (I386 && (this.regPS & X86.PS.VM) && this.nIOPL < 3) { if (DEBUG) this.printMessage("IRET in v86-mode (IOPL < 3)", this.bitsMessage, true); X86.helpFault.call(this, X86.EXCEPTION.GP_FAULT, 0); return; } X86.helpIRET.call(this); }; /** * op=0xD0 (GRP2 byte,1) * * @this {X86CPU} */ X86.opGRP2b1 = function() { this.decodeModGrpByte.call(this, X86.aOpGrp2b, X86.helpSRC1); }; /** * op=0xD1 (GRP2 word,1) * * @this {X86CPU} */ X86.opGRP2w1 = function() { this.decodeModGrpWord.call(this, this.sizeData == 2? X86.aOpGrp2w : X86.aOpGrp2d, X86.helpSRC1); }; /** * op=0xD2 (GRP2 byte,CL) * * @this {X86CPU} */ X86.opGRP2bCL = function() { this.decodeModGrpByte.call(this, X86.aOpGrp2b, X86.helpSRCCL); }; /** * op=0xD3 (GRP2 word,CL) * * @this {X86CPU} */ X86.opGRP2wCL = function() { this.decodeModGrpWord.call(this, this.sizeData == 2? X86.aOpGrp2w : X86.aOpGrp2d, X86.helpSRCCL); }; /** * op=0xD4 0x0A (AAM) * * From "The 8086 Book": * * 1. Divide AL by 0x0A; store the quotient in AH and the remainder in AL * 2. Set PF, SF, and ZF based on the AL register (CF, OF, and AF are undefined) * * From "Undocumented Opcodes" (http://www.rcollins.org/secrets/opcodes/AAM.html): * * AAM is shown as a two byte encoding used to divide AL by 10, putting the quotient in AH, and the remainder in AL. * However, AAM is listed in the op code map as a single byte instruction. This leads one to wonder why a two-byte * opcode is listed in the single-byte opcode map. In reality, the second byte is an undocumented operand to AAM. * The operand is the divisor. In its documented incarnation, AAM is encoded as D4 0A. The operand 0A is the divisor. * This divisor can be changed to any value between 0 and FF. * * Using AAM in this manner is useful -- as it extends the CPU instruction set to include a DIV IMM8 instruction * that is not available from any other form of the DIV instruction. The extended form of the AAM instruction is also * useful because it sets the flags register according to the results, unlike the DIV or IDIV instruction. * * According to Intel documentation, SF, ZF, and PF flags are set according to the result, while OF, AF, and CF * are undefined. However, if AAM were used strictly as documented, then the Sign Flag (SF) could not be set under * any circumstances, since anything divided by 10 will leave a remainder between 0 and 9. Obviously the remainder * could never be between 128 and 255 (or -1 and -128 if you prefer) if used only as documented. Since AAM divides * an 8 bit number by another 8-bit number, a carry or overflow could never occur. Therefore CF and OF always=0. * Intel claims they are undefined, but my observations are consistent with my theory. * * Contrary to documentation, AAM will generate exceptions in real mode, protected mode, and V86 mode. AAM can only * generate Exception 0 -- divide by 0. * * Finally, in the Pentium User's Manual, this heretofore undocumented form of AMM is described. Intel says: * * Note: imm8 has the value of the instruction's second byte. The second byte under normally assembly [sic] of * this instruction will be 0A, however, explicit modification of this byte will result in the operation described * above and may alter results. * * This instruction exists in this form on all Intel x86 processors. See the file [AAM.ASM](/docs/x86/ops/AAM/AAM.ASM) * for diagnostics source code for this instruction. * * @this {X86CPU} */ X86.opAAM = function() { var b = this.getIPByte(); if (!b) { X86.helpDIVOverflow.call(this); return; } var AL = this.regEAX & 0xff; this.regEAX = (this.regEAX & ~0xffff) | ((AL / b) << 8) | (AL % b); /* * setLogicResult() is perfect, because it ensures that CF and OF are cleared as well (see above for why). */ this.setLogicResult(this.regEAX, X86.RESULT.BYTE); this.nStepCycles -= this.cycleCounts.nOpCyclesAAM; }; /** * op=0xD5 (AAD) * * From "The 8086 Book": * * 1. Multiply AH by 0x0A, add AH to AL, and store 0x00 in AH * 2. Set PF, SF, and ZF based on the AL register (CF, OF, and AF are undefined) * * From "Undocumented Opcodes" (http://www.rcollins.org/secrets/opcodes/AAD.html): * * This instruction is the multiplication counterpart to AAM. As is the case with AAM, AAD uses the second * byte as an operand. This operand is the multiplicand for AAD. Like AAM, AAD provides a way to execute a MUL * IMM8 that is unavailable through any other means in the CPU. * * Unlike MUL, or IMUL, AAD sets all of the CPU status flags according to the result. Intel states that the * Overflow Flag (OF), Auxiliary carry Flag (AF), and Carry Flag (CF) are undefined. This assertion is incorrect. * These flags are fully defined, and are set consistently with respect to any other integer operations. * * And again, like AMM, beginning with the Pentium, Intel has finally acknowledged the existence of the second * byte of this instruction as its operand. Intel says: * * Note: imm8 has the value of the instruction's second byte. The second byte under normally assembly [sic] * of this instruction will be 0A, however, explicit modification of this byte will result in the operation * described above and may alter results. * * This instruction exists in this form on all Intel x86 processors. See the file [AAD.ASM](/docs/x86/ops/AAD/AAD.ASM) * for diagnostics source code for this instruction. * * TODO: Confirm on real hardware that flags reflect the result of the final addition (ie, that the result of the * intermediate multiplication is irrelevant); it also might be nice to confirm that an operand override has no effect. * * @this {X86CPU} */ X86.opAAD = function() { var dst = (this.regEAX & 0xff); var src = (((this.regEAX >> 8) & 0xff) * this.getIPByte())|0; var result = (dst + src)|0; this.regEAX = (this.regEAX & ~0xffff) | (result & 0xff); this.setArithResult(dst, src, result, X86.RESULT.BYTE | X86.RESULT.ALL); this.nStepCycles -= this.cycleCounts.nOpCyclesAAD; }; /** * op=0xD6 (SALC aka SETALC) (undocumented until Pentium Pro) * * Sets AL to 0xFF if CF=1, 0x00 otherwise; no flags are affected (similar to SBB AL,AL, but without side-effects) * * WARNING: I have no idea how many clocks this instruction originally required, so for now, I'm going with a minimum of 2. * * @this {X86CPU} */ X86.opSALC = function() { this.regEAX = (this.regEAX & ~0xff) | (this.getCF()? 0xFF : 0); this.nStepCycles -= 2; }; /** * op=0xD7 (XLAT) * * @this {X86CPU} */ X86.opXLAT = function() { /* * TODO: Verify whether XLAT wraps its address calculation.... */ this.regEAX = (this.regEAX & ~0xff) | this.getEAByte(this.segData, (this.regEBX + (this.regEAX & 0xff))); this.nStepCycles -= this.cycleCounts.nOpCyclesXLAT; }; /** * opESC() * * @this {X86CPU} * @param {number} bOpcode */ X86.opESC = function(bOpcode) { this.bOpcode = bOpcode; this.decodeModRegWord.call(this, X86.fnESC); }; /** * op=0xD8 (ESC0) * * @this {X86CPU} */ X86.opESC0 = function() { X86.opESC.call(this, X86.OPCODE.ESC0); }; /** * op=0xD9 (ESC1) * * @this {X86CPU} */ X86.opESC1 = function() { X86.opESC.call(this, X86.OPCODE.ESC1); }; /** * op=0xDA (ESC2) * * @this {X86CPU} */ X86.opESC2 = function() { X86.opESC.call(this, X86.OPCODE.ESC2); }; /** * op=0xDB (ESC3) * * @this {X86CPU} */ X86.opESC3 = function() { X86.opESC.call(this, X86.OPCODE.ESC3); }; /** * op=0xDC (ESC4) * * @this {X86CPU} */ X86.opESC4 = function() { X86.opESC.call(this, X86.OPCODE.ESC4); }; /** * op=0xDD (ESC5) * * @this {X86CPU} */ X86.opESC5 = function() { X86.opESC.call(this, X86.OPCODE.ESC5); }; /** * op=0xDE (ESC6) * * @this {X86CPU} */ X86.opESC6 = function() { X86.opESC.call(this, X86.OPCODE.ESC6); }; /** * op=0xDF (ESC7) * * @this {X86CPU} */ X86.opESC7 = function() { X86.opESC.call(this, X86.OPCODE.ESC7); }; /** * 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() { var disp = this.getIPDisp(); var n = (this.regECX - 1) & this.maskAddr; this.regECX = (this.regECX & ~this.maskAddr) | n; if (n && !this.getZF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesLoopNZ; return; } this.nStepCycles -= this.cycleCounts.nOpCyclesLoopFall; }; /** * 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() { var disp = this.getIPDisp(); var n = (this.regECX - 1) & this.maskAddr; this.regECX = (this.regECX & ~this.maskAddr) | n; if (n && this.getZF()) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesLoopZ; return; } this.nStepCycles -= this.cycleCounts.nOpCyclesLoopZFall; }; /** * 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() { var disp = this.getIPDisp(); var n = (this.regECX - 1) & this.maskAddr; this.regECX = (this.regECX & ~this.maskAddr) | n; if (n) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesLoop; return; } this.nStepCycles -= this.cycleCounts.nOpCyclesLoopFall; }; /** * 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} */ X86.opJCXZ = function() { var disp = this.getIPDisp(); if (!(this.regECX & this.maskAddr)) { this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesLoopZ; return; } this.nStepCycles -= this.cycleCounts.nOpCyclesLoopZFall; }; /** * op=0xE4 (IN AL,port) * * @this {X86CPU} */ X86.opINb = function() { var port = this.getIPByte(); if (!this.checkIOPM(port, 1, true)) return; this.regEAX = (this.regEAX & ~0xff) | (this.bus.checkPortInputNotify(port, 1, this.regLIP - 2) & 0xff); if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiIO; this.nStepCycles -= this.cycleCounts.nOpCyclesInP; }; /** * op=0xE5 (IN AX,port) * * @this {X86CPU} */ X86.opINw = function() { var port = this.getIPByte(); if (!this.checkIOPM(port, this.sizeData, true)) return; this.regEAX = (this.regEAX & ~this.maskData) | (this.bus.checkPortInputNotify(port, this.sizeData, this.regLIP - 2) & this.maskData); if (BACKTRACK) { this.backTrack.btiAL = this.backTrack.btiIO; this.backTrack.btiAH = this.backTrack.btiIO; } this.nStepCycles -= this.cycleCounts.nOpCyclesInP; }; /** * op=0xE6 (OUT port,AL) * * @this {X86CPU} */ X86.opOUTb = function() { var port = this.getIPByte(); if (!this.checkIOPM(port, 1, false)) return; this.bus.checkPortOutputNotify(port, 1, this.regEAX & 0xff, this.regLIP - 2); this.nStepCycles -= this.cycleCounts.nOpCyclesOutP; }; /** * op=0xE7 (OUT port,AX) * * @this {X86CPU} */ X86.opOUTw = function() { var port = this.getIPByte(); if (!this.checkIOPM(port, this.sizeData, false)) return; this.bus.checkPortOutputNotify(port, this.sizeData, this.regEAX & this.maskData, this.regLIP - 2); this.nStepCycles -= this.cycleCounts.nOpCyclesOutP; }; /** * op=0xE8 (CALL disp16) * * @this {X86CPU} */ X86.opCALL = function() { var disp = this.getIPWord(); var oldIP = this.getIP(); var newIP = oldIP + disp; this.pushWord(oldIP); this.setIP(newIP); this.nStepCycles -= this.cycleCounts.nOpCyclesCall; }; /** * op=0xE9 (JMP disp16) * * @this {X86CPU} */ X86.opJMP = function() { var disp = this.getIPWord(); this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmp; }; /** * op=0xEA (JMP seg:off) * * @this {X86CPU} */ X86.opJMPF = function() { this.setCSIP(this.getIPWord(), this.getIPShort()); this.nStepCycles -= this.cycleCounts.nOpCyclesJmpF; }; /** * op=0xEB (JMP short disp8) * * @this {X86CPU} */ X86.opJMPs = function() { var disp = this.getIPDisp(); this.setIP(this.getIP() + disp); this.nStepCycles -= this.cycleCounts.nOpCyclesJmp; }; /** * op=0xEC (IN AL,dx) * * @this {X86CPU} */ X86.opINDXb = function() { var port = this.regEDX & 0xffff; if (!this.checkIOPM(port, 1, true)) return; this.regEAX = (this.regEAX & ~0xff) | (this.bus.checkPortInputNotify(port, 1, this.regLIP - 1) & 0xff); if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiIO; this.nStepCycles -= this.cycleCounts.nOpCyclesInDX; }; /** * op=0xED (IN AX,dx) * * @this {X86CPU} */ X86.opINDXw = function() { var port = this.regEDX & 0xffff; if (!this.checkIOPM(port, this.sizeData, true)) return; this.regEAX = (this.regEAX & ~this.maskData) | (this.bus.checkPortInputNotify(port, this.sizeData, this.regLIP - 1) & this.maskData); if (BACKTRACK) { this.backTrack.btiAL = this.backTrack.btiIO; this.backTrack.btiAH = this.backTrack.btiIO; } this.nStepCycles -= this.cycleCounts.nOpCyclesInDX; }; /** * op=0xEE (OUT dx,AL) * * @this {X86CPU} */ X86.opOUTDXb = function() { var port = this.regEDX & 0xffff; if (!this.checkIOPM(port, 1, false)) return; if (BACKTRACK) this.backTrack.btiIO = this.backTrack.btiAL; this.bus.checkPortOutputNotify(port, 1, this.regEAX & 0xff, this.regLIP - 1); this.nStepCycles -= this.cycleCounts.nOpCyclesOutDX; }; /** * op=0xEF (OUT dx,AX) * * @this {X86CPU} */ X86.opOUTDXw = function() { var port = this.regEDX & 0xffff; if (!this.checkIOPM(port, 2, false)) return; if (BACKTRACK) { this.backTrack.btiIO = this.backTrack.btiAL; this.backTrack.btiIO = this.backTrack.btiAH; } this.bus.checkPortOutputNotify(port, this.sizeData, this.regEAX & this.maskData, this.regLIP - 1); this.nStepCycles -= this.cycleCounts.nOpCyclesOutDX; }; /** * op=0xF0 (LOCK:) * * @this {X86CPU} */ X86.opLOCK = function() { this.opFlags |= X86.OPFLAG.LOCK | X86.OPFLAG.NOINTR; this.nStepCycles -= this.cycleCounts.nOpCyclesPrefix; }; /** * op=0xF1 (INT1; undocumented; 80186/80188 and up; TODO: Verify) * * Note that this handler is assigned to opcode 0xF1 only on 80186 processors and up, because on 8086/8086 * processors, we treat that opcode as an alias for LOCK (0xF0). * * For the 80186 and up, and we treat it as undefined. Starting with the 80386, this opcode is known as INT1 * or ICEBP, since it effectively performs an INT 0x01 but is normally only performed with an ICE. * * @this {X86CPU} */ X86.opINT1 = function() { X86.opUndefined.call(this); }; /** * op=0xF2 (REPNZ:) (repeat CMPS or SCAS until NZ; repeat MOVS, LODS, or STOS unconditionally) * * @this {X86CPU} */ X86.opREPNZ = function() { this.opFlags |= X86.OPFLAG.REPNZ | X86.OPFLAG.NOINTR; this.nStepCycles -= this.cycleCounts.nOpCyclesPrefix; }; /** * op=0xF3 (REPZ:) (repeat CMPS or SCAS until Z; repeat MOVS, LODS, or STOS unconditionally) * * @this {X86CPU} */ X86.opREPZ = function() { this.opFlags |= X86.OPFLAG.REPZ | X86.OPFLAG.NOINTR; this.nStepCycles -= this.cycleCounts.nOpCyclesPrefix; }; /** * op=0xF4 (HLT) * * @this {X86CPU} */ X86.opHLT = function() { /* * TODO: Consider swapping out this function whenever setProtMode() changes the mode to V86-mode. */ if (I386 && (this.regPS & X86.PS.VM)) { X86.helpFault.call(this, X86.EXCEPTION.GP_FAULT, 0); return; } /* * The CPU is never REALLY halted by a HLT instruction; instead, by setting X86.INTFLAG.HALT, * we are signalling to stepCPU() that it's free to end the current burst AND that it should not * execute any more instructions until checkINTR() indicates a hardware interrupt is requested. */ this.intFlags |= X86.INTFLAG.HALT; this.nStepCycles -= 2; /* * If a Debugger is present and the HALT message category is enabled, then we REALLY halt the CPU, * on the theory that whoever's using the Debugger would like to see HLTs. */ if (DEBUGGER && this.dbg && this.messageEnabled(Messages.HALT)) { this.resetIP(-1); // this is purely for the Debugger's benefit, to show the HLT this.dbg.stopCPU(); return; } /* * We also REALLY halt the machine if interrupts have been disabled, since that means it's dead * in the water (we have no NMI generation mechanism at the moment). */ if (!this.getIF()) { if (DEBUGGER && this.dbg) this.resetIP(-1); this.stopCPU(); } }; /** * op=0xF5 (CMC) * * @this {X86CPU} */ X86.opCMC = function() { if (this.getCF()) this.clearCF(); else this.setCF(); this.nStepCycles -= 2; // CMC takes 2 cycles on all CPUs }; /** * op=0xF6 (GRP3 byte) * * The MUL byte instruction is problematic in two cases: * * 0xF6 0xE0: MUL AL * 0xF6 0xE4: MUL AH * * because the OpModGrpByte decoder function will attempt to put the fnMULb() function's * return value back into AL or AH, undoing fnMULb's update of AX. And since fnMULb doesn't * know what the target is (only the target's value), it cannot easily work around the problem. * * A simple, albeit kludgy, solution is for fnMULb to always save its result in a special * "register" (eg, regMDLo), which we will then put back into regEAX if it's been updated. * This also relieves us from having to decode any part of the ModRM byte, so maybe it's not * such a bad work-around after all. * * Similar issues with IMUL (and DIV and IDIV) are resolved using the same special variable(s). * * @this {X86CPU} */ X86.opGRP3b = function() { this.fMDSet = false; this.decodeModGrpByte.call(this, X86.aOpGrp3b, X86.helpSRCNone); if (this.fMDSet) this.regEAX = (this.regEAX & ~this.maskData) | (this.regMDLo & this.maskData); }; /** * op=0xF7 (GRP3 word) * * The MUL word instruction is problematic in two cases: * * 0xF7 0xE0: MUL AX * 0xF7 0xE2: MUL DX * * because the OpModGrpWord decoder function will attempt to put the fnMULw() function's * return value back into AX or DX, undoing fnMULw's update of DX:AX. And since fnMULw doesn't * know what the target is (only the target's value), it cannot easily work around the problem. * * A simple, albeit kludgy, solution is for fnMULw to always save its result in a special * "register" (eg, regMDLo/regMDHi), which we will then put back into regEAX/regEDX if it's been * updated. This also relieves us from having to decode any part of the ModRM byte, so maybe * it's not such a bad work-around after all. * * @this {X86CPU} */ X86.opGRP3w = function() { this.fMDSet = false; this.decodeModGrpWord.call(this, X86.aOpGrp3w, X86.helpSRCNone); if (this.fMDSet) { this.regEAX = (this.regEAX & ~this.maskData) | (this.regMDLo & this.maskData); this.regEDX = (this.regEDX & ~this.maskData) | (this.regMDHi & this.maskData); } }; /** * op=0xF8 (CLC) * * @this {X86CPU} */ X86.opCLC = function() { this.clearCF(); this.nStepCycles -= 2; // CLC takes 2 cycles on all CPUs }; /** * op=0xF9 (STC) * * @this {X86CPU} */ X86.opSTC = function() { this.setCF(); this.nStepCycles -= 2; // STC takes 2 cycles on all CPUs }; /** * op=0xFA (CLI) * * @this {X86CPU} */ X86.opCLI = function() { /* * The following code should be sufficient for all modes, because in real-mode, CPL is always zero, * and in V86-mode, CPL is always 3. */ if (this.nCPL > this.nIOPL) { if (DEBUG && (this.regPS & X86.PS.VM)) this.printMessage("CLI in v86-mode (IOPL < 3)", this.bitsMessage, true); X86.helpFault.call(this, X86.EXCEPTION.GP_FAULT, 0); return; } this.clearIF(); this.nStepCycles -= this.cycleCounts.nOpCyclesCLI; // CLI takes LONGER on an 80286 }; /** * op=0xFB (STI) * * @this {X86CPU} */ X86.opSTI = function() { /* * The following code should be sufficient for all modes, because in real-mode, CPL is always zero, * and in V86-mode, CPL is always 3. */ if (this.nCPL > this.nIOPL) { if (DEBUG && (this.regPS & X86.PS.VM)) this.printMessage("STI in v86-mode (IOPL < 3)", this.bitsMessage, true); X86.helpFault.call(this, X86.EXCEPTION.GP_FAULT, 0); return; } this.setIF(); this.opFlags |= X86.OPFLAG.NOINTR; this.nStepCycles -= 2; // STI takes 2 cycles on all CPUs }; /** * op=0xFC (CLD) * * @this {X86CPU} */ X86.opCLD = function() { this.clearDF(); this.nStepCycles -= 2; // CLD takes 2 cycles on all CPUs }; /** * op=0xFD (STD) * * @this {X86CPU} */ X86.opSTD = function() { this.setDF(); this.nStepCycles -= 2; // STD takes 2 cycles on all CPUs }; /** * op=0xFE (GRP4 byte) * * @this {X86CPU} */ X86.opGRP4b = function() { this.decodeModGrpByte.call(this, X86.aOpGrp4b, X86.helpSRCNone); }; /** * op=0xFF (GRP4 word) * * @this {X86CPU} */ X86.opGRP4w = function() { this.decodeModGrpWord.call(this, X86.aOpGrp4w, X86.helpSRCNone); }; /** * opInvalid() * * @this {X86CPU} */ X86.opInvalid = function() { X86.helpFault.call(this, X86.EXCEPTION.UD_FAULT); }; /** * opUndefined() * * @this {X86CPU} */ X86.opUndefined = function() { this.setIP(this.opLIP - this.segCS.base); this.setError("Undefined opcode " + Str.toHexByte(this.getByte(this.regLIP)) + " at " + Str.toHexLong(this.regLIP)); this.stopCPU(); }; /** * opTBD() * * @this {X86CPU} */ X86.opTBD = function() { this.setIP(this.opLIP - this.segCS.base); this.printMessage("unimplemented 80386 opcode", true); this.stopCPU(); }; /* * This 256-entry array of opcode functions is at the heart of the CPU engine: stepCPU(n). * * It might be worth trying a switch() statement instead, to see how the performance compares, * but I suspect that would vary quite a bit across JavaScript engines; for now, I'm putting my * money on array lookup. */ X86.aOps = [ X86.opADDmb, X86.opADDmw, X86.opADDrb, X86.opADDrw, // 0x00-0x03 X86.opADDALb, X86.opADDAX, X86.opPUSHES, X86.opPOPES, // 0x04-0x07 X86.opORmb, X86.opORmw, X86.opORrb, X86.opORrw, // 0x08-0x0B X86.opORALb, X86.opORAX, X86.opPUSHCS, X86.opPOPCS, // 0x0C-0x0F X86.opADCmb, X86.opADCmw, X86.opADCrb, X86.opADCrw, // 0x10-0x13 X86.opADCALb, X86.opADCAX, X86.opPUSHSS, X86.opPOPSS, // 0x14-0x17 X86.opSBBmb, X86.opSBBmw, X86.opSBBrb, X86.opSBBrw, // 0x18-0x1B X86.opSBBALb, X86.opSBBAX, X86.opPUSHDS, X86.opPOPDS, // 0x1C-0x1F X86.opANDmb, X86.opANDmw, X86.opANDrb, X86.opANDrw, // 0x20-0x23 X86.opANDAL, X86.opANDAX, X86.opES, X86.opDAA, // 0x24-0x27 X86.opSUBmb, X86.opSUBmw, X86.opSUBrb, X86.opSUBrw, // 0x28-0x2B X86.opSUBALb, X86.opSUBAX, X86.opCS, X86.opDAS, // 0x2C-0x2F X86.opXORmb, X86.opXORmw, X86.opXORrb, X86.opXORrw, // 0x30-0x33 X86.opXORALb, X86.opXORAX, X86.opSS, X86.opAAA, // 0x34-0x37 X86.opCMPmb, X86.opCMPmw, X86.opCMPrb, X86.opCMPrw, // 0x38-0x3B X86.opCMPALb, X86.opCMPAX, X86.opDS, X86.opAAS, // 0x3C-0x3F X86.opINCAX, X86.opINCCX, X86.opINCDX, X86.opINCBX, // 0x40-0x43 X86.opINCSP, X86.opINCBP, X86.opINCSI, X86.opINCDI, // 0x44-0x47 X86.opDECAX, X86.opDECCX, X86.opDECDX, X86.opDECBX, // 0x48-0x4B X86.opDECSP, X86.opDECBP, X86.opDECSI, X86.opDECDI, // 0x4C-0x4F X86.opPUSHAX, X86.opPUSHCX, X86.opPUSHDX, X86.opPUSHBX, // 0x50-0x53 X86.opPUSHSP_8086, X86.opPUSHBP, X86.opPUSHSI, X86.opPUSHDI, // 0x54-0x57 X86.opPOPAX, X86.opPOPCX, X86.opPOPDX, X86.opPOPBX, // 0x58-0x5B X86.opPOPSP, X86.opPOPBP, X86.opPOPSI, X86.opPOPDI, // 0x5C-0x5F /* * On an 8086/8088, opcodes 0x60-0x6F are aliases for the conditional jumps 0x70-0x7F. Sometimes you'll see * references to these opcodes (like 0x60) being a "two-byte NOP" and using them differentiate an 8088 from newer * CPUs, but they're only a "two-byte NOP" if the second byte is zero, resulting in zero displacement. */ X86.opJO, X86.opJNO, X86.opJC, X86.opJNC, // 0x60-0x63 X86.opJZ, X86.opJNZ, X86.opJBE, X86.opJNBE, // 0x64-0x67 X86.opJS, X86.opJNS, X86.opJP, X86.opJNP, // 0x68-0x6B X86.opJL, X86.opJNL, X86.opJLE, X86.opJNLE, // 0x6C-0x6F X86.opJO, X86.opJNO, X86.opJC, X86.opJNC, // 0x70-0x73 X86.opJZ, X86.opJNZ, X86.opJBE, X86.opJNBE, // 0x74-0x77 X86.opJS, X86.opJNS, X86.opJP, X86.opJNP, // 0x78-0x7B X86.opJL, X86.opJNL, X86.opJLE, X86.opJNLE, // 0x7C-0x7F /* * On all processors, opcode groups 0x80 and 0x82 perform identically (0x82 opcodes sign-extend their * immediate data, but since both 0x80 and 0x82 are byte operations, the sign extension has no effect). * * WARNING: Intel's "Pentium Processor User's Manual (Volume 3: Architecture and Programming Manual)" refers * to opcode 0x82 as a "reserved" instruction, but also cryptically refers to it as "MOVB AL,imm". This is * assumed to be an error in the manual, because as far as I know, 0x82 has always mirrored 0x80. */ X86.opGRP1b, X86.opGRP1w, X86.opGRP1b, X86.opGRP1sw, // 0x80-0x83 X86.opTESTrb, X86.opTESTrw, X86.opXCHGrb, X86.opXCHGrw, // 0x84-0x87 X86.opMOVmb, X86.opMOVmw, X86.opMOVrb, X86.opMOVrw, // 0x88-0x8B X86.opMOVwsr, X86.opLEA, X86.opMOVsrw, X86.opPOPmw, // 0x8C-0x8F X86.opNOP, X86.opXCHGCX, X86.opXCHGDX, X86.opXCHGBX, // 0x90-0x93 X86.opXCHGSP, X86.opXCHGBP, X86.opXCHGSI, X86.opXCHGDI, // 0x94-0x97 X86.opCBW, X86.opCWD, X86.opCALLF, X86.opWAIT, // 0x98-0x9B X86.opPUSHF, X86.opPOPF, X86.opSAHF, X86.opLAHF, // 0x9C-0x9F X86.opMOVALm, X86.opMOVAXm, X86.opMOVmAL, X86.opMOVmAX, // 0xA0-0xA3 X86.opMOVSb, X86.opMOVSw, X86.opCMPSb, X86.opCMPSw, // 0xA4-0xA7 X86.opTESTALb, X86.opTESTAX, X86.opSTOSb, X86.opSTOSw, // 0xA8-0xAB X86.opLODSb, X86.opLODSw, X86.opSCASb, X86.opSCASw, // 0xAC-0xAF X86.opMOVALb, X86.opMOVCLb, X86.opMOVDLb, X86.opMOVBLb, // 0xB0-0xB3 X86.opMOVAHb, X86.opMOVCHb, X86.opMOVDHb, X86.opMOVBHb, // 0xB4-0xB7 X86.opMOVAX, X86.opMOVCX, X86.opMOVDX, X86.opMOVBX, // 0xB8-0xBB X86.opMOVSP, X86.opMOVBP, X86.opMOVSI, X86.opMOVDI, // 0xBC-0xBF /* * On an 8086/8088, opcodes 0xC0 -> 0xC2, 0xC1 -> 0xC3, 0xC8 -> 0xCA and 0xC9 -> 0xCB. */ X86.opRETn, X86.opRET, X86.opRETn, X86.opRET, // 0xC0-0xC3 X86.opLES, X86.opLDS, X86.opMOVb, X86.opMOVw, // 0xC4-0xC7 X86.opRETFn, X86.opRETF, X86.opRETFn, X86.opRETF, // 0xC8-0xCB X86.opINT3, X86.opINTn, X86.opINTO, X86.opIRET, // 0xCC-0xCF X86.opGRP2b1, X86.opGRP2w1, X86.opGRP2bCL, X86.opGRP2wCL, // 0xD0-0xD3 /* * Even as of the Pentium, opcode 0xD6 is still marked as "reserved", but it's always been SALC (aka SETALC). */ X86.opAAM, X86.opAAD, X86.opSALC, X86.opXLAT, // 0xD4-0xD7 X86.opESC0, X86.opESC1, X86.opESC2, X86.opESC3, // 0xD8-0xDB X86.opESC4, X86.opESC5, X86.opESC6, X86.opESC7, // 0xDC-0xDF X86.opLOOPNZ, X86.opLOOPZ, X86.opLOOP, X86.opJCXZ, // 0xE0-0xE3 X86.opINb, X86.opINw, X86.opOUTb, X86.opOUTw, // 0xE4-0xE7 X86.opCALL, X86.opJMP, X86.opJMPF, X86.opJMPs, // 0xE8-0xEB X86.opINDXb, X86.opINDXw, X86.opOUTDXb, X86.opOUTDXw, // 0xEC-0xEF /* * On an 8086/8088, opcode 0xF1 is believed to be an alias for 0xF0; in any case, it definitely behaves like * a prefix on those processors, so we treat it as such. On the 80186 and up, we treat as opINT1(). * * As of the Pentium, opcode 0xF1 is still marked "reserved". */ X86.opLOCK, X86.opLOCK, X86.opREPNZ, X86.opREPZ, // 0xF0-0xF3 X86.opHLT, X86.opCMC, X86.opGRP3b, X86.opGRP3w, // 0xF4-0xF7 X86.opCLC, X86.opSTC, X86.opCLI, X86.opSTI, // 0xF8-0xFB X86.opCLD, X86.opSTD, X86.opGRP4b, X86.opGRP4w // 0xFC-0xFF ]; /* * A word (or two) on instruction groups (eg, Grp1, Grp2), which are groups of instructions that * use a mod/reg/rm byte, where the reg field of that byte selects a function rather than a register. * * I start with the groupings used by Intel's "Pentium Processor User's Manual (Volume 3: Architecture * and Programming Manual)", but I deviate slightly, mostly by subdividing their groups with letter suffixes: * * Opcodes Intel PCx86 PC Mag TechRef * ------- ----- ---- -------------- * 0x80-0x83 Grp1 Grp1b and Grp1w Group A * 0xC0-0xC1 Grp2 Grp2b and Grp2w (opGRP2bn/wn) Group B * 0xD0-0xD3 Grp2 Grp2b and Grp2w (opGRP2b1/w1 and opGRP2bCL/wCL) Group B * 0xF6-0xF7 Grp3 Grp3b and Grp3w Group C * 0xFE Grp4 Grp4b Group D * 0xFF Grp5 Grp4w Group E * 0x0F,0x00 Grp6 Grp6 (SLDT, STR, LLDT, LTR, VERR, VERW) Group F * 0x0F,0x01 Grp7 Grp7 (SGDT, SIDT, LGDT, LIDT, SMSW, LMSW, INVLPG) Group G * 0x0F,0xBA Grp8 Grp8 (BT, BTS, BTR, BTC) Group H * 0x0F,0xC7 Grp9 Grp9 (CMPXCH) (N/A, 80486 and up) * * My only serious deviation is Grp5, which I refer to as Grp4w, because it contains word forms of * the INC and DEC instructions found in Grp4b. Granted, Grp4w also contains versions of the CALL, * JMP and PUSH instructions, which are not in Grp4b, but there's nothing in Grp4b that conflicts with * Grp4w, so I think my nomenclature makes more sense. To compensate, I don't use Grp5, so that the * remaining group numbers remain in sync with Intel's. * * To the above list, I've added a few "single-serving" groups: opcode 0x8F uses GrpPOPw, and opcodes 0xC6/0xC7 * use GrpMOVn. In both of these groups, the only valid (documented) instruction is where reg=0x0. * * TODO: Test what happens on real hardware when the reg field is non-zero for opcodes 0x8F and 0xC6/0xC7. */ X86.aOpGrp1b = [ X86.fnADDb, X86.fnORb, X86.fnADCb, X86.fnSBBb, // 0x80/0x82(reg=0x0-0x3) X86.fnANDb, X86.fnSUBb, X86.fnXORb, X86.fnCMPb // 0x80/0x82(reg=0x4-0x7) ]; X86.aOpGrp1w = [ X86.fnADDw, X86.fnORw, X86.fnADCw, X86.fnSBBw, // 0x81/0x83(reg=0x0-0x3) X86.fnANDw, X86.fnSUBw, X86.fnXORw, X86.fnCMPw // 0x81/0x83(reg=0x4-0x7) ]; X86.aOpGrpPOPw = [ X86.fnPOPw, X86.fnGRPFault, X86.fnGRPFault, X86.fnGRPFault, // 0x8F(reg=0x0-0x3) X86.fnGRPFault, X86.fnGRPFault, X86.fnGRPFault, X86.fnGRPFault // 0x8F(reg=0x4-0x7) ]; X86.aOpGrpMOVn = [ X86.fnMOVn, X86.fnGRPUndefined, X86.fnGRPUndefined, X86.fnGRPUndefined, // 0xC6/0xC7(reg=0x0-0x3) X86.fnGRPUndefined, X86.fnGRPUndefined, X86.fnGRPUndefined, X86.fnGRPUndefined // 0xC6/0xC7(reg=0x4-0x7) ]; X86.aOpGrp2b = [ X86.fnROLb, X86.fnRORb, X86.fnRCLb, X86.fnRCRb, // 0xC0/0xD0/0xD2(reg=0x0-0x3) X86.fnSHLb, X86.fnSHRb, X86.fnGRPUndefined, X86.fnSARb // 0xC0/0xD0/0xD2(reg=0x4-0x7) ]; X86.aOpGrp2w = [ X86.fnROLw, X86.fnRORw, X86.fnRCLw, X86.fnRCRw, // 0xC1/0xD1/0xD3(reg=0x0-0x3) X86.fnSHLw, X86.fnSHRw, X86.fnGRPUndefined, X86.fnSARw // 0xC1/0xD1/0xD3(reg=0x4-0x7) ]; X86.aOpGrp2d = [ X86.fnROLd, X86.fnRORd, X86.fnRCLd, X86.fnRCRd, // 0xC1/0xD1/0xD3(reg=0x0-0x3) X86.fnSHLd, X86.fnSHRd, X86.fnGRPUndefined, X86.fnSARd // 0xC1/0xD1/0xD3(reg=0x4-0x7) ]; X86.aOpGrp3b = [ X86.fnTESTib, X86.fnGRPUndefined, X86.fnNOTb, X86.fnNEGb, // 0xF6(reg=0x0-0x3) X86.fnMULb, X86.fnIMULb, X86.fnDIVb, X86.fnIDIVb // 0xF6(reg=0x4-0x7) ]; X86.aOpGrp3w = [ X86.fnTESTiw, X86.fnGRPUndefined, X86.fnNOTw, X86.fnNEGw, // 0xF7(reg=0x0-0x3) X86.fnMULw, X86.fnIMULw, X86.fnDIVw, X86.fnIDIVw // 0xF7(reg=0x4-0x7) ]; X86.aOpGrp4b = [ X86.fnINCb, X86.fnDECb, X86.fnGRPUndefined, X86.fnGRPUndefined, // 0xFE(reg=0x0-0x3) X86.fnGRPUndefined, X86.fnGRPUndefined, X86.fnGRPUndefined, X86.fnGRPUndefined // 0xFE(reg=0x4-0x7) ]; X86.aOpGrp4w = [ X86.fnINCw, X86.fnDECw, X86.fnCALLw, X86.fnCALLFdw, // 0xFF(reg=0x0-0x3) X86.fnJMPw, X86.fnJMPFdw, X86.fnPUSHw, X86.fnGRPUndefined // 0xFF(reg=0x4-0x7) ];