Force all segment register writes to be 16-bit, even when the operand size is 32-bit
This commit is contained in:
parent
03853a87b7
commit
adb2b9aa90
9 changed files with 1265 additions and 1216 deletions
|
|
@ -1707,8 +1707,14 @@ X86CPU.prototype.setProtMode = function(fProt, fV86)
|
|||
if (I386 && this.model >= X86.MODEL_80386) {
|
||||
this.segFS.updateMode(false, fProt, fV86);
|
||||
this.segGS.updateMode(false, fProt, fV86);
|
||||
this.resetSizes();
|
||||
}
|
||||
/*
|
||||
* This function used to be called only when I386 is true, but it's probably best if we ALWAYS call it, even
|
||||
* for 16-bit-only CPUs like the 8086 and 80286; this allows us to write opcode logic by either checking I386
|
||||
* and using appropriate hard-coded sizes, or NOT checking I386 and simply using the "soft-coded" sizes in
|
||||
* sizeData and sizeAddr.
|
||||
*/
|
||||
this.resetSizes();
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -3759,17 +3765,18 @@ X86CPU.prototype.popWord = function()
|
|||
};
|
||||
|
||||
/**
|
||||
* pushData(d, size)
|
||||
* pushData(d, width, size)
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} d is the data to push at current SP; SP decreased by size
|
||||
* @param {number} size is the size of the data to push (must be either 2 or 4)
|
||||
* @param {number} width is the width of the data to push, in bytes (must be either 2 or 4)
|
||||
* @param {number} size is the size of the data to push, in bytes (must be > 0 and <= width)
|
||||
*/
|
||||
X86CPU.prototype.pushData = function(d, size)
|
||||
X86CPU.prototype.pushData = function(d, width, size)
|
||||
{
|
||||
this.assert(size == 2 || size == 4);
|
||||
this.assert((width == 2 || width == 4) && (size > 0 && size <= width));
|
||||
|
||||
var regLSP = (this.regLSP - size)|0;
|
||||
var regLSP = (this.regLSP - width)|0;
|
||||
|
||||
/*
|
||||
* Properly comparing regLSP to regLSPLimitLow would normally require coercing both to unsigned
|
||||
|
|
@ -3790,10 +3797,19 @@ X86CPU.prototype.pushData = function(d, size)
|
|||
}
|
||||
}
|
||||
|
||||
if (size == 2) {
|
||||
switch(size) {
|
||||
case 1:
|
||||
this.setByte(regLSP, d);
|
||||
break
|
||||
case 2:
|
||||
this.setShort(regLSP, d);
|
||||
} else {
|
||||
break
|
||||
case 4:
|
||||
this.setLong(regLSP, d);
|
||||
break
|
||||
default:
|
||||
this.assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -567,8 +567,13 @@ X86.fnCALLF = function(off, sel)
|
|||
var oldIP = this.getIP();
|
||||
var oldSize = (I386? this.sizeData : 2);
|
||||
if (this.setCSIP(off, sel, true) != null) {
|
||||
this.pushData(this.opCS, oldSize);
|
||||
this.pushData(oldIP, oldSize);
|
||||
/*
|
||||
* 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; at least, that's the case for all other
|
||||
* segment register writes, so we assume this case is no different. Hence, the hard-coded size of 2.
|
||||
*/
|
||||
this.pushData(this.opCS, oldSize, 2);
|
||||
this.pushData(oldIP, oldSize, oldSize);
|
||||
}
|
||||
this.opLSP = X86.ADDR_INVALID;
|
||||
this.opCS = -1;
|
||||
|
|
@ -1941,12 +1946,14 @@ X86.fnMOVn = function(dst, src)
|
|||
*/
|
||||
X86.fnMOVxx = function(dst, src)
|
||||
{
|
||||
/*
|
||||
* When a 32-bit OPERAND size is in effect, segment register writes via opMOVwsr() must write 32 bits
|
||||
* (zero-extended) if the destination is a register, but only 16 bits if the destination is memory,
|
||||
* hence the setDataSize(2) below.
|
||||
*
|
||||
* The only other caller, opMOVrc(), is not affected, because it writes only to register destinations.
|
||||
*/
|
||||
if (this.regEAWrite !== X86.ADDR_INVALID) {
|
||||
/*
|
||||
* When a 32-bit OPERAND size is in effect, opMOVwsr() will write 32 bits (zero-extended) if the destination
|
||||
* is a register, but only 16 bits if the destination is memory. The only other caller, opMOVrc(), is not
|
||||
* affected, because it writes only to register destinations.
|
||||
*/
|
||||
this.setDataSize(2);
|
||||
}
|
||||
return X86.fnMOV.call(this, dst, this.regXX);
|
||||
|
|
|
|||
|
|
@ -1109,11 +1109,15 @@ X86.opSETNLE = function()
|
|||
X86.opPUSHFS = function()
|
||||
{
|
||||
/*
|
||||
* TODO: Reportedly, 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, whereas we will write
|
||||
* a 32-bit value, effectively zeroing the 2 upper bytes. Need to confirm this.
|
||||
* 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().
|
||||
*/
|
||||
this.pushWord(this.segFS.sel);
|
||||
if (!I386) {
|
||||
this.pushWord(this.segFS.sel);
|
||||
} else {
|
||||
this.pushData(this.segFS.sel, this.sizeData, 2);
|
||||
}
|
||||
this.nStepCycles -= this.cycleCounts.nOpCyclesPushSeg;
|
||||
};
|
||||
|
||||
|
|
@ -1210,11 +1214,15 @@ X86.opIBTS = function()
|
|||
X86.opPUSHGS = function()
|
||||
{
|
||||
/*
|
||||
* TODO: Reportedly, 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, whereas we will write
|
||||
* a 32-bit value, effectively zeroing the 2 upper bytes. Need to confirm this.
|
||||
* 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().
|
||||
*/
|
||||
this.pushWord(this.segGS.sel);
|
||||
if (!I386) {
|
||||
this.pushWord(this.segGS.sel);
|
||||
} else {
|
||||
this.pushData(this.segGS.sel, this.sizeData, 2);
|
||||
}
|
||||
this.nStepCycles -= this.cycleCounts.nOpCyclesPushSeg;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -129,11 +129,15 @@ X86.opADDAX = function()
|
|||
X86.opPUSHES = function()
|
||||
{
|
||||
/*
|
||||
* TODO: Reportedly, 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, whereas we will write
|
||||
* a 32-bit value, effectively zeroing the 2 upper bytes. Need to confirm this.
|
||||
* 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().
|
||||
*/
|
||||
this.pushWord(this.segES.sel);
|
||||
if (!I386) {
|
||||
this.pushWord(this.segES.sel);
|
||||
} else {
|
||||
this.pushData(this.segES.sel, this.sizeData, 2);
|
||||
}
|
||||
this.nStepCycles -= this.cycleCounts.nOpCyclesPushSeg;
|
||||
};
|
||||
|
||||
|
|
@ -227,11 +231,15 @@ X86.opORAX = function()
|
|||
X86.opPUSHCS = function()
|
||||
{
|
||||
/*
|
||||
* TODO: Reportedly, 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, whereas we will write
|
||||
* a 32-bit value, effectively zeroing the 2 upper bytes. Need to confirm this.
|
||||
* 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().
|
||||
*/
|
||||
this.pushWord(this.segCS.sel);
|
||||
if (!I386) {
|
||||
this.pushWord(this.segCS.sel);
|
||||
} else {
|
||||
this.pushData(this.segCS.sel, this.sizeData, 2);
|
||||
}
|
||||
this.nStepCycles -= this.cycleCounts.nOpCyclesPushSeg;
|
||||
};
|
||||
|
||||
|
|
@ -333,11 +341,15 @@ X86.opADCAX = function()
|
|||
X86.opPUSHSS = function()
|
||||
{
|
||||
/*
|
||||
* TODO: Reportedly, 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, whereas we will write
|
||||
* a 32-bit value, effectively zeroing the 2 upper bytes. Need to confirm this.
|
||||
* 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().
|
||||
*/
|
||||
this.pushWord(this.segSS.sel);
|
||||
if (!I386) {
|
||||
this.pushWord(this.segSS.sel);
|
||||
} else {
|
||||
this.pushData(this.segSS.sel, this.sizeData, 2);
|
||||
}
|
||||
this.nStepCycles -= this.cycleCounts.nOpCyclesPushSeg;
|
||||
};
|
||||
|
||||
|
|
@ -431,11 +443,15 @@ X86.opSBBAX = function()
|
|||
X86.opPUSHDS = function()
|
||||
{
|
||||
/*
|
||||
* TODO: Reportedly, 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, whereas we will write
|
||||
* a 32-bit value, effectively zeroing the 2 upper bytes. Need to confirm this.
|
||||
* 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().
|
||||
*/
|
||||
this.pushWord(this.segDS.sel);
|
||||
if (!I386) {
|
||||
this.pushWord(this.segDS.sel);
|
||||
} else {
|
||||
this.pushData(this.segDS.sel, this.sizeData, 2);
|
||||
}
|
||||
this.nStepCycles -= this.cycleCounts.nOpCyclesPushSeg;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -921,29 +921,29 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fProbe)
|
|||
* Frames coming from V86-mode ALWAYS contain 32-bit values, and look like this:
|
||||
*
|
||||
* low: EIP
|
||||
* CS (padded to 32 bits)
|
||||
* CS (upper 16 bits undefined)
|
||||
* EFLAGS
|
||||
* ESP
|
||||
* SS (padded to 32 bits)
|
||||
* ES (padded to 32 bits)
|
||||
* DS (padded to 32 bits)
|
||||
* FS (padded to 32 bits)
|
||||
* high: GS (padded to 32 bits)
|
||||
* SS (upper 16 bits undefined)
|
||||
* ES (upper 16 bits undefined)
|
||||
* DS (upper 16 bits undefined)
|
||||
* FS (upper 16 bits undefined)
|
||||
* high: GS (upper 16 bits undefined)
|
||||
*
|
||||
* Our caller (eg, fnINT()) will take care of pushing the final bits (EFLAGS, CS, and EIP).
|
||||
*/
|
||||
cpu.setDataSize(4);
|
||||
cpu.assert(I386 && cpu.model >= X86.MODEL_80386);
|
||||
cpu.pushWord(cpu.segGS.sel);
|
||||
cpu.pushData(cpu.segGS.sel, 4, 2);
|
||||
cpu.setGS(0);
|
||||
cpu.pushWord(cpu.segFS.sel);
|
||||
cpu.pushData(cpu.segFS.sel, 4, 2);
|
||||
cpu.setFS(0);
|
||||
cpu.pushWord(cpu.segDS.sel);
|
||||
cpu.pushData(cpu.segDS.sel, 4, 2);
|
||||
cpu.setDS(0);
|
||||
cpu.pushWord(cpu.segES.sel);
|
||||
cpu.pushData(cpu.segES.sel, 4, 2);
|
||||
cpu.setES(0);
|
||||
}
|
||||
cpu.pushWord(regSSPrev);
|
||||
cpu.pushData(regSSPrev, cpu.sizeData, 2);
|
||||
cpu.pushWord(regSPPrev);
|
||||
while (i) cpu.pushWord(this.awParms[--i]);
|
||||
this.fStackSwitch = true;
|
||||
|
|
|
|||
Loading…
Reference in a new issue