Force all segment register writes to be 16-bit, even when the operand size is 32-bit

This commit is contained in:
Jeff Parsons 2016-01-27 10:58:01 -08:00
commit adb2b9aa90
9 changed files with 1265 additions and 1216 deletions

View file

@ -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;
}
/*

View file

@ -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);

View file

@ -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;
};

View file

@ -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;
};

View file

@ -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;