VOGONS


First post, by superfury

User metadata
Rank l33t++
Rank
l33t++

When a x86 fault occurs(any of the reported CPU faults), does it restore the state of the CPU in any way?

UniPCemu (in it's current release) currently restores SS, ESP, EBP, EFLAGS, DS, ES, FS, GS(and TR when task switching) to the last saved state(before any changes were made to the registers within the instruction(or before task switching, when task committing and after the instruction(only during a single-step exception)).

I've just added the CPL and backing segment descriptors for the restored segment selectors as well(thus restoring the complete CPU state on faults(except the TLB, which works independently anyways and will just act as normal)). Thus said CPU state is currently completely restored(essentially anything from the program's point of view is restored after returning, except the general purpose registers and table pointers(which it may not even see to begin with(when not in supervisor mode))).

Last edited by superfury on 2019-03-17, 18:51. Edited 1 time in total.

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

Reply 2 of 9, by superfury

User metadata
Rank l33t++
Rank
l33t++

All those registers aren't restored when it repeats. They're saved before each of those repeated instructions starts executing(before every single repeated step during REP CMPS*, so before it starts running and after each time it successfully(without faults) completes said action(that is, e.g. reading memory, storing it in a register and increasing the pointer registers, progressing from the running state to completed state, after which the EFLAGS is checked(and masked according to the CPU mode and CPU type or set(in the case of the 80(1)8X CPUs high 4 bits of FLAGS).

So each time any instruction starts(or repeats) or ends(only during debug exceptions), when starting to fetch new instruction data from the PIQ(and decoding it) or repeating one(a repeated instruction has completed one byte/word/dword and is starting the next byte/word/dword without fetching from the PIQ(keeping it's running state)) said saving of all possibly affected registers and descriptors(and CPL) occurs.

So those saved registers are always saved(using a flag for each and every one of them to mark them saved(except DS/ES/FS/GS and their descriptors, which use a single flag together, as they're always saved together(mainly for the V86 exception trapping to PL0 which affects them)) either before the instruction (or one of it's repeats with the REP prefix) starts, always pointing to the most recent state.

The exceptions to being saved before execution of said instructions are only a few:
- The pre-commit and post-commit state of the TR register and it's descriptor during task switching.
- The post-execution state during single-step exception(triggered by the Trap flag or debug exceptions on the current instructions that are supposed to point to the next instruction), the Single-step exception handler being triggered(with the Resume flag being cleared before the instruction starts).

When any fault occurs(any of the CPU's documented exceptions), any states from the saved information(when their loaded flag is set to a non-zero value(programmed as 1, but any value other than 0 will work) are restored from the image in the CPU's saved registers at the last commit point.

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

Reply 4 of 9, by peterferrie

User metadata
Rank Oldbie
Rank
Oldbie
jmarsh wrote:
superfury wrote:

EFLAGS

Don't think that's right, cmpsb/w and scasb/w with a rep prefix set new flags for every repetition.

How would you determine that? It's true for single-step, but otherwise the performance hit to do it on every iteration makes it seem unlikely to me.
Certainly on later Pentium CPUs, it's not true. The flags are updated lazily, and if you self-overwrite the instruction, the flags might even be wrong.

Reply 5 of 9, by jmarsh

User metadata
Rank Oldbie
Rank
Oldbie
peterferrie wrote:

How would you determine that?

Easy, an interrupt or page fault can cause an exception that allows the flags to be examined. Other relevant registers (esi/edi/ecx) must also be updated on every repeat so the instruction can be resumed properly after an exception.

Reply 6 of 9, by superfury

User metadata
Rank l33t++
Rank
l33t++

About EFLAGS, seeing as it can be modified during faults, it'll need to be restored? Imagine an V86 mode(EFLAGS.VM set) INT xxh (clearing said flag transitioning to PL0), then in normal protected mode (EFLAGS.VM cleared at that point due to transferring to PL0) faulting loading the CS register of the interrupt handler. Then, that fault would push EFLAGS with VM=0 to return to, breaking the V86 mode and making it a normal protected-mode task(imagine the iret back to the 'protected mode' CS selector, which may(imagine the horror of said privilege escalation) or may not be a valid code segment). The same can be said about SP and ESP.

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

Reply 7 of 9, by Stenzek

User metadata
Rank Newbie
Rank
Newbie

I tried to break instructions up into transactions, with read/modify/write/update parts. State such as flags changes are performed as part of the "update" stage. So for example if a page was not writable, the read/modify would succeed, exception would be raised on the write, which jmp's back to the dispatcher, before the flags are changed. Similar for segment loads, the values are checked and any exceptions are raised prior to the register actually being loaded.

If you look at the Intel manuals, the psuedocode for complex instructions/interrupts follows a similar process. Nested exceptions do create an interesting issue here, again, everything has to be validated prior to being loaded. IIRC on real hardware there are some things which are still undefined (e.g. exceptions during task switching), so if you wanted exact behavior here, you'd have to test on your target hardware anyway. But hitting such issues is unlikely, at least in all the software I've tried.

Reply 8 of 9, by peterferrie

User metadata
Rank Oldbie
Rank
Oldbie
jmarsh wrote:
peterferrie wrote:

How would you determine that?

Easy, an interrupt or page fault can cause an exception that allows the flags to be examined. Other relevant registers (esi/edi/ecx) must also be updated on every repeat so the instruction can be resumed properly after an exception.

No, that's not every iteration, that's on every exception, the same as single-step.

Reply 9 of 9, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've modified UniPCemu's saving/restoring algorithm to only (re)load the segment registers(together with their descriptor caches) when they're changed somewhere during the currently instruction, while before any commit point.
When a commit point is reached, it discards the variable(clearing it) that indicates any saved descriptors and their selectors being loaded and valid.
Then a fault is raised, it checks for said variable bits(up to 8 bits, one for each segment), and when it finds one set, said bit number is used to reload the segment descriptor cache and selector from the backup values(restoring it) and then clears the variable when all are processed(since all descriptors are restored to their old values after all no restoration is required).

That increases CPU emulation speed by a few percent(between 1-5%). Now it's running at 22%(in normal real mode) or up to 30%(when booting) speed at 3MIPS in IPS clocking mode.

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