Here is the code I use for returning to legacy protected mode. The first few lines save the environment, so that the loader can re-enter long mode (for example after calling a BIOS interrupt or executing some v86 code).
Code: Select all
LdrTransition64To32:
xchg rax, rcx ; swap regs (call destination -> rcx)
mov esi, offset LoaderData ; load address of data structure
pushfq ;
pop [rsi + 1Ch] ; save flags
mov [rsi + 0Ch], esp ; save rsp
sidt fword ptr [rsi + 3Ch] ; save IDTR
cli ; disable interrupts
; this is based on the AMD manual
mov rax, cr0 ;
and eax, 7FFFFFFFh ; disable paging
mov cr0, rax ;
mov eax, mrTaskLoaderx86PageDirStart; load cr3 with 32-bit page table
mov cr3, rax ;
mov ecx, 0C0000080h ; EFER MSR number
rdmsr ; read EFER
and eax, not 100h ;
wrmsr ; write EFER
mov rax, cr4 ; disable PAE
and al, not 20h ;
mov cr4, rax ;
; below this line is speculation, this may be outright wrong.
mov rax, offset LdrTransition64To32$0 ; address to return to
pushfq ; fake an iretq
push Code32R0Sel ;
push rax ;
iretq ;
What am I doing wrong? Bochs says
Code: Select all
00055028596e[CPU0 ] SetCR0: attempt to leave 64 bit mode directly to legacy mode !
00055028596d[CPU0 ] exception(0x0d): error_code=0000
00055028596d[CPU0 ] interrupt(): vector = 13, INT = 0, EXT = 1
00055028596d[CPU0 ] interrupt(long mode): INTERRUPT TO SAME PRIVILEGE
Code: Select all
00055028597e[CPU0 ] WRMSR: attempt to change LME when CR0.PG=1
00055028597d[CPU0 ] exception(0x0d): error_code=0000
00055028597d[CPU0 ] interrupt(): vector = 13, INT = 0, EXT = 1
00055028597d[CPU0 ] interrupt(long mode): INTERRUPT TO SAME PRIVILEGE
Code: Select all
00055028594d[CPU0 ] MOV_RqCq: read of CR4
00055028596d[CPU0 ] MOV_CqRq: write to CR4 of 00000000:00000000
00055028596e[CPU0 ] SetCR4: attempt to change PAE when EFER.LMA=1
00055028596d[CPU0 ] exception(0x0d): error_code=0000
00055028596d[CPU0 ] interrupt(): vector = 13, INT = 0, EXT = 1
00055028596d[CPU0 ] interrupt(long mode): INTERRUPT TO SAME PRIVILEGE