Page 1 of 1
PAE paging problem
Posted: Sun Apr 21, 2024 12:33 pm
by awik
Hi all,
I've written a program that enters long mode, clears the screen, and exits long mode. As part of returning from long mode to 32-bit "regular" protected mode, I'm trying to re-enable PAE-style paging. So, after the exit from long mode, I change CR3 from pointing to the PML4 table to point to the Page Directory Pointer Table instead. Then Bochs generates this error message:
Code: Select all
202206508436e[CPU0 ] SetCR0(): PDPTR check failed !
What might be wrong? The PDPT works fine in long mode.
Source code is available on request.
Regards,
Albert.
Re: PAE paging problem
Posted: Sun Apr 21, 2024 1:15 pm
by Octocontrabass
awik wrote:What might be wrong?
Bochs displays that message when reserved bits are set in the PDPT.
awik wrote:The PDPT works fine in long mode.
Long mode and protected mode have different definitions for the PDPT. A PDPT that's valid in long mode may not be valid in protected mode.
Re: PAE paging problem
Posted: Mon Apr 22, 2024 11:56 pm
by awik
You're were right. Apparently, only bit 0 may be set in a PDPT entry in regular protected mode. It is necessary to re-initialise the entries, because in long mode, several other bits get set automatically.
-Albert.
Re: PAE paging problem
Posted: Tue Apr 23, 2024 11:06 am
by rdos
You must create a GDT, switch to compatibility mode, disable paging, change CR3, set PAE bit and then renable protected mode paging.
My code looks like this: (it's mostly hex-coded since my assembler cannot handle mixed 32 & 64-bit code)
Code: Select all
IMAGE_BASE = 110000h
param_struc STRUC
lfb_base DD ?,?
lfb_width DD ?
lfb_height DD ?
lfb_line_size DD ?
lfb_flags DD ?
mem_entries DD ?
acpi_table DD ?,?
param_struc ENDS
_TEXT segment byte public use16 'CODE'
.386p
db 0Ebh ; jmp init64
db 38h + SIZE param_struc
param param_struc <>
rom_gdt:
gdt0:
dw 0
dd 0
dw 0
gdt8:
dw 0
dd 0
dw 0
gdt10:
dw 28h-1
dd 92000000h + OFFSET rom_gdt + IMAGE_BASE
dw 0
gdt18:
dw 0FFFFh
dd 9A000000h
dw 0CFh
gdt20:
dw 0FFFFh
dd 92000000h
dw 08Fh
gdt_ptr:
dw 28h-1
dd OFFSET rom_gdt + IMAGE_BASE
dd 0
prot_ptr:
dd OFFSET prot_init + IMAGE_BASE
dw 18h
init64:
db 0FAh ; cli
db 0Fh ; lgdt gdt_ptr
db 01h
db 15h
dd 0FFFFFFE8h
;
db 0FFh
db 1Dh
dd 0FFFFFFECh
prot_init:
db 0Fh ; mov eax,cr0
db 20h
db 0C0h
;
db 25h ; and eax,7FFFFFFFh
dd 07FFFFFFFh
;
db 0Fh ; mov cr0,eax
db 22h
db 0C0h
;
db 0B9h ; mov ecx,IA32_EFER
dd 0C0000080h
;
db 0Fh ; rdmsr
db 32h
;
db 25h ; and eax,0FFFFFEFFh
dd 0FFFFFEFFh
;
db 0Fh ; wrmsr
db 30h
;
db 0Fh ; mov rax,cr4
db 20h
db 0E0h
;
db 83h ; and eax,NOT 20h
db 0E0h
db 0DFh
;
db 0Fh ; mov cr4,rax
db 22h
db 0E0h
;
db 0B8h ; mov eax,20h
dd 20h
;
db 8Eh ; mov ds,eax
db 0D8h
;
db 0BBh ; mov ebx,OFFSET gdt18
dd OFFSET gdt18 + IMAGE_BASE
;
db 0BAh ; mov edx,IMAGE_BASE
dd IMAGE_BASE
;
db 89h ; mov [ebx+2],edx
db 53h
db 02h
;
db 0B0h ; mov al,9Ah
db 9Ah
;
db 86h ; xchg al,[ebx+5]
db 43h
db 5
;
db 32h ; xor cl,cl
db 0C9h
;
db 8Ah ; mov ch,al
db 0E8h
;
db 66h ; mov [ebx+6],cx
db 89h
db 4Bh
db 6
;
db 0EAh ; jmp 18:init
dd OFFSET init
dw 18h
init:
mov ax,20h
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
Re: PAE paging problem
Posted: Wed Apr 24, 2024 7:41 am
by awik
rdos wrote:You must create a GDT, switch to compatibility mode, disable paging, change CR3, set PAE bit and then renable protected mode paging.
I already have a GDT, and the PAE bit is already set.
My code looks like this: (it's mostly hex-coded since my assembler cannot handle mixed 32 & 64-bit code)
My (now working) code is as follows:
Code: Select all
;
; Going from 64-bit mode straight to legacy (non-long) mode is
; not legal. Also, we cannot go directly to a 32-bit code segment
; with a (non-zero) base, because the base does not get loaded as
; long (pun not intended) as we are in Long Mode. The solution is
; to go to Compatibility Mode first.
;
; mov ax, SEG_BASED_CS_32
mov ax, SEG_FLAT_CS_32 ; segment suitable for
; ; Compatibility Mode
push rax
lea eax,[esi+.exit_from_64]
push rax
retfq
BITS 32
ALIGN 4
.exit_from_64:
;
; We are now in Compatibility Mode.
;
; We need to turn off paging before we can exit Long Mode.
;
mov eax,cr0
btr eax,CR0_PG_BIT
mov cr0,eax
;
; Disable Long Mode
;
mov ecx,MSR_EFER
rdmsr
btr eax,EFER_LM_BIT
wrmsr
;
push SEG_BASED_CS_32 ; "based" 32-bit code segment
push .exit_from_lm32
retfd
ALIGN 4
BITS 32
.exit_from_lm32:
;
; We are now running from our "based" 32-bit code segment.
;
mov ax,SEG_BASED_DS_16 ; "based" data/stack segment
mov ss, ax
sub esp,esi ; convert linear to segment-relative
sub ebp,esi
mov ds, ax
mov es, ax
;
; Set up paging for "normal" Protected Mode
;
mov eax,[ebp+BSS_PDPT_PHYSADDR]
mov ebx,eax
sub ebx,esi ; convert linear to segment-relative
and byte [ebx], 1 ; clear reserved bits
mov cr3,eax
mov eax,cr0
bts eax,CR0_PG_BIT
mov cr0,eax
;
Re: PAE paging problem
Posted: Wed Apr 24, 2024 11:43 pm
by rdos
Not sure about the non-zero base. You can load selectors with non-zero base in long mode, but the base will not be applied until you are in compatibility mode.
It's perfectly possible to do a far jump to legacy code with a non-zero base. Segment register loads operates exactly the same in long mode as in protected mode.
I posted my EFI 64->32 bit code previously, but I also have simpler code to switch between protected mode and long mode and the reverse. Switching to long mode is pretty easy:
Code: Select all
; EAX = cr3
switch_to_long_mode Proc far
push eax
push ebx
push ecx
push edx
pushf
;
mov ebx,eax
cli
;
mov eax,cr0
and eax,7FFFFFFFh
mov cr0,eax
;
mov ecx,IA32_EFER
rdmsr
or eax,101h
wrmsr
;
mov cr3,ebx
;
mov eax,cr0
or eax,80000000h
mov cr0,eax
;
lidt fword ptr cs:long_idt_size
;
popf
pop edx
pop ecx
pop ebx
pop eax
ret
switch_to_long_mode Endp
Switching back is easy too:
Code: Select all
; EAX = cr3
switch_to_protected_mode Proc far
push eax
push ebx
push ecx
push edx
pushf
;
mov ebx,eax
cli
;
mov eax,cr0
and eax,7FFFFFFFh
mov cr0,eax
;
mov ecx,IA32_EFER
rdmsr
and eax,0FFFFFEFFh
wrmsr
;
mov cr3,ebx
;
mov eax,cr0
or eax,80000000h
mov cr0,eax
;
lidt fword ptr cs:prot_idt_size
;
popf
pop edx
pop ecx
pop ebx
pop eax
ret
switch_to_protected_mode Endp
However, the switching back code is running in compatibility mode since the scheduler is running in compatibility mode and not in long mode. The switching from long mode to compatibility mode happens as part of a syscall or an IRQ, which contain code to do a far call to the common handler code in compatibility mode.
Also note that the GDT can be shared between protected mode and long mode, but not the IDT. There is a need for a specific IDT for long mode.
Re: PAE paging problem
Posted: Thu Apr 25, 2024 3:36 am
by awik
rdos wrote:Not sure about the non-zero base.
I was pretty sure about it, because I had tested it, but now that I tested it again, the base *does* get set. I think I must have pushed the wrong (ie. flat) address last time.
Switching to long mode is pretty easy:
It *is* easy once you have PAE paging set up. Otherwise, you have to set up 4 levels of paging structures first.
... a syscall or an IRQ, which contain handlers with a compatibility mode descriptor.
Cool, I didn't know you could do that.
Also note that the GDT can be shared between protected mode and long mode, but not the IDT. There is a need for a specific IDT for long mode.
I know. I haven't tried creating a 64-bit IDT myself yet.
-Albert.
Re: PAE paging problem
Posted: Fri Apr 26, 2024 5:47 am
by awik
awik wrote:rdos wrote:... a syscall or an IRQ, which contain handlers with a compatibility mode descriptor.
Cool, I didn't know you could do that.
The "AMD64 Architecture Programmer's Manual", volume 2, "System Programming), page 102, reads: "The target code segment referenced by a long-mode gate descriptor must be a 64-bit code segment (CS.L=1, CS.D=0). If the target is not a 64-bit code segment, a general-protection exception, #GP(error), occurs.".
Is this incorrect?
Re: PAE paging problem
Posted: Fri Apr 26, 2024 6:56 am
by rdos
awik wrote:awik wrote:rdos wrote:... a syscall or an IRQ, which contain handlers with a compatibility mode descriptor.
Cool, I didn't know you could do that.
The "AMD64 Architecture Programmer's Manual", volume 2, "System Programming), page 102, reads: "The target code segment referenced by a long-mode gate descriptor must be a 64-bit code segment (CS.L=1, CS.D=0). If the target is not a 64-bit code segment, a general-protection exception, #GP(error), occurs.".
Is this incorrect?
The manual is correct. I changed my post so that the IRQ handler does a far call to the compatibility mode server procedure. That's part of the reason you need different IDTs, and also different high-level handler procedures. The long mode interrupt handlers must run in long mode, but can defer their function to compatibility mode for common cases like IRQs. Exceptions, including page fault, need to be handled completely in long mode, perhaps except for aborting a faulty process.