[Solved] Invalid Page Tables with identity paging.
Posted: Sun Apr 11, 2021 1:43 am
I am trying to set up identity paging on x64 with the following code
I get an immediate tripple fault and #PF and #DF handlers are not called.
The only conclusion is the page tables are invalid.
From the serial debug log (not included as a typical run of this function yeilds a 25 megabyte file)
the actual page table walk does appear to work.
Any ideas?
Code: Select all
#include <stdint.h>
#include <intrin.h>
#include "efiMemory.h"
#include "physmem.h"
#pragma pack(push,1)
typedef struct {
uint8_t present : 1; // 1
uint8_t rw : 1; // 2
uint8_t user : 1; // 3
uint8_t pwt : 1; // 4
uint8_t pcd : 1; // 5
uint8_t accessed : 1; // 6
uint8_t ignored0 : 1; // 7
uint8_t reserved0 : 1; // 8
uint8_t ignored1 : 4; // 12
uint64_t pdpt_addr : 36; // 48
uint8_t reserved1 : 4; // 52
uint16_t ignored : 11; // 63
uint8_t execute_disable : 1; // 64
} PML4_ENTRY; // 64 bits -- verified with SDM
typedef struct {
uint8_t present : 1; // 1
uint8_t rw : 1; // 2
uint8_t user : 1; // 3
uint8_t pwt : 1; // 4
uint8_t pcd : 1; // 5
uint8_t accessed : 1; // 6
uint8_t ignored0 : 1; // 7
uint8_t page_size : 1; // 8
uint8_t ignored1 : 4; // 12
uint64_t pd_addr : 36; // 48
uint8_t reserved0 : 4; // 52
uint16_t ignored2 : 11; // 63
uint8_t execute_disable : 1; // 64
} PDPTE_ENTRY; // 64 bits -- checked against SDM
typedef struct {
uint64_t present : 1; // 1
uint64_t rw : 1; // 2
uint64_t user : 1; // 3
uint64_t pwt : 1; // 4
uint64_t pcd : 1; // 5
uint64_t accessed : 1; // 6
uint64_t ignored0 : 1; // 7
uint64_t page_size : 1; // 8
uint64_t ignored1 : 4; // 12
uint64_t pt_addr : 36; // 48
uint64_t reserved0 : 4; // 52
uint64_t ignoreed1 : 11; // 63
uint64_t execute_disabled : 1; // 64
} PDIR_ENTRY; // 64 bits.
typedef struct {
uint64_t present : 1; // 1
uint64_t rw : 1; // 2
uint64_t user : 1; // 3
uint64_t pwt : 1; // 4
uint64_t pcd : 1; // 5
uint64_t accessed : 1; // 6
uint64_t dirty : 1; // 7
uint64_t pat : 1; // 8
uint64_t global : 1; // 9
uint64_t ignored0 : 3; // 12
uint64_t page_addr : 36; // 48
uint64_t reserved0 : 4; // 52
uint64_t ignored1 : 7; // 59
uint64_t prot_key : 4; // 63
uint64_t execute_disable : 1; // 64
} PT_ENTRY; // 64 bits
#pragma pack(pop)
PML4_ENTRY* KernelPML4;
extern void PrintSerial(const char* str);
extern void PrintSerialUIntHex(uint64_t value);
void MmMapPage(uint64_t phys, void* virt);
static void ZeroPage(void* vaddr) {
uint8_t *p = (uint8_t*)vaddr;
for (int i = 0; i < 4096; i++) {
p[i] = 0;
}
}
/* Identity Map all of ram from EFI memory map.
once tested this will be moved to the boot
loader to load kernel in the higher half.
MmInitalizeFromEfi must have been called
before this is called.
if EFI has set IA32_EFER.LA57 = 1 this will
fail. -=Todo=-: is there a spec anywhere
that says what state the CPU is in on x64
after ExitBootServices() has been called?
*/
void MmInitalizeIdentityPaging(EFI_MEMORY_DESCRIPTOR *MemMap,uint64_t mMapSize,uint64_t mMapDescrSize) {
KernelPML4 = (PML4_ENTRY*) MmAllocPhysicalPage();
ZeroPage(KernelPML4);
PrintSerial("PML4 table zeroed.\r\n");
uint64_t mMapEntryCount = mMapSize / mMapDescrSize;
for (int i = 0; i < mMapEntryCount; i++) {
EFI_MEMORY_DESCRIPTOR* desc = (EFI_MEMORY_DESCRIPTOR*)((uint64_t)MemMap + (i * mMapDescrSize));
for (uint64_t j = 0; j < desc->numPages; j++) {
uint64_t a = ((uint64_t)desc->physAddr) + ( j * 4096);
MmMapPage(a, (void*)a);
}
}
uint64_t NewCR3 = ((uint64_t)KernelPML4 & 0xFFFFFF000);
PrintSerial("New CR3 is ");
PrintSerialUIntHex(NewCR3);
PrintSerial("\r\n");
__writecr3(NewCR3); // <--- tripple fault here, neither PF or DF handlers are called
// <---- so page tables must be bad.
PrintSerial("CR3 updated\r\n");
}
void MmMapPage(uint64_t phys, void* virt) {
// walk the page table, allocating new pages
// for not present entries as nessecary.
// Note: this only works with 4kb pages.
// fixme: crash with an error message if address is not page aligned.
uint64_t va = (uint64_t)virt;
uint64_t PML4Index = (va & 0xFF8000000000) >> 38;
uint64_t PDPTRIndex = (va & 0x7FC00000000) >> 29;
uint64_t PDIRIndex = (va & 0x3FE00000) >> 20;
uint64_t PTIndex = (va & 0x1FF000) >> 11;
PDPTE_ENTRY* PDPTR = NULL;
PrintSerial("Mapping ");
PrintSerialUIntHex(va);
PrintSerial(" to ");
PrintSerialUIntHex(phys);
PrintSerial("\r\n");
PrintSerial("PML4Index = ");
PrintSerialUIntHex(PML4Index);
PrintSerial("\r\n");
PrintSerial("PDPTRIndex = ");
PrintSerialUIntHex(PDPTRIndex);
PrintSerial("\r\n");
PrintSerial("PDIRIndex = ");
PrintSerialUIntHex(PDIRIndex);
PrintSerial("\r\n");
PrintSerial("PTIndex = ");
PrintSerialUIntHex(PTIndex);
PrintSerial("\r\n");
// PML4
if (KernelPML4[PML4Index].present == 0) {
PDPTR = (PDPTE_ENTRY*) MmAllocPhysicalPage();
ZeroPage(PDPTR);
PrintSerial("New PDPT at ");
PrintSerialUIntHex((uint64_t)PDPTR);
PrintSerial("\r\n");
KernelPML4[PML4Index].pdpt_addr = ((uint64_t)PDPTR) >> 12;
KernelPML4[PML4Index].present = 1;
KernelPML4[PML4Index].rw = 1;
}
else {
PDPTR = (PDPTE_ENTRY*)(KernelPML4[PML4Index].pdpt_addr << 12);
}
PDIR_ENTRY* PDIR = NULL;
// Page Directory Pointer Table
if (PDPTR[PDPTRIndex].present == 0) {
PDIR = (PDIR_ENTRY*) MmAllocPhysicalPage();
ZeroPage(PDIR);
PrintSerial("New PDIR at ");
PrintSerialUIntHex((uint64_t)PDIR);
PrintSerial("\r\n");
PDPTR[PDPTRIndex].pd_addr = ((uint64_t)PDIR) >> 12;
PDPTR[PDPTRIndex].present = 1;
PDPTR[PDPTRIndex].rw = 1;
}
else {
PDIR = (PDIR_ENTRY*)(PDPTR[PDPTRIndex].pd_addr << 12);
}
PT_ENTRY* PTABLE = NULL;
// Page Directory
if (PDIR[PDIRIndex].present == 0) {
PTABLE = (PT_ENTRY*)MmAllocPhysicalPage();
ZeroPage(PTABLE);
PrintSerial("New Page Table at ");
PrintSerialUIntHex((uint64_t)PTABLE);
PrintSerial("\r\n");
PDIR[PDIRIndex].pt_addr = ((uint64_t)PTABLE) >> 12;
PDIR[PDIRIndex].present = 1;
PDIR[PDIRIndex].rw = 1;
}
else {
PTABLE = (PT_ENTRY*) (PDIR[PDIRIndex].pt_addr << 12);
}
// now the actual page table manipulation
PTABLE[PTIndex].page_addr = (phys & 0xFFFFFF000) >> 12;
PTABLE[PTIndex].present = 1;
// -=Todo=-: Add flags for read/write and stuff. In the future
// we will need to be able to set cache disable for
// the frame buffer, etc .
PTABLE[PTIndex].rw = 1;
PrintSerial("Page table page_addr = ");
PrintSerialUIntHex(PTABLE[PTIndex].page_addr);
PrintSerial("\r\n");
}
Code: Select all
Int8DoubleFaultLl:
mov dx,03f8h
mov al,'D'
out dx,al
mov al,'F'
out dx,al
cli
hlt
iretq
...
Int14PageFaultLl:
;push rax
;push rdx
mov dx,03f8h
mov al,'P'
out dx,al
mov al,'F'
out dx,al
cli
hlt
iretq
The only conclusion is the page tables are invalid.
From the serial debug log (not included as a typical run of this function yeilds a 25 megabyte file)
the actual page table walk does appear to work.
Any ideas?