VOGONS


First post, by superfury

User metadata
Rank l33t++
Rank
l33t++

When, in real mode, the PE bit of the CR0 register is set, what is the resulting CPL when the instruction is complete and the next instruction is executed? Some documentation I find using a simple google search specify that the low 2 bits of CS(the RPL bits) should be zeroed. Is the current privilege level always zero in that case, when executing the far jump? Or is that dependent on the DPL of the SS descriptor? Let's say a program like HIMEM.SYS executes LOADALL with SS.DPL being 3, while CS.RPL when returning to real mode is 0(the function entering protected mode again). When the function sets the PE bit in CR0 and executes the far jump to a privilege level 0 kernel-mode segment, will it fault because the CPL is 3?

I'd assume that SS.DPL is ALWAYS the CPL of the CPU? So when it changes, the CPL changes to that value. Thus CPL is ALWAYS equal to the SS.DPL descriptor cache value? So, in effect, the CS.DPL value isn't used for this, only the CS.RPL is used with the SS.C(actually R)PL,RPL,DPL calculations when loading segments?

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

Reply 1 of 13, by peterferrie

User metadata
Rank Oldbie
Rank
Oldbie

Simply setting CR0.PE and then jumping to enable it will land you in ring 0. No selectors exist until they are reloaded (they will retain their original mappings and can still be used as segment registers).

Reply 2 of 13, by superfury

User metadata
Rank l33t++
Rank
l33t++

I mean that after the MOV CR0,EAX and before JMP segment:offset, what privilege mode is the JMP itself executing at? Segment descriptors continue to work normally(as they do in real mode, which is essentially a modified protected mode?)? So the SS descriptor(containing real mode values or different CPL(SS.DPL is CPL)) should determine the CPL when entering protected mode(also real mode CPL)? So LOADALL with SS.DPL being 3 and CR0.PE=0 should create a unreal mode running in ring 3? Then, when of course loading SS, SS.DPL stays unchanged, thus CPL stays at 3? Then, when entering protected mode again(by setting CR0.PE), it should still be running at CPL 3? SS.DPL determines CPL after all? Of course, jumping loads the segment selector according to protected mode semantics(when CR0.PE is set), thus verifying CS.RPL, loaded descriptor.DPL and SS.DPL(which is CPL) against each other in a privilege check? So I'd expect the jump to a privilege level 0 entry to cause a general protection fault(maybe even double or triple fault, depending on the GDT used)?

Although, it can be easily tested by loading SS.DPL using LOADALL, then executing a privileged instruction that's real mode compatible? Like MOV CR0,EAX? Or even easier: LGDT?

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

Reply 3 of 13, by crazyc

User metadata
Rank Member
Rank
Member

You're probably right though I doubt anyone's actually tested it. You'd get a quick triple fault the moment you tried anything that required a privilege check unless you set up a vaild idt, gdt and tss (what would happen in real mode? maybe just a int Dh? would the ss dpl be reset then?). This guy did something on the same track by using RSM to set ss dpl to 0 in VM86 mode.

Reply 4 of 13, by superfury

User metadata
Rank l33t++
Rank
l33t++

Logically, it would cause an INT Dh(#GP fault) without error code, SS and it's descriptor unmodified(and still working for the interrupt call, since it isn't using CPL during the interrupt handling itself, using real mode semantics). Of course, a ISR like HIMEM.SYS would try to enter protected mode, causing another #GP fault, with it repeating until the stack exhausts? Then it would cause a stack fault? So, in short, with HIMEM.SYS loaded it would result in a stack fault, unless you're setting up your own #GP handler?
This would apply to any privileged instruction(As long as SS.DPL isn't zero)?

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

Reply 5 of 13, by crazyc

User metadata
Rank Member
Rank
Member

I wouldn't really expect any consistent behavior between different cpu models since it's very undefined. Maybe some world do a real mode short circuit of all the privilege checks. Others might attempt a privilege level change and try to load the ring 0 stack from wherever the tr points at the moment probably resulting in a triple fault.

Reply 6 of 13, by superfury

User metadata
Rank l33t++
Rank
l33t++

You could of course try to find out if CPL is affected by setting up a #GP handler and see if it triggers after a LOADALL LGDT with loadall loading the current and compatible values, only setting SS.DPL to 1-3? If the #GP handler is triggered, real mode CPL does exist as SS.DPL(I assume it does, since I can't find anywhere that LOADALL performs differently in the various modes loading the segment descriptors and CPL)?

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

Reply 7 of 13, by peterferrie

User metadata
Rank Oldbie
Rank
Oldbie
superfury wrote:

I mean that after the MOV CR0,EAX and before JMP segment:offset, what privilege mode is the JMP itself executing at?

It's still real mode. Until CS is reloaded, the mode doesn't change. Even if you load selector values into the other segment registers, they will read real-mode memory at those weird segments.
Anything that requires active protected-mode will trigger a fault. Anything that requires just ring 0 will work as expected.

Reply 8 of 13, by superfury

User metadata
Rank l33t++
Rank
l33t++
peterferrie wrote:
superfury wrote:

I mean that after the MOV CR0,EAX and before JMP segment:offset, what privilege mode is the JMP itself executing at?

It's still real mode. Until CS is reloaded, the mode doesn't change. Even if you load selector values into the other segment registers, they will read real-mode memory at those weird segments.
Anything that requires active protected-mode will trigger a fault. Anything that requires just ring 0 will work as expected.

Afaik that cannot be true? Since reloading CS causes the descriptor to be loaded from memory, it must already be in protected mode(it loads the descriptor from the GDT after all, including privilege checks and RPL in the low 2 bits of the CS segment selector etc.)? Setting bits 0&1 of CS causes a fault because of that(MAX(CPL,RPL)>0 with a privileged jump to CPL 0).

Also, the contents of SS.DPL(or in other words CPL) IS valid after setting CR0.PE. That much is certain(since it's already operating and parsing using protected-mode semantics, otherwise the jump would internally load CS instead of referring to the descriptor in the GDT). The only question left is: does LOADALL loading CR0.PE=0 and SS.DPL set to non-zero result in a real-mode with CPL being non-zero? Or is it forced to zero(or 3 during Virtual 8086 mode), while all other modes use SS.DPL as CPL? Thus switching to Real mode changes CPL to 0, ignoring SS.DPL until setting the CR0.PE bit again? Looking at Dosbox-X's CPU core, that seems the case. Although I can't find any definite information about real CPUs regarding such behaviour(anybody got some hard documentation on that)?

Edit: Currently, a simple implementation switching modes:

void updateCPUmode() //Update the CPU mode!
{
static const byte modes[4] = { CPU_MODE_REAL, CPU_MODE_PROTECTED, CPU_MODE_REAL, CPU_MODE_8086 }; //All possible modes (VM86 mode can't exist without Protected Mode!)
byte mode = 0; //Buffer new mode to start using for comparison!
if (!CPU[activeCPU].registers)
{
CPU_initRegisters(); //Make sure we have registers!
if (!CPU[activeCPU].registers) CPU[activeCPU].registers = &dummyregisters; //Dummy registers!
}
mode = FLAG_V8; //VM86 mode?
mode <<= 1;
mode |= (CPU[activeCPU].registers->CR0&CR0_PE); //Protected mode?
mode = modes[mode]; //What is the newly set mode, if changed?
if (unlikely(mode!=CPUmode)) //Mode changed?
{
if ((CPUmode==CPU_MODE_REAL) && (mode==CPU_MODE_PROTECTED)) //Switching from real mode to protected mode?
{
CPU[activeCPU].CPL = GENERALSEGMENT_DPL(CPU[activeCPU].SEG_DESCRIPTOR[CPU_SEGMENT_SS]); //DPL of SS determines CPL from now on!
}
else if ((CPUmode!=CPU_MODE_REAL) && (mode==CPU_MODE_REAL)) //Switching back to real mode?
{
CPU[activeCPU].CPL = 0; //Make sure we're CPL 0 in Real mode!
}
else if ((CPUmode!=CPU_MODE_8086) && (mode==CPU_MODE_8086)) //Switching to Virtual 8086 mode?
{
CPU[activeCPU].CPL = 3; //Make sure we're CPL 3 in Virtual 8086 mode!
}
CPUmode = mode; //Mode levels: Real mode > Protected Mode > VM86 Mode!
}
CPU[activeCPU].is_paging = ((CPUmode!=CPU_MODE_REAL)&((CPU[activeCPU].registers->CR0&CR0_PG)>>31)); //Are we paging in protected mode!
}

Although, paging might need to be updated to apply to real mode as well, according to known documentation(using LOADALL, real mode with Paging seems to be possible)? It's just that MOV CR0,reg with that combination is usually blocked? But LOADALL seems to respect that setting, effectively activating real mode with paging?

Last edited by superfury on 2018-04-06, 04:31. 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 9 of 13, by superfury

User metadata
Rank l33t++
Rank
l33t++

So, according to what I can find, trying to set CR0.PE=0 and CR0.PG=1 throws a #GP(0) exception((CR0&0x80000001)==0x80000000). But with LOADALL(Maybe SMM on newer CPUs), you can do that. Thus setup real mode with paging? One possible use for that is easy: EMS emulation on 80386/80486 CPUs? Maybe even possible on Pentium and up with System Management Mode(instead of LOADALL)?

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

Reply 10 of 13, by crazyc

User metadata
Rank Member
Rank
Member

Again, I don't think it's possible to predict what that will do because it's a configuration that can't happen outside of smm or loadall and I'd expect different cpu models to behave differently.

Reply 11 of 13, by Azarien

User metadata
Rank Oldbie
Rank
Oldbie

As far as I remember, according to Intel spec the very next instruction after MOV CR0,EAX "should" be a far jump. So enabling protected mode is a two-instruction operation: mov cr0, r32 / jmp far. Anything in between them is undocumented behavior, I think.

Reply 12 of 13, by superfury

User metadata
Rank l33t++
Rank
l33t++

Well, there's one thing I know for sure: the JMP after the MOV is already used while running in protected mode, otherwise it would jump just like in real mode, not using the GDT at all. So you can do something like MOV DS,AX to load the descriptor from the GDT and other instructions as well, as long as no interrupts are triggered. Because interrupts are triggered with an invalid IDT(R) will fault, and might even double/triple fault depending on the loaded GDT(R) and IDT(R). So it's for sure that the JMP is already executing in protected mode. The JMP isn't required, but advised to do to make sure any further loads returning using IRET or RETF don't return to an invalid real-mode selector which is invalid(probably) when loaded in protected mode(imagine returning to an invalid segment or the wrong RPL in the segment selector, which can be disasterous to the OS when jumped through the return(or task switch) mechanism). And Intel also tells the prefetch queue 'might' be used as in real mode, so needs to be flushed. There's probably no might there: it will continue to fetch and execute instructions as in real mode until it's reloaded, because the CS descriptor cache is still loaded with real-mode compatible values, thus continuing correctly until overflowing past offset 0xFFFF or an interrupt. So you should be at least able to squeeze some instructions in before an interrupt occurs or you reload through a jump imstruction. Even more should be able to squeeze in when clearing the interrupt flag beforehand, only leaving NMI interrupts and faults to mess with the running real-mode code.

That's at least as far as I've found out reading all those documentation on the internet.

So, logically, unreal mode DS can be as simple as(GDTR already loaded through LGDT):

PUSH BX
PUSH EAX
MOV BX,0008
MOV EAX,CR0
CLI
OR EAX,1
MOV CR0,EAX
MOV DS,BX
AND AX,FE
MOV CR0,EAX
POP EAX
POP BX
;Now DS is in unreal mode, other registers unaffected

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

Reply 13 of 13, by peterferrie

User metadata
Rank Oldbie
Rank
Oldbie

Yes, exactly that:
http://pferrie.host22.com/misc/tiny/unreal.htm
Any load of segment registers once CR0.PE is set will load from GDT. Ignore what I said earlier about that.
The jump doesn't need to be a jump, just anything that will cause CS to be loaded, for obvious reasons.