VOGONS


First post, by superfury

User metadata
Rank l33t++
Rank
l33t++

What happens when a 16-bit trap gate is used from V86 mode? What is the width of the segment registers(GS,FS,DS,ES) and SS and ESP that's pushed on the stack?

Are they just pushed as 16-bit values(so essentially a PUSH GS, PUSH FS, PUSH DS, PUSH ES, PUSH SS, PUSH SP instructions)? Or are some or all forced to become 32-bit pushes? Are they zero-extended in 32-bit mode(the segment registers)?

Currently, UniPCemu pushes them all as 16-bit memory writes and (E)SP decreases, except ESP(which is always a 32-bit decrease of ESP and memory write).

Edit: Let me rephrase the question: what happens if a Virtual 8086 monitor handler in the IDT is using a 16-bit interrupt or trap gate instead of a 32-bit one?

Different sources say different things:
https://www.felixcloutier.com/x86/intn:into:int3:int1 :

TempSS ← SS; TempESP ← ESP; SS ← NewSS; ESP ← NewESP; (* Following pushes are 16 bits for 16-bit IDT gates and 3 […]
Show full quote

TempSS ← SS;
TempESP ← ESP;
SS ← NewSS;
ESP ← NewESP;
(* Following pushes are 16 bits for 16-bit IDT gates and 32 bits for 32-bit IDT gates;
Segment selector pushes in 32-bit mode are padded to two words *)
Push(GS);
Push(FS);
Push(DS);
Push(ES);
Push(TempSS);
Push(TempESP);
Push(TempEFlags);
Push(CS);
Push(EIP);
GS ← 0; (* Segment registers made NULL, invalid for use in protected mode *)
FS ← 0;
DS ← 0;
ES ← 0;
CS ← Gate(CS); (* Segment descriptor information also loaded *)
CS(RPL) ← 0;
CPL ← 0;
IF IDT gate is 32-bit
THEN
EIP ← Gate(instruction pointer);
ELSE (* IDT gate is 16-bit *)
EIP ← Gate(instruction pointer) AND 0000FFFFH;
FI;
(* Start execution of new routine in Protected Mode *)
END;

However, IRET (https://www.felixcloutier.com/x86/iret:iretd ) seems to deny that behaviour:

RETURN-TO-VIRTUAL-8086-MODE: (* Interrupted procedure was in virtual-8086 mode: PE = 1, CPL=0, VM = 1 in flag image *) I […]
Show full quote

RETURN-TO-VIRTUAL-8086-MODE:
(* Interrupted procedure was in virtual-8086 mode: PE = 1, CPL=0, VM = 1 in flag image *)
IF EIP not within CS limit
THEN #GP(0); FI;
EFLAGS ← tempEFLAGS;
ESP ← Pop();
SS ← Pop(); (* Pop 2 words; throw away high-order word *)
ES ← Pop(); (* Pop 2 words; throw away high-order word *)
DS ← Pop(); (* Pop 2 words; throw away high-order word *)
FS ← Pop(); (* Pop 2 words; throw away high-order word *)
GS ← Pop(); (* Pop 2 words; throw away high-order word *)
CPL ← 3;
(* Resume execution in Virtual-8086 mode *)

The 80386 manual ( https://pdos.csail.mit.edu/6.828/2012/readings/i386/INT.htm ) seems to ignore the gate size:

INTERRUPT-FROM-V86-MODE: TempEFlags := EFLAGS; VM := 0; TF := 0; IF service through Interrupt Gate THEN IF = 0; T […]
Show full quote

INTERRUPT-FROM-V86-MODE:
TempEFlags := EFLAGS;
VM := 0;
TF := 0;
IF service through Interrupt Gate THEN IF = 0;
TempSS := SS;
TempESP := ESP;
SS := TSS.SS0; (* Change to level 0 stack segment *)
ESP := TSS.ESP0; (* Change to level 0 stack pointer *)
Push(GS); (* padded to two words *)
Push(FS); (* padded to two words *)
Push(DS); (* padded to two words *)
Push(ES); (* padded to two words *)
GS := 0;
FS := 0;
DS := 0;
ES := 0;
Push(TempSS); (* padded to two words *)
Push(TempESP);
Push(TempEFlags);
Push(CS); (* padded to two words *)
Push(EIP);
CS:EIP := selector:offset from interrupt gate;
(* Starts execution of new routine in 80386 Protected Mode *)

Confirmed by IRET in the same manual (https://pdos.csail.mit.edu/6.828/2012/readings/i386/IRET.htm):

FI;STACK-RETURN-TO-V86: (* Interrupted procedure was in V86 mode *) IF return CS selector RPL < > 3 THEN #GP(Return select […]
Show full quote

FI;STACK-RETURN-TO-V86: (* Interrupted procedure was in V86 mode *)
IF return CS selector RPL < > 3
THEN #GP(Return selector);
FI;
IF top 36 bytes of stack not within limits
THEN #SS(0);
FI;
Examine return CS selector and associated descriptor:
IF selector is null, THEN #GP(0); FI;
IF selector index not within its descriptor table limits;
THEN #GP(Return selector);
FI;
IF AR byte does not indicate code segment
THEN #GP(Return selector);
FI;
IF code segment DPL not = 3;
THEN #GP(Return selector);
FI;
IF code segment not present
THEN #NP(Return selector);
FI;

Examine return SS selector and associated descriptor:
IF selector is null THEN #GP(0); FI;
IF selector index not within its descriptor table limits
THEN #GP(SS selector);
FI;
IF selector RPL not = RPL of return CS selector
THEN #GP(SS selector);
FI;
IF AR byte does not indicate a writable data segment
THEN #GP(SS selector);
FI;
IF stack segment DPL not = RPL of return CS selector
THEN #GP(SS selector);
FI;
IF SS not present
THEN #NP(SS selector);
FI;

IF instruction pointer not within code segment limit THEN #GP(0);
FI;
EFLAGS := SS:[eSP + 8]; (* Sets VM in interrupted routine *)
EIP := Pop();
CS := Pop(); (* CS behaves as in 8086, due to VM = 1 *)
throwaway := Pop(); (* pop away EFLAGS already read *)
ES := Pop(); (* pop 2 words; throw away high-order word *)
DS := Pop(); (* pop 2 words; throw away high-order word *)
FS := Pop(); (* pop 2 words; throw away high-order word *)
GS := Pop(); (* pop 2 words; throw away high-order word *)
IF CS.RPL > CPL
THEN
TempESP := Pop();
TempSS := Pop();
SS:ESP := TempSS:TempESP;
FI;

But oddly enough, that one checks for CS.RPL(which doesn't exist with V86 segment selectors) as well as having the loads of TempESP/TempSS in the wrong place(it's supposed to be directly after the POP CS, according to the interrupt handling.

Simply said, it confuses the hell out of me what's actually correct behaviour for x86 processors concerning Virtual 8086 mode interrupts and IRET.

Anyone?

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