The list file is unchanging, though(only if source code is changed in the testsuite, which is only the base constants and the additional logging code):
test386.lst.zip
The #DE handler moves a new EIP to the stack (0010:C20C), so that the iretd at 0010:C213 can return to a ret, instead of the div that caused the #DE (see the comment in the source asm). The CPU then correctly executes a ret (0010:C214) but, instead of returning to 0010:2941, it jumps to 0010:0000.
Maybe a wrongly sized stack pop?
Edit: A wrongly sized stack pop occurred to me too, but the iret returns correctly and a 32-bit ret should pop and increase 32-bits afaik(esp+4, 32-bits having been read)? Also, earlier call/ret instructions worked?
And, the address you mentioned(0010:C20C isn't triggered by my breakpoint, so it's not ever reached). So maybe an earlier error?
Edit: I see one being raised at 0010:2BFC. It raises a div0 fault, no error code. CS, EIP and EFLAGS are pushed as dwords(Esp-0xC).
I opened an issue rather than forking the code because I haven't spent the time yet to understand how to add new tests by myself.
I hope that's acceptable.
I've updated test386.asm with a BCD undefined flags test (for the 80386 only though).
I've also validated the flags values against a 386SX hardware.
The old Intel dev manuals are wrong.
The correct algorithm is reported in the current IA-32 Software Dev's Manual (Volume 2A).
1old_AL ← AL; 2old_CF ← CF; 3CF ← 0; 4IF (((AL AND 0FH) > 9) or AF = 1) 5 THEN 6 AL ← AL - 6; 7 CF ← old_CF or (Borrow from AL ← AL − 6); 8 AF ← 1; 9 ELSE 10 AF ← 0; 11FI; 12IF ((old_AL > 99H) or (old_CF = 1)) 13 THEN 14 AL ← AL − 60H; 15 CF ← 1; 16FI;
Don't blindly trust old docs.
BTW, I've verified the results on a real 386.
There's something I notice about the test386.asm repository: it does seem to have a EE file(test386-EE-reference) for normal logs, but not another one for the logs for the undefined flags as well(the TEST_UNDEF enabled test)?
Also, looking at the normal log(the undefined logs HLTs when enabled?), by examining the softdebugger unit in UniPCemu, I see that it was last writing "#DE " block before crashing, so it was actually triggered and written to port E9. Maybe returning from that point crashes it somehow?
Edit: The bugs up until IMUL8 seems to have been fixed now. The IMUL8 instructions and onwardsI(all remaining IMUL instructions) seems to have problems with it's output, as well as input flags(PS)?
This is my current implementation of those IMUL instructions:
16-bit:
1void CPU186_OP69() 2{ 3 memcpy(&info,¶ms.info[MODRM_src0],sizeof(info)); //Reg! 4 memcpy(&info2,¶ms.info[MODRM_src1],sizeof(info2)); //Second parameter(R/M)! 5 if (MODRM_MOD(params.modrm)==3) //Two-operand version? 6 { 7 debugger_setcommand("IMUL %s,%04X",info.text,immw); //IMUL reg,imm16 8 } 9 else //Three-operand version? 10 { 11 debugger_setcommand("IMUL %s,%s,%04X",info.text,info2.text,immw); //IMUL reg,r/m16,imm16 12 } 13 if (CPU[activeCPU].instructionstep==0) //First step? 14 { 15 if (MODRM_MOD(params.modrm)!=3) //Use R/M to calculate the result(Three-operand version)? 16 { 17 if (modrm_check16(¶ms,1,1)) return; //Abort on fault! 18 if (CPU8086_instructionstepreadmodrmw(0,&temp1.val16,1)) return; //Read R/M! 19 temp1.val16high = 0; //Clear high part by default! 20 } 21 else 22 { 23 if (CPU8086_instructionstepreadmodrmw(0,&temp1.val16,0)) return; //Read reg instead! Word register = Word register * imm16! 24 temp1.val16high = 0; //Clear high part by default! 25 } 26 ++CPU[activeCPU].instructionstep; //Next step! 27 } 28 if (CPU[activeCPU].instructionstep==1) //Second step? 29 { 30 temp2.val32 = immw; //Immediate word is second/third parameter! 31 if ((temp1.val32 &0x8000)==0x8000) temp1.val32 |= 0xFFFF0000; 32 if ((temp2.val32 &0x8000)==0x8000) temp2.val32 |= 0xFFFF0000; 33 temp3.val32s = temp1.val32s; //Load and... 34 temp3.val32s *= temp2.val32s; //Signed multiplication! 35 CPU_apply286cycles(); //Apply the 80286+ cycles! 36 //We're writing to the register always, so no normal writeback! 37 ++CPU[activeCPU].instructionstep; //Next step! 38 } 39 modrm_write16(¶ms,MODRM_src0,temp3.val16,0); //Write to the destination(register)! 40 if (((temp3.val32>>15)==0) || ((temp3.val32>>15)==0x1FFFF)) FLAGW_OF(0); //Overflow flag is cleared when high word is a sign extension of the low word! 41 else FLAGW_OF(1); 42 FLAGW_CF(FLAG_OF); //OF=CF! 43 FLAGW_SF((temp3.val16&0x8000)>>15); //Sign! 44 FLAGW_PF(parity[temp3.val16&0xFF]); //Parity flag! 45 FLAGW_ZF((temp3.val16==0)?1:0); //Set the zero flag! 46} 47 48void CPU186_OP6B() 49{ 50 memcpy(&info,¶ms.info[MODRM_src0],sizeof(info)); //Reg! 51 memcpy(&info2,¶ms.info[MODRM_src1],sizeof(info2)); //Second parameter(R/M)! 52 if (MODRM_MOD(params.modrm)==3) //Two-operand version? 53 { 54 debugger_setcommand("IMUL %s,%02X",info.text,immb); //IMUL reg,imm8 55 } 56 else //Three-operand version? 57 { 58 debugger_setcommand("IMUL %s,%s,%02X",info.text,info2.text,immb); //IMUL reg,r/m16,imm8 59 } 60
…Show last 35 lines
61 if (CPU[activeCPU].instructionstep==0) //First step? 62 { 63 if (MODRM_MOD(params.modrm)!=3) //Use R/M to calculate the result(Three-operand version)? 64 { 65 if (modrm_check16(¶ms,1,1)) return; //Abort on fault! 66 if (CPU8086_instructionstepreadmodrmw(0,&temp1.val16,MODRM_src1)) return; //Read R/M! 67 temp1.val16high = 0; //Clear high part by default! 68 } 69 else 70 { 71 if (CPU8086_instructionstepreadmodrmw(0,&temp1.val16,MODRM_src0)) return; //Read reg instead! Word register = Word register * imm16! 72 temp1.val16high = 0; //Clear high part by default! 73 } 74 ++CPU[activeCPU].instructionstep; //Next step! 75 } 76 if (CPU[activeCPU].instructionstep==1) //Second step? 77 { 78 temp2.val32 = (uint_32)immb; //Read unsigned parameter! 79 80 if (temp1.val32&0x8000) temp1.val32 |= 0xFFFF0000;//Sign extend to 32 bits! 81 if (temp2.val32&0x80) temp2.val32 |= 0xFFFFFF00; //Sign extend to 32 bits! 82 temp3.val32s = temp1.val32s * temp2.val32s; 83 CPU_apply286cycles(); //Apply the 80286+ cycles! 84 //We're writing to the register always, so no normal writeback! 85 ++CPU[activeCPU].instructionstep; //Next step! 86 } 87 88 modrm_write16(¶ms,MODRM_src0,temp3.val16,0); //Write to register! 89 if (((temp3.val32>>7)==0) || ((temp3.val32>>7)==0x1FFFFFF)) FLAGW_OF(0); //Overflow is cleared when the high byte is a sign extension of the low byte? 90 else FLAGW_OF(1); 91 FLAGW_CF(FLAG_OF); //Same! 92 FLAGW_SF((temp3.val16&0x8000)>>15); //Sign! 93 FLAGW_PF(parity[temp3.val16&0xFF]); //Parity flag! 94 FLAGW_ZF((temp3.val16==0)?1:0); //Set the zero flag! 95}
32-bit:
1void CPU386_OP69() 2{ 3 memcpy(&info,¶ms.info[MODRM_src0],sizeof(info)); //Reg! 4 memcpy(&info2,¶ms.info[MODRM_src1],sizeof(info2)); //Second parameter(R/M)! 5 if (MODRM_MOD(params.modrm)==3) //Two-operand version? 6 { 7 debugger_setcommand("IMUL %s,%08X",info.text,imm32); //IMUL reg,imm32 8 } 9 else //Three-operand version? 10 { 11 debugger_setcommand("IMUL %s,%s,%08X",info.text,info2.text,imm32); //IMUL reg,r/m32,imm32 12 } 13 if (CPU[activeCPU].instructionstep==0) //First step? 14 { 15 if (MODRM_MOD(params.modrm)!=3) //Use R/M to calculate the result(Three-operand version)? 16 { 17 if (modrm_check32(¶ms,1,1)) return; //Abort on fault! 18 if (CPU80386_instructionstepreadmodrmdw(0,&temp1.val32,1)) return; //Read R/M! 19 temp1.val32high = 0; //Clear high part by default! 20 } 21 else 22 { 23 if (CPU80386_instructionstepreadmodrmdw(0,&temp1.val32,0)) return; //Read reg instead! Word register = Word register * imm32! 24 temp1.val32high = 0; //Clear high part by default! 25 } 26 ++CPU[activeCPU].instructionstep; //Next step! 27 } 28 if (CPU[activeCPU].instructionstep==1) //Second step? 29 { 30 temp2.val64 = imm32; //Immediate word is second/third parameter! 31 if ((temp1.val64 &0x80000000ULL)==0x80000000ULL) temp1.val64 |= 0xFFFFFFFF00000000ULL; 32 if ((temp2.val64 &0x80000000ULL)==0x80000000ULL) temp2.val64 |= 0xFFFFFFFF00000000ULL; 33 temp3.val64s = temp1.val64s; //Load and... 34 temp3.val64s *= temp2.val64s; //Signed multiplication! 35 CPU_apply286cycles(); //Apply the 80286+ cycles! 36 //We're writing to the register always, so no normal writeback! 37 ++CPU[activeCPU].instructionstep; //Next step! 38 } 39 modrm_write32(¶ms,MODRM_src0,temp3.val32); //Write to the destination(register)! 40 if (((temp3.val64>>31)==0ULL) || ((temp3.val64>>31)==0x1FFFFFFFFULL)) FLAGW_OF(0); //Overflow flag is cleared when high word is a sign extension of the low word! 41 else FLAGW_OF(1); 42 FLAGW_CF(FLAG_OF); //OF=CF! 43 FLAGW_SF((temp3.val32&0x80000000U)>>31); //Sign! 44 FLAGW_PF(parity[temp3.val32&0xFF]); //Parity flag! 45 FLAGW_ZF((temp3.val32==0)?1:0); //Set the zero flag! 46} 47 48void CPU386_OP6B() 49{ 50 memcpy(&info,¶ms.info[MODRM_src0],sizeof(info)); //Reg! 51 memcpy(&info2,¶ms.info[MODRM_src1],sizeof(info2)); //Second parameter(R/M)! 52 if (MODRM_MOD(params.modrm)==3) //Two-operand version? 53 { 54 debugger_setcommand("IMUL %s,%02X",info.text,immb); //IMUL reg,imm8 55 } 56 else //Three-operand version? 57 { 58 debugger_setcommand("IMUL %s,%s,%02X",info.text,info2.text,immb); //IMUL reg,r/m32,imm8 59 } 60
…Show last 35 lines
61 if (CPU[activeCPU].instructionstep==0) //First step? 62 { 63 if (MODRM_MOD(params.modrm)!=3) //Use R/M to calculate the result(Three-operand version)? 64 { 65 if (modrm_check32(¶ms,1,1)) return; //Abort on fault! 66 if (CPU80386_instructionstepreadmodrmdw(0,&temp1.val32,MODRM_src1)) return; //Read R/M! 67 temp1.val32high = 0; //Clear high part by default! 68 } 69 else 70 { 71 if (CPU80386_instructionstepreadmodrmdw(0,&temp1.val32,MODRM_src0)) return; //Read reg instead! Word register = Word register * imm32! 72 temp1.val32high = 0; //Clear high part by default! 73 } 74 ++CPU[activeCPU].instructionstep; //Next step! 75 } 76 if (CPU[activeCPU].instructionstep==1) //Second step? 77 { 78 temp2.val64 = (uint_64)immb; //Read unsigned parameter! 79 80 if (temp1.val64&0x80000000ULL) temp1.val64 |= 0xFFFFFFFF00000000ULL;//Sign extend to 64 bits! 81 if (temp2.val64&0x80ULL) temp2.val64 |= 0xFFFFFFFFFFFFFF00ULL; //Sign extend to 64 bits! 82 temp3.val64s = temp1.val64s * temp2.val64s; 83 CPU_apply286cycles(); //Apply the 80286+ cycles! 84 //We're writing to the register always, so no normal writeback! 85 ++CPU[activeCPU].instructionstep; //Next step! 86 } 87 88 modrm_write32(¶ms,MODRM_src0,temp3.val32); //Write to register! 89 if (((temp3.val64>>15)==0ULL) || ((temp3.val64>>15)==0x1FFFFFFFFFFFFFFULL)) FLAGW_OF(0); //Overflow is cleared when the high byte is a sign extension of the low byte? 90 else FLAGW_OF(1); 91 FLAGW_CF(FLAG_OF); //Same! 92 FLAGW_SF((temp3.val32&0x80000000U)>>31); //Sign! 93 FLAGW_PF(parity[temp3.val32&0xFF]); //Parity flag! 94 FLAGW_ZF((temp3.val32==0)?1:0); //Set the zero flag! 95}
There's something I notice about the test386.asm repository: it does seem to have a EE file(test386-EE-reference) for normal logs, but not another one for the logs for the undefined flags as well(the TEST_UNDEF enabled test)?
Test 0xE0 (undefined behaviours) depends on the CPU family. I didn't want to create a ref file for each family, with only the initial 10 lines or so which are really different. I can't create an additional "log file" either, because it's not a file, it's a byte write to an output port.
So 0xE0 halts in case of error.
I'm just asking to be sure: to the CPU, does a 2-operand opcode 69/6B(IMUL r16,imm8/16) even exist? Or does it always decode to 3 operands, with r/m and immediate being multiplied and stored into the reg operand?
I'm just asking to be sure: to the CPU, does a 2-operand opcode 69/6B(IMUL r16,imm8/16) even exist? Or does it always decode to 3 operands, with r/m and immediate being multiplied and stored into the reg operand?
@vladstamate: Add another one with SIB disp32 variant for the full 12 byte instruction length? Or another one for 32-bit 66h 0Fh OP modrm SIB disp32 imm32? Isn't that more than 12(13)? Although only newer CPUs have such 0F opcodes?
@vladstamate: Add another one with SIB disp32 variant for the full 12 byte instruction length? Or another one for 32-bit 66h 0Fh OP modrm SIB disp32 imm32? Isn't that more than 12(13)? Although only newer CPUs have such 0F opcodes?
Yes true. 12 with SIB. 386 does not have 0F IMULs AFAIK.