Page 1 of 1

[Solved] Invalid Page Tables with identity paging.

Posted: Sun Apr 11, 2021 1:43 am
by mariaw
I am trying to set up identity paging on x64 with the following code

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");
}
I get an immediate tripple fault and #PF and #DF handlers are not called.

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?

Re: Invalid Page Tables with identity paging.

Posted: Sun Apr 11, 2021 2:42 am
by iansjack
I'd recommend that you debug your OS, running in qemu, and inspect the page tables that you have created.

Re: Invalid Page Tables with identity paging.

Posted: Sun Apr 11, 2021 5:51 am
by nullplan
Well, one issue is already given as a "fixme" in the code: You never verify that the addresses are aligned. For reference, here's how my stage 1 kernel handles that:

Code: Select all

#define PAGE_PRESENT    1
#define PAGE_WRITABLE   2
#define PAGE_USER       4
#define PAGE_PS         128
#define PAGE_PTR_MASK   0xfffff000  /* we are in 32-bit mode still */

static uint64_t *pml4;
static void mmap(uint64_t va, uint64_t pa, uint64_t len, uint64_t flags)
{
    if (!pml4)
        pml4 = page_alloc_or_die();

    if ((va ^ pa) & 0xfff)
        die("Virtual and physical addresses misaligned!\n");
    len += va & 0xfff;
    va &= -0x1000ull;
    pa &= -0x1000ull;

    len = (len + 0xfff) & -0x1000ull;
    while (len)
    {
        /* how to choose page size?
         * Choose biggest size possible.
         */
        /* addr:
         *    6         5         4         3         2         1
         * 3210987654321098765432109876543210987654321098765432109876543210
         * `sign extens.  ´`pml4idx´`pdpidx ´`pdidx  ´`ptidx  ´`offset    ´
         */
        if (is_gb_paging_possible() && len >= 1 << 30 && !(va & ((1 << 30) - 1)) && !(pa & (1 << 30) - 1))
        {
            size_t pml4idx = (va >> 39) & 0x1ff;
            size_t pdpidx = (va >> 30) & 0x1ff;
            if (!pml4[pml4idx] & PAGE_PRESENT)
                pml4[pml4idx] = (uint64_t)(uintptr_t)page_alloc_or_die() | PAGE_PRESENT | PAGE_WRITABLE;
            uint64_t *pdpt = (uint64_t*)(uintptr_t)(pml4[pml4idx] & PAGE_PTR_MASK);
            pdpt[pdpidx] = pa | flags | PAGE_PRESENT | PAGE_PS;
            va += 1 << 30;
            pa += 1 << 30;
            len -= 1 << 30;
        }
        else if (len >= 2 << 20 && !(va & (2 << 20) - 1) && !(pa & (2 << 20) - 1))
        {
            size_t pml4idx = (va >> 39) & 0x1ff;
            size_t pdpidx = (va >> 30) & 0x1ff;
            size_t pdidx = (va >> 21) & 0x1ff;
            if (!pml4[pml4idx] & 1)
                pml4[pml4idx] = (uint64_t)(uintptr_t)page_alloc_or_die() | PAGE_PRESENT | PAGE_WRITABLE;
            uint64_t *pdpt, *pdt;
            pdpt = (void*)(uintptr_t)(pml4[pml4idx] & PAGE_PTR_MASK);
            if (!pdpt[pdpidx] & PAGE_PRESENT)
                pdpt[pdpidx] = (uint64_t)(uintptr_t)page_alloc_or_die() | PAGE_PRESENT | PAGE_WRITABLE;
            pdt = (void*)(uintptr_t)(pdpt[pdpidx] & PAGE_PTR_MASK);
            pdt[pdidx] = pa | flags | PAGE_PRESENT | PAGE_PS;

            va += 1 << 21;
            pa += 1 << 21;
            len -= 1 << 21;
        }
        else
        {
            size_t pml4idx = (va >> 39) & 0x1ff;
            size_t pdpidx = (va >> 30) & 0x1ff;
            size_t pdidx = (va >> 21) & 0x1ff;
            size_t ptidx = (va >> 12) & 0x1ff;
            uint64_t *pdpt, *pdt, *pt;
            if (!pml4[pml4idx] & PAGE_PRESENT)
                pml4[pml4idx] = (uint64_t)(uintptr_t)page_alloc_or_die() | PAGE_PRESENT | PAGE_WRITABLE;
            pdpt = (uint64_t*)(uintptr_t)(pml4[pml4idx] & PAGE_PTR_MASK);
            if (!pdpt[pdpidx] & PAGE_PRESENT)
                pdpt[pdpidx] = (uint64_t)(uintptr_t)page_alloc_or_die() | PAGE_PRESENT | PAGE_WRITABLE;
            pdt = (uint64_t*)(uintptr_t)(pdpt[pdpidx] & PAGE_PTR_MASK);
            if (!pdt[pdidx] & PAGE_PRESENT)
                pdt[pdidx] = (uint64_t)(uintptr_t)page_alloc_or_die() | PAGE_PRESENT | PAGE_WRITABLE;
            pt = (uint64_t*)(uintptr_t)(pdt[pdidx] & PAGE_PTR_MASK);
            pt[ptidx] = pa | flags | PAGE_PRESENT;

            va += 1 << 12;
            pa += 1 << 12;
            len -= 1 << 12;
        }
    }
}
As a rule, I avoid bitfields for external communication, because I can never remember what order the compiler will allocate the bits in. Indeed, that is not specified anywhere and can change between compilers. So I stick to shifts and masks. And yes, talking to the CPU is external communication.

The above code can be easily extended to allow for NX, USER, and the other flags you are going to need. The above code only runs in a loader context, and therefore does not need those things. NX is not yet enabled there, and neither is userspace.

[Solved] Re: Invalid Page Tables with identity paging.

Posted: Sun Apr 11, 2021 2:59 pm
by mariaw
Thank you nullplan. By looking at your code I was able to quickly find what was wrong with mine.

this part:

Code: Select all

   uint64_t PML4Index = (va & 0xFF8000000000) >> 38;
   uint64_t PDPTRIndex = (va & 0x7FC00000000) >> 29;
   uint64_t PDIRIndex = (va & 0x3FE00000) >> 20;
   uint64_t PTIndex = (va & 0x1FF000) >> 11;
was incorrect.
I changed it to

Code: Select all

uint64_t va = (uint64_t)virt;
	uint64_t PML4Index = (va >> 39) & 0x1ff;
	uint64_t PDPTRIndex = (va >> 30) & 0x1ff;
	uint64_t PDIRIndex = (va >> 21) & 0x1ff;
	uint64_t PTIndex = (va >> 12) & 0x1ff;
based off of nullplan's 4kb pages case
and now I get

Code: Select all

...
New CR3 is 0000000002503000
CR3 updated
Successfully Initalized Paging
TTTTTTTTTTTTTTTTTTTTTTTT

from the serial port (the Ts are a timer ISR test).

Thanks for the help.