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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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)
{
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.
* 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) {
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().
*/
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().
*/
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().
*/
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().
*/
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().
*/
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().
*/
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;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long