VOGONS


test386.asm CPU tester

Topic actions

First post, by hottobar

User metadata
Rank Newbie
Rank
Newbie

Debugging an emulated CPU can be the most frustrating debugging experience ever, with so many moving pieces that can interfere with the instructions flow. You'll end reading huge trace logs with millions of executed instructions in the hope of finding that damn bug that keeps Windows from booting.

To try to tame the problem I created a 386 CPU tester, written in NASM assembly, started as a derivative work of PCjs.

https://github.com/barotto/test386.asm

In order to test the CPU in a repeatable and predictable way, test386.asm is used as a BIOS replacement, so it does not depend on any OS.
It executes a series of specific tests, both in real and protected mode, reporting a diagnostic code that can be used to determine any possible problem.

You can read further details in the README.

Suggestions, issue reporting, and code contributions are very well accepted.

Please note that this program has been tested only on Bochs and on my emulator (IBMulator) and it's still incomplete, so it currently tests only a small subset of every possible instruction combination. The full list of tested opcodes is included in the repo.

Reply 1 of 178, by hottobar

User metadata
Rank Newbie
Rank
Newbie

@superfury
answering your questions from the other thread, the SDL crash is definitely not related to test386.asm, which doesn't even use the video output.
The COM port is not mandatory and can be disabled.
The LPT port is only used by writing ASCII bytes to 3BCh (unless you changed the address); see print_p.asm
If your CPU gets halted with diagnostic code 00, you should check your emulator's trace log because it could be a problem with the the very first test which is related to conditional jumps and loops.

Reply 2 of 178, by superfury

User metadata
Rank l33t++
Rank
l33t++

It seems to be even more simple: the first diagnostic code (00h) isn't ever written to the I/O port, thus it goes wrong even before that(the 00h is the default value that's initialized when the emulation starts). I've tested and verified already that the first two instructions(the JMP at F000:FFF0 and CLI it jumps to) are correctly decoded. Somewhere after that, SDL 2.0.6 crashes in it's own thread(it doesn't seem to be the cause of my application itself afaik).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 3 of 178, by vladstamate

User metadata
Rank Oldbie
Rank
Oldbie

I am running it now too on CAPE. I've manged to compile with no trouble with NASM (with some warnings). Seems to be fine for me, as in it already showed issues, so I am fixing stuff now.

Thank you for this hottbar, this is superuseful. Me and superfury were looking for a long time for something like this.

YouTube channel: https://www.youtube.com/channel/UC7HbC_nq8t1S9l7qGYL0mTA
Collection: http://www.digiloguemuseum.com/index.html
Emulator: https://sites.google.com/site/capex86/
Raytracer: https://sites.google.com/site/opaqueraytracer/

Reply 4 of 178, by hottobar

User metadata
Rank Newbie
Rank
Newbie
vladstamate wrote:

it already showed issues, so I am fixing stuff now.

Happy to hear! This tool helped me to fix many bugs as well.
I hope some day it'll be able to test much more because my CPU still has some problems that this program is not able to detect.

Reply 5 of 178, by superfury

User metadata
Rank l33t++
Rank
l33t++

Something's going very wrong with your test ROM:

Edit: Whoops, removed the ROM directory, causing the internal BIOS to go haywire instead of loading a valid ROM...

It seems to go wrong with the JCXZ check?

Filename
debugger.log
File size
33.14 KiB
Downloads
70 downloads
File comment
JCXZ check jumping to HLT.
File license
Fair use/fair dealing exception

Edit: It also doesn't seem the POST macro is being assembled, it resolves to no code being inserted? The conditional jump tests start immediately after CLI, the code "POST 0" isn't being assembled at all?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 6 of 178, by superfury

User metadata
Rank l33t++
Rank
l33t++

After fixing some JCXZ/LOOP and related three instructions' bugs to use the address, it now continues onwards?

It eventually gets to some IMULD/DIVD instructions, which makes it crash to 0000:0000 due to faults?

This is the latest test log(progress 😁):

debugger_NASM_UniPCemu_20171017_1437.zip

Now there's a problem with the (I)MULD/DIVD instructions?

F000:00000457 B0 01 movb al,01
F000:00000459 BA 84 00 movw dx,0084
F000:0000045C EE out dx,al
F000:0000045D 66 B8 01 00 00 80 movd eax,80000001
F000:00000463 66 F7 E8 imuld eax
F000:00000466 66 B8 11 22 33 44 movd eax,44332211
F000:0000046C 66 89 C3 movd ebx,eax
F000:0000046F 66 B9 55 66 77 88 movd ecx,88776655
F000:00000475 66 F7 E1 muld ecx
00:01:34:77.07472: Interrupt 00=0000:00000000@F000:0478(F7); ERRORCODE: -1
F000:00000478 66 F7 F1 divd ecx
0000:00000000 00 00 addb [ds:bx+si],al

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 7 of 178, by vladstamate

User metadata
Rank Oldbie
Rank
Oldbie

It seems like you are generating an exception (overflow?)

YouTube channel: https://www.youtube.com/channel/UC7HbC_nq8t1S9l7qGYL0mTA
Collection: http://www.digiloguemuseum.com/index.html
Emulator: https://sites.google.com/site/capex86/
Raytracer: https://sites.google.com/site/opaqueraytracer/

Reply 9 of 178, by superfury

User metadata
Rank l33t++
Rank
l33t++

Can anyone see what's invalid behaviour about that code? It multiplies 0x80000001(unsigned, which is -2147483647 when used with IMULD) and multiplies it with itself? Thus resulting in EDX=C0000000 EAX=00000001? Then it loads EAX with 44332211h and copies that to EBX. Then it loads ECX with 88776655h and multiplies it with itself(unsigned multiply, resulting in EDX=48BF146A EAX=9BEDD839. Then it divides 48BF146A9BEDD839h with 88776655h, which overflows for some unknown reason? That shouldn't happen?

extern byte tmps,tmpp; //Sign/parity backup!

extern byte CPU_databussize; //Current data bus size!

extern byte tempAL;
uint_32 tempEAX;
uint_64 tempEDXEAX;

OPTINLINE void op_div32(uint_64 valdiv, uint_32 divisor) {
//word v1, v2;
if ((!divisor) && (CPU[activeCPU].internalinstructionstep==0)) //First step?
{
//Timings always!
++CPU[activeCPU].internalinstructionstep; //Next step after we're done!
CPU[activeCPU].cycles_OP += 1; //Take 1 cycle only!
CPU[activeCPU].executed = 0; //Not executed yet!
return;
}
uint_32 quotient, remainder; //Result and modulo!
byte error, applycycles; //Error/apply cycles!
CPU80386_internal_DIV(valdiv,divisor,&quotient,&remainder,&error,32,2,6,&applycycles); //Execute the unsigned division! 8-bits result and modulo!
if (error==0) //No error?
{
REG_EAX = quotient; //Quotient!
REG_EDX = remainder; //Remainder!
}
else //Error?
{
CPU_exDIV0(); //Exception!
return; //Exception executed!
}
if (applycycles) /* No 80286+ cycles instead? */
{
if (MODRM_EA(params)) //Memory?
{
CPU[activeCPU].cycles_OP += 6 - EU_CYCLES_SUBSTRACT_ACCESSREAD; //Mem max!
}
}
}
//Universal DIV instruction for x86 DIV instructions!
/*

Parameters:
val: The value to divide
divisor: The value to divide by
quotient: Quotient result container
remainder: Remainder result container
error: 1 on error(DIV0), 0 when valid.
resultbits: The amount of bits the result contains(16 or 8 on 8086) of quotient and remainder.
SHLcycle: The amount of cycles for each SHL.
ADDSUBcycle: The amount of cycles for ADD&SUB instruction to execute.

*/
void CPU80386_internal_DIV(uint_64 val, uint_32 divisor, uint_32 *quotient, uint_32 *remainder, byte *error, byte resultbits, byte SHLcycle, byte ADDSUBcycle, byte *applycycles)
{
uint_64 temp, temp2, currentquotient; //Remaining value and current divisor!
byte shift; //The shift to apply! No match on 0 shift is done!
temp = val; //Load the value to divide!
*applycycles = 1; //Default: apply the cycles normally!
if (divisor==0) //Not able to divide?
{
*quotient = 0;
*remainder = temp; //Unable to comply!
*error = 1; //Divide by 0 error!
return; //Abort: division by 0!
}

if (CPU_apply286cycles()) /* No 80286+ cycles instead? */
{
SHLcycle = ADDSUBcycle = 0; //Don't apply the cycle counts for this instruction!
*applycycles = 0; //Don't apply the cycles anymore!
}

temp = val; //Load the remainder to use!
*quotient = 0; //Default: we have nothing after division!
nextstep:
//First step: calculate shift so that (divisor<<shift)<=remainder and ((divisor<<(shift+1))>remainder)
temp2 = divisor; //Load the default divisor for x1!
if (temp2>temp) //Not enough to divide? We're done!
{
goto gotresult; //We've gotten a result!
}
currentquotient = 1; //We're starting with x1 factor!
for (shift=0;shift<(resultbits+1);++shift) //Check for the biggest factor to apply(we're going from bit 0 to maxbit)!
{
if ((temp2<=temp) && ((temp2<<1)>temp)) //Found our value to divide?
{
CPU[activeCPU].cycles_OP += SHLcycle; //We're taking 1 more SHL cycle for this!
break; //We've found our shift!
}
temp2 <<= 1; //Shift to the next position!
currentquotient <<= 1; //Shift to the next result!
CPU[activeCPU].cycles_OP += SHLcycle; //We're taking 1 SHL cycle for this! Assuming parallel shifting!
}
if (shift==(resultbits+1)) //We've overflown? We're too large to divide!
{
*error = 1; //Raise divide by 0 error due to overflow!
return; //Abort!
}
Show last 20 lines
	//Second step: substract divisor<<n from remainder and increase result with 1<<n.
temp -= temp2; //Substract divisor<<n from remainder!
*quotient += currentquotient; //Increase result(divided value) with the found power of 2 (1<<n).
CPU[activeCPU].cycles_OP += ADDSUBcycle; //We're taking 1 substract and 1 addition cycle for this(ADD/SUB register take 3 cycles)!
goto nextstep; //Start the next step!
//Finished when remainder<divisor or remainder==0.
gotresult: //We've gotten a result!
if (temp>((1<<resultbits)-1)) //Modulo overflow?
{
*error = 1; //Raise divide by 0 error due to overflow!
return; //Abort!
}
if (*quotient>((1<<resultbits)-1)) //Quotient overflow?
{
*error = 1; //Raise divide by 0 error due to overflow!
return; //Abort!
}
*remainder = temp; //Give the modulo! The result is already calculated!
*error = 0; //We're having a valid result!
}

Can anyone see what's going wrong here?

Edit: After some checking, I've found various problems that were causing those (I)MUL and (I)DIV instructions to fail:
- Overflow due to 32-bit shifting off the range of the variable to check for overflow, causing an invalid overflow detection flag to be set when dividing. It caused a Divide by 0 fault incorrectly(it would actually fit within 32-bits, but the value to check against(0xFFFFFFFF) was truncated to be 00000000h due to wrong typing of the shifting number("1" needing to be "1ULL" to prevent shifting off 32-bit boundaries and becoming truncated to 0).
- Truncation after multiply due to type conversions being 32-bit instead of 64-bit(required for getting a 64-bit result out of the multiply operation).

Having fixed this, the BIOS now continues on to the next step(Diagnostics code 02h).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 10 of 178, by hottobar

User metadata
Rank Newbie
Rank
Newbie
superfury wrote:

Edit: It also doesn't seem the POST macro is being assembled, it resolves to no code being inserted? The conditional jump tests start immediately after CLI, the code "POST 0" isn't being assembled at all?

Can you upload your intermediate assembler source-listing file? This can be generated by NASM with the -l command line switch (see here).

This file can be used to see the assembler result in a redable format. With it you can compare your CPU log with what the ROM is supposed to execute.

Reply 11 of 178, by superfury

User metadata
Rank l33t++
Rank
l33t++

Still, that "POST 0" macro call isn't being assembled at all:

63                                  ;
64 ; We set our exception handlers at fixed addresses to simplify interrupt gate descriptor initialization.
65 ;
66 OFF_INTDEFAULT equ OFF_ERROR
67 OFF_INTDIVERR equ OFF_INTDEFAULT+0x200
68 OFF_INTPAGEFAULT equ OFF_INTDIVERR+0x200
69 OFF_INTBOUND equ OFF_INTPAGEFAULT+0x200
70
71 ;
72 ; Output a byte to the POST port, destroys al and dx
73 ;
74 %macro POST 1
75 mov al, 0x%1
76 mov dx, POST_PORT
77 out dx, al
78 %endmacro
79
80
81 header:
82 00000000 746573743338362E61- db COPYRIGHT
82 00000009 736D20284329203230-
82 00000012 31322D32303135204A-
82 0000001B 65666620506172736F-
82 00000024 6E732C202843292032-
82 0000002D 303137204D6172636F-
82 00000036 20426F72746F6C696E-
82 0000003F 202020202020
83
84 cpuTest:
85 00000045 FA cli
86
87
88 ; ==============================================================================
89 ; Real mode tests
90 ; ==============================================================================
91
92 ;
93 ; Conditional jumps
94 ;
95 %include "tests/jcc_m.asm"
1 <1> ;
2 <1> ; Tests conditional relative jumps.
3 <1> ; Uses: AX, ECX, Flags
4 <1> ;
5 <1> ; Opcodes tested, with positive and negative offsets:
6 <1> ;
7 <1> ; rel8 rel16/32 mnemonic condition
8 <1> ; 70 0F80 JO OF=1
9 <1> ; 71 0F81 JNO OF=0
10 <1> ; 72 0F82 JC CF=1
11 <1> ; 73 0F83 JNC CF=0
12 <1> ; 74 0F84 JZ ZF=1
13 <1> ; 75 0F85 JNZ ZF=0
14 <1> ; 76 0F86 JBE CF=1 || ZF=1
15 <1> ; 77 0F87 JA CF=0 && ZF=0
16 <1> ; 78 0F88 JS SF=1
17 <1> ; 79 0F89 JNS SF=0
18 <1> ; 7A 0F8A JP PF=1
19 <1> ; 7B 0F8B JNP PF=0
20 <1> ; 7C 0F8C JL SF!=OF
Show last 52 lines
    21                              <1> ; 7D    0F8D     JNL      SF=OF
22 <1> ; 7E 0F8E JLE ZF=1 || SF!=OF
23 <1> ; 7F 0F8F JNLE ZF=0 && SF=OF
24 <1> ; E3 JCXZ CX=0
25 <1> ; E3 JECXZ ECX=0
26 <1> ;
27 <1> %macro testJcc 1
28 <1> mov ah, PS_CF
29 <1> sahf ; don't use the stack (pushf/popf)
30 <1> jnc %%err1 ; 73 / 0F83 JNC CF=0
31 <1> jc %%jcok ; 72 / 0F82 JC CF=1
32 <1> hlt
33 <1> %%jz:
34 <1> mov ah, PS_ZF
35 <1> sahf
36 <1> jnz %%err1 ; 75 / 0F85 JNZ ZF=0
37 <1> jz %%jzok ; 74 / 0F84 JZ ZF=1
38 <1> hlt
39 <1> %%jp:
40 <1> mov ah, PS_PF
41 <1> sahf
42 <1> jnp %%err1 ; 7B / 0F8B JNP PF=0
43 <1> jp %%jpok ; 7A / 0F8A JP PF=1
44 <1> hlt
45 <1> %%js:
46 <1> mov ah, PS_SF
47 <1> sahf
48 <1> jns %%err1 ; 79 / 0F89 JNS SF=0
49 <1> js %%jsok ; 78 / 0F88 JS SF=1
50 <1> hlt
51 <1> %%jna:
52 <1> mov ah, PS_ZF|PS_CF
53 <1> sahf
54 <1> ja %%err1 ; 77 / 0F87 JA CF=0 && ZF=0
55 <1> jna %%jnaok ; 76 / 0F86 JBE CF=1 || ZF=1
56 <1> %%next1:
57 <1> jmp %%jnc
58 <1>
59 <1> %if %1==16
60 <1> times 128 hlt
61 <1> %endif
62 <1>
63 <1> %%err1:
64 <1> hlt
65 <1>
66 <1> ; test negative offsets
67 <1> %%jcok: jc %%jz
68 <1> %%jzok: jz %%jp
69 <1> %%jpok: jp %%js
70 <1> %%jsok: js %%jna
71 <1> %%jnaok: jna %%next1
72 <1>

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 12 of 178, by hottobar

User metadata
Rank Newbie
Rank
Newbie

It seems like you have an old version of the src/test386.asm file. I've added the POST 0 macro only recently, after you noticed that it wasn't emitting a 00h diagnostic code for the first test.

Here's mine so you can compare.

Filename
test386.lst.zip
File size
60.72 KiB
Downloads
71 downloads
File license
Fair use/fair dealing exception

Reply 13 of 178, by superfury

User metadata
Rank l33t++
Rank
l33t++

After fixing the MOV Sw instructions to allow 32-bit register reading/writing besides 16-bit Segment registers, now it advanced to Diagnostics code 04h(Near CALL/Loading pointers?).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 14 of 178, by superfury

User metadata
Rank l33t++
Rank
l33t++

It seems to go wrong with the Near CALL test?

Filename
debugger.log
File size
37.3 KiB
Downloads
67 downloads
File comment
Near CALL test executing using the normal detailed logging.
File license
Fair use/fair dealing exception

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 15 of 178, by superfury

User metadata
Rank l33t++
Rank
l33t++

After fixing the stack PUSH/POP operations to depend on the situation(instructions based on operand size and hardware-based(interrupts/task switching etc) being specific), it now properly continues on to POST 09h(Initialize and enter protected mode), which seems to go wrong somewhere.

Edit: Having fixed the 0F01 opcode, it now enters protected mode and immediately(the BIOS itself) crashes after loading CR0 with paging, triple faulting.

It's a problem with the PDE/PTE(Paging)? The linear address being 0xFA079(the JMP instruction's start?). This translates to PDE containing 0x00002007, which points to a PTE containing 0x56781234? This fires the Page Fault handler? Is that correct?

Edit: Found a bug in the Paging unit that caused it to shift all addresses from PDE/PTE to shift right with 12 instead of not shifting at all(it's the address, masking is enough to get the physical addresses). Now Paging is working properly and it continues into normal protected mode.

Now it reaches the "lea ebp, [esp-4]" instruction, which causes an #UD because it thinks the [ESP] base of the SIB byte is illegal? Isn't that illegal?

Edit: Fixing that to allow that, I now see it translating the linear address 0x1000 to PDE 0, PTE 1, which is past 2MB(0x200000 being the PTE address)?

Edit: Having fixed the Paging unit and patching the use of ESP-4 for SIB parameters, it now continues to the next test: code 0Bh.

Edit: Found an strange bug: Using opcode 0x8C(MOV Ev,Sw), moves to 32-bit registers are zero-extended to 32-bits, while moves to memory seem to only write the lower half(leaving the upper 16-bits unchanged)?

Edit: Now it reaches test 0xC, which goes wrong with the MOVZ/SX instructions?

Edit: Having fixed those instruction properly(after consulting the 80386 manual and seeing the 16-bit/32-bit bugs, fixing the timing table contents and rewriting modr/m parsing for those instructions), it now gets to the next phase(phase 0xD or onwards).
Edit: It now gets to Diagnostics code 0xE, which crashes due to a fault.

Edit: It seems to do trange things(correct output, though) with this:

testLEA32 [eax * 2], 0x00000002

Then it executes the following, which fails the test(also extremely strange code not matching the assembly in the repository):

testLEA32 [ebx * 4], 0x00000008

The Common format log of these cases:

0010:00000F96 50 pushd eax
0010:00000F97 8D 04 00 lead eax,[ds:eax+eax*1]
0010:00000F9A 83 F8 02 cmpd eax,0002
0010:00000F9D 0F 85 5D B0 00 00 jne 0000c000
0010:00000FA3 58 popd eax
0010:00000FA4 50 pushd eax
0010:00000FA5 8D 04 9D lead eax,[ds:00000000+ebx*4]
0010:00000FA8 00 00 addb [ds:eax],al
0010:00000FAA 00 00 addb [ds:eax],al
0010:00000FAC 83 F8 08 cmpd eax,0008
0010:00000FAF 0F 85 4B B0 00 00 jne 0000c000
0010:00000FB5 58 popd eax
0010:00000FB6 50 pushd eax
0010:00000FB7 8D 04 CD lead eax,[ds:00000000+ecx*8]
0010:00000FBA 00 00 addb [ds:eax],al
0010:00000FBC 00 00 addb [ds:eax],al
0010:00000FBE 83 F8 20 cmpd eax,0020
0010:00000FC1 0F 85 39 B0 00 00 jne 0000c000
0010:00000FC7 58 popd eax
0010:00000FC8 50 pushd eax
0010:00000FC9 8D 44 00 40 lead eax,[ds:40+eax+eax*1]
0010:00000FCD 83 F8 42 cmpd eax,0042
0010:00000FD0 0F 85 2A B0 00 00 jne 0000c000
0010:00000FD6 58 popd eax
0010:00000FD7 50 pushd eax
0010:00000FD8 8D 04 9D lead eax,[ds:00000000+ebx*4]
0010:00000FDB 40 incd eax
0010:00000FDC 00 00 addb [ds:eax],al
00:10:11:29.04144: #GP fault(00000018)!
0010:00000FDE 00 83 F8 48 0F 85 addb [ds:ebx+850f48f8],al
0010:0000C000 FA cli
0010:0000C001 F4 hlt
0010:0000C002 F4 <hlt>

Those are rows 536+ of the test386.asm (https://github.com/barotto/test386.asm/blob/m … est386.asm#L536) executing in UniPCemu.

Edit: There was a problem with SIB with 32-bit displacement. Fixing this makes it continue to POST 10h. That one fails(what a surpriseXD).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 17 of 178, by vladstamate

User metadata
Rank Oldbie
Rank
Oldbie
peterferrie wrote:

This tester needs some of the corner cases. I shall make a PR for them. 😀

Yes please do! The emulator developers will love you.

YouTube channel: https://www.youtube.com/channel/UC7HbC_nq8t1S9l7qGYL0mTA
Collection: http://www.digiloguemuseum.com/index.html
Emulator: https://sites.google.com/site/capex86/
Raytracer: https://sites.google.com/site/opaqueraytracer/

Reply 19 of 178, by superfury

User metadata
Rank l33t++
Rank
l33t++

It seems test 10h fails because of a #GP fault on the REP STOSB instruction(the first subtest's first string instruction), errorcode 0? That shouldn't happen?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io