OSDev.org

The Place to Start for Operating System Developers
It is currently Thu Mar 28, 2024 3:37 pm

All times are UTC - 6 hours




Post new topic Reply to topic  [ 4 posts ] 
Author Message
 Post subject: [Solved] Invalid Page Tables with identity paging.
PostPosted: Sun Apr 11, 2021 1:43 am 
Offline
User avatar

Joined: Fri Oct 05, 2012 4:41 am
Posts: 5
I am trying to set up identity paging on x64 with the following code
Code:
#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:
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?


Last edited by mariaw on Sun Apr 11, 2021 3:37 pm, edited 1 time in total.

Top
 Profile  
 
 Post subject: Re: Invalid Page Tables with identity paging.
PostPosted: Sun Apr 11, 2021 2:42 am 
Offline
Member
Member
User avatar

Joined: Sat Mar 31, 2012 3:07 am
Posts: 4591
Location: Chichester, UK
I'd recommend that you debug your OS, running in qemu, and inspect the page tables that you have created.


Top
 Profile  
 
 Post subject: Re: Invalid Page Tables with identity paging.
PostPosted: Sun Apr 11, 2021 5:51 am 
Offline
Member
Member

Joined: Wed Aug 30, 2017 8:24 am
Posts: 1593
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:
#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.

_________________
Carpe diem!


Top
 Profile  
 
 Post subject: [Solved] Re: Invalid Page Tables with identity paging.
PostPosted: Sun Apr 11, 2021 2:59 pm 
Offline
User avatar

Joined: Fri Oct 05, 2012 4:41 am
Posts: 5
Thank you nullplan. By looking at your code I was able to quickly find what was wrong with mine.

this part:
Code:
   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:
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:
...
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.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 4 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: Bing [Bot], Google [Bot], Majestic-12 [Bot] and 48 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group