Page 1 of 1

Entering Long Mode Directly

Posted: Mon Feb 26, 2018 11:43 am
by Rudster816
Does anybody have any experience doing this on recent hardware? I was using the following AP trampoline to go from real mode to long mode

Code: Select all

Trampoline_Start:
	cli
	xor ax, ax
	mov ds, ax
	mov ebx, [HeapStart]
	; Set CR3 to PML4
	mov eax, [ebx+BasicInfo.PageTable]
	mov cr3, eax
	; Enable PAE and PSE
    mov eax, cr4
    or eax, 0x30
    mov cr4, eax	
	; Enable Long mode
	mov ecx, 0xC0000080
	rdmsr
	or eax, 0x00000100
	wrmsr
	; Enable Paging and Protection at same time
	mov eax, cr0
	or eax, 0x80000001
	mov cr0, eax
	lgdt [GDT_PTR_Long]
	jmp 0x8:LongModeTest	
Trampoline_End:
It works on Qemu (not like that means much in my experience), VMWare, and VirtualBox. Trying it on one of my laptops that has an i5 3320M and the AP does start and starts executing the real mode portion, but never gets to the 64-Bit portion. The computer doesn't restart though so it isn't triple faulting. If I change the trampoline to switch to protected mode first then long mode the mode switching works on the VMs and hardware.

Code: Select all

Trampoline_Start:
	cli
	lgdt [GDT_PTR]
	mov eax, cr0
	or al, 1
	mov cr0, eax
	jmp 0x8:.AP_PMode
[BITS 32]
.AP_PMode:
	mov eax, 0x10
	mov ds, ax

	mov ebx, [HeapStart]
	; Set CR3 to PML4
	mov eax, [ebx+BasicInfo.PageTable]
	mov cr3, eax
	; Enable PAE and PSE
    mov eax, cr4
    or eax, 0x30
    mov cr4, eax	
	; Enable Long mode
	mov ecx, 0xC0000080
	rdmsr
	or eax, 0x00000100
	wrmsr
	; Enable Paging
	mov eax, cr0
	or eax, 0x80000000
	mov cr0, eax
	lgdt [GDT_PTR_Long]
	jmp 0x8:LongModeTest	
Trampoline_End:

I'm wondering if anyone else has experienced similar issues? I'm not sure if there is a problem with my code or if switching directly to long mode from real mode no longer works on new hardware. I assume it has to be the former but I don't see anything wrong with my code, and am I wrong assuming that it should either
1. Successfully jump to LongModeTest in long mode or
2. Triple Fault

I haven't gotten a chance to debug it more (I should probably try it with 0 length IDT). Here is LongModeTest:

Code: Select all

[BITS 64]
LongModeTest:
	cli
	mov ax, 0x10
	mov ds, ax
	mov es, ax
	mov fs, ax
	mov gs, ax
	mov ss, ax
    mov rax, [PayloadEntry]
	mov edi, [HeapStart]
	
	; Tell BSP we've started
	mov DWORD [CpuGood], 1

	; Wait for BSP to give us the OK to execute payload
	.GetLock:
		lock bts DWORD [Spinlock], 0
		jc .PauseSpin
		jmp rax
		
	.PauseSpin:
		pause
		test DWORD [Spinlock], 1
		jnz .PauseSpin
		jmp .GetLock
So long as I switch to protected mode first, it sets the CpuGood flag fine. It can also write to the screen without crashing. On hardware it seems to triple fault as soon as it acquires the lock and jumps to RAX. I haven't tried to debug this at all yet, but can confirm that it works on VMWare and VirtualBox just fine. There are some other differences between the hardware VMs in regards to that issue though, but they don't affect the mode switching code.


Is there something wrong with my trampoline (other than not trying to mask NMIs) that would be causing this? Has anyone else had similar problems on hardware? Im trying to figure out if its possible that some pieces of hardware will not go from Real mode to Long mode directly, but this doesn't seem possible.

Re: Entering Long Mode Directly

Posted: Mon Feb 26, 2018 12:22 pm
by iansjack
If going via protected mode works then what's the problem?

Re: Entering Long Mode Directly

Posted: Mon Feb 26, 2018 12:36 pm
by Rudster816
iansjack wrote:If going via protected mode works then what's the problem?
First and foremost, I'm curious. Second on the off chance it is hardware related than someone should probably update the wiki on Entering_Long_Mode_Directly.

It would also beg the question what is the status of the CPU? If it hasn't triple faulted and didn't do the long jump, then what is it doing? I think that would be considered a bug. I know Intel manuals specifically say that the CPU needs to be in protected mode to activate long mode, but AFAIK they don't say what will happen if they aren't either.

Re: Entering Long Mode Directly

Posted: Mon Feb 26, 2018 12:50 pm
by BrightLight
Entering long mode directly works on all my test PCs, on all SMP APs. I seriously doubt this is a hardware bug or some undefined hardware behavior. It's much more likely something is wrong with your code.

Code: Select all

trampoline16:
use16
	xor ax, ax
	mov ds, ax
	mov es, ax

	lgdt [0x1800]	; temporary GDT just for switching
			; we can't use the real GDT because it's around 1024 GB
			; and we can't access that in real mode and pmode

	; enable PAE, SSE and PSE
	mov eax, 0x630
	mov cr4, eax

	mov eax, cr0
	and eax, 0xFFFFFFFB
	or eax, 2
	mov cr0, eax

	; go to long mode
	mov eax, PML4
	mov cr3, eax

	mov ecx, 0xC0000080
	rdmsr
	or eax, 0x100
	wrmsr

	mov eax, cr0
	or eax, 0x80000001
	and eax, not 0x60000000
	mov cr0, eax
	jmp 0x08:0x1100

Re: Entering Long Mode Directly

Posted: Tue Feb 27, 2018 12:13 am
by Brendan
Hi,
Rudster816 wrote:I'm not sure if there is a problem with my code or if switching directly to long mode from real mode no longer works on new hardware. I assume it has to be the former but I don't see anything wrong with my code, and am I wrong assuming that it should either
1. Successfully jump to LongModeTest in long mode or
2. Triple Fault
Sadly, no. When the AP is started it begins in real mode using the BIOS IVT, and any exception that occurs while in real mode will use the BIOS interrupt handler. More specifically; if a general protection fault occurs while your trampoline is in real mode the CPU will start the BIOS interrupt handler for "int 0x0D", the BIOS will assume that the interrupt was caused by "IRQ5", do almost nothing (check if the device that generates IRQ5 needs attention, then send an EOI to the PIC chip) and return to the instruction that caused the general protection fault (which will cause another general protection fault, which will cause an infinite loop of general protection faults).

For this reason (and because it's theoretically possible for the AP CPU to receive an NMI while executing the trampoline) I'd recommend loading an IDT with "limit = 0" as early in your trampoline as possible (e.g. "lidt [cs:null_idt]" as the first instruction in the trampoline), so that you have a guarantee that all interrupts and exceptions that can't be masked will trigger a triple fault.

Note: In my opinion it's always a bad idea to mask/disable NMI because NMI is used to indicate hardware failures. When you're unable to handle NMI properly (which is almost impossible for code like this) it's much better to reset the computer (via. triple fault) than it is to continue executing after hardware has failed.
Rudster816 wrote:

Code: Select all

Trampoline_Start:
	cli
	xor ax, ax
	mov ds, ax
	mov ebx, [HeapStart]
	; Set CR3 to PML4
	mov eax, [ebx+BasicInfo.PageTable]
Can you guarantee that "ebx+BasicInfo.PageTable" is always within the 64 KiB segment limit? Maybe on some computers (those with more memory?) "HeapStart" has a much higher value, and maybe on some computers the "mov eax, [ebx+BasicInfo.PageTable]" instruction causes a general protection fault.


Cheers,

Brendan

Re: Entering Long Mode Directly

Posted: Tue Feb 27, 2018 2:13 am
by iansjack
Rudster816 wrote:I know Intel manuals specifically say that the CPU needs to be in protected mode to activate long mode, but AFAIK they don't say what will happen if they aren't either.
I think it is unreasonable to expect the manuals to detail all undefined behaviour. By it's very nature undefined behaviour is undefined. The result might well depend upon the particular model, or even batch, of the CPU.

If instructions, or sequences of instructions, are undocumented then it is not, IMO, sensible to use them. That sort of programming has broken many an application in the past as new CPUs are introduced.

Re: Entering Long Mode Directly

Posted: Tue Feb 27, 2018 4:11 am
by alexfru
iansjack wrote:I think it is unreasonable to expect the manuals to detail all undefined behaviour. By it's very nature undefined behaviour is undefined.
It should be bounded, however. I'm not sure what make of the abort class exceptions like #DF and #MC. Just how botched is the CPU state on these? Should it still be possible to log the error (meaning, utilize page translation, caches, DMA, disk I/O, interrupts, etc etc) or things are so undefined that one shouldn't even try? Or one should try, but nobody knows if it'll work? The manual doesn't quite say.

Re: Entering Long Mode Directly

Posted: Tue Feb 27, 2018 7:48 pm
by Rudster816
Brendan wrote:Hi,

Sadly, no. When the AP is started it begins in real mode using the BIOS IVT, and any exception that occurs while in real mode will use the BIOS interrupt handler. More specifically; if a general protection fault occurs while your trampoline is in real mode the CPU will start the BIOS interrupt handler for "int 0x0D", the BIOS will assume that the interrupt was caused by "IRQ5", do almost nothing (check if the device that generates IRQ5 needs attention, then send an EOI to the PIC chip) and return to the instruction that caused the general protection fault (which will cause another general protection fault, which will cause an infinite loop of general protection faults).

For this reason (and because it's theoretically possible for the AP CPU to receive an NMI while executing the trampoline) I'd recommend loading an IDT with "limit = 0" as early in your trampoline as possible (e.g. "lidt [cs:null_idt]" as the first instruction in the trampoline), so that you have a guarantee that all interrupts and exceptions that can't be masked will trigger a triple fault.

Note: In my opinion it's always a bad idea to mask/disable NMI because NMI is used to indicate hardware failures. When you're unable to handle NMI properly (which is almost impossible for code like this) it's much better to reset the computer (via. triple fault) than it is to continue executing after hardware has failed.

Can you guarantee that "ebx+BasicInfo.PageTable" is always within the 64 KiB segment limit? Maybe on some computers (those with more memory?) "HeapStart" has a much higher value, and maybe on some computers the "mov eax, [ebx+BasicInfo.PageTable]" instruction causes a general protection fault.


Cheers,

Brendan

I had a feeling it was something stupid, not sure why I didn't see that. [HeapStart] is in the first 64Kb, but the pointer it contains definitely is not. I guess the BIOS AP initialization routines in the VMs must have raised the segmentation limit and not restored it prior to halting. Sometimes I forgot that just because it works in VMs doesn't mean it isn't broken lol.


I've been meaning to add a 0 length IDT to my code, but have been lazy. I agree though, I've never seen the point in trying to mask NMIs when Intel says they should be. I'd rather triple fault, which accomplishes the same thing that a sane OS should do when it gets an NMI minus an error message.