Page 1 of 1

How can I resolve 4 Level Paging issue in x86 architecture based 64 bit OS?

Posted: Sat Jan 18, 2025 7:40 am
by gamingjam60
Hello!

I am trying to develop a x86 architecture based 64 bit Operating system. The limine bootloader is using to load kernel.
Limine initially enable 4 level Paging. So I have defined following structures to use previously enabled paging system.

Code: Select all

typedef struct dir_entry { // 64 bit
    uint64_t present      : 1;  // always 1
    uint64_t rw           : 1;  // 0 for read-only, 1 for read-write
    uint64_t user         : 1;  // 0 for kernel, 1 for user
    uint64_t pwt          : 1;  
    uint64_t pcd          : 1;
    uint64_t accessed     : 1;
    uint64_t reserved_1   : 3;  // all zeros
    uint64_t available_1  : 3;  // zero
    uint64_t base_addr    : 40; // Table base address
    // uint64_t reserved_2   : 12; // Reserved must be zero
    uint64_t available_2  : 11; // zero
    uint64_t xd           : 1;
} __attribute__((packed)) dir_entry_t;


typedef struct page { // 64 bit
    uint64_t present   : 1;
    uint64_t rw        : 1;
    uint64_t user      : 1;
    uint64_t pwt       : 1;
    uint64_t pcd       : 1;
    uint64_t accessed  : 1;
    uint64_t dirty     : 1;
    uint64_t pat       : 1;
    uint64_t global    : 1;
    uint64_t ignored   : 3;
    uint64_t frame     : 40;
    uint64_t reserved  : 11;
    uint64_t nx        : 1;
} __attribute__((packed)) page_t;


typedef struct pt { // page table structure is containing 512 page entries
    page_t pages[512];
} __attribute__((aligned(PAGE_SIZE))) pt_t;

typedef struct pd { // page directory structure is containg 512 page table entries
    dir_entry_t entry_t[512]; // Each entry have Physical addresses of PTs
} __attribute__((aligned(PAGE_SIZE))) pd_t;

typedef struct pdpt { // pdpt structure is containing 512 page directory entries
    dir_entry_t entry_t[512]; // Each entry have Physical addresses of PDs
} __attribute__((aligned(PAGE_SIZE))) pdpt_t;

typedef struct pml4 { // pml4 structure is containing 512 pdpt directory entries
    dir_entry_t entry_t[512]; // Each entry have Physical addresses of PDPTs
} __attribute__((aligned(PAGE_SIZE))) pml4_t;
Following macros are using to get pml4, pdpt, pd, pt indexes from virtual address,

Code: Select all

#define PML4_INDEX(va)   (((va) >> 39) & 0x1FF)  // Bits 39-47
#define PDPT_INDEX(va)   (((va) >> 30) & 0x1FF)  // Bits 30-38
#define PD_INDEX(va)     (((va) >> 21) & 0x1FF)  // Bits 21-29
#define PT_INDEX(va)     (((va) >> 12) & 0x1FF)  // Bits 12-20
#define PAGE_OFFSET(va)  ((va) & 0xFFF)          // Bits 0-11
I have written following below get_page function to get page_t pointer from virtual address ,

Code: Select all

page_t* get_page(uint64_t va, int make, pml4_t* pml4) {

    uint64_t pml4_index = PML4_INDEX(va);
    uint64_t pdpt_index = PDPT_INDEX(va);
    uint64_t pd_index = PD_INDEX(va);
    uint64_t pt_index = PT_INDEX(va);
    uint64_t page_offset = PAGE_OFFSET(va);

    // Get the PML4 entry from pml4_index which is found from va
    dir_entry_t* pml4_entry = &pml4->entry_t[pml4_index];

    // the below if block will create pdpt if not present and make = 1
    if (!pml4_entry->present) {
        if (!make) {
            return NULL; // Page table does not exist and we are not allowed to create it
        }
        // Allocate a new PDPT
        pdpt_t* pdpt = alloc_pdpt(); // Creating a new pdpt
        if (!pdpt) {
            return NULL; // Allocation failed
        }
        // Set up the PML4 entry
        pml4_entry->present = 1;
        pml4_entry->rw = 1; // Read/write
        pml4_entry->user = 0; // Kernel mode
        pml4_entry->base_addr = (uint64_t) pdpt >> 12; // Base address of PDPT
    }

    // Get the PDPT entry
    pdpt_t* pdpt = (pdpt_t*)(pml4_entry->base_addr << 12); // Converting base address into pdpt pointer
    dir_entry_t* pdpt_entry = &pdpt->entry_t[pdpt_index]; // 

    // the below if block will create pd if not present and make = 1
    if (!pdpt_entry->present) {
        if (!make) {
            return NULL; // Page directory does not exist and we are not allowed to create it
        }
        // Allocate a new PD
        pd_t* pd = alloc_pd();
        if (!pd) {
            return NULL; // Allocation failed
        }
        // Set up the PDPT entry
        pdpt_entry->present = 1;
        pdpt_entry->rw = 1; // Read/write
        pdpt_entry->user = 0; // Kernel mode
        pdpt_entry->base_addr = (uint64_t)pd >> 12; // Base address of PD
    }

    // Get the PD entry
    pd_t* pd = (pd_t*)(pdpt_entry->base_addr << 12);
    dir_entry_t* pd_entry = &pd->entry_t[pd_index];

    // the below if block will create pt if not present and make = 1 
    if (!pd_entry->present) {
        if (!make) {
            return NULL; // Page table does not exist and we are not allowed to create it
        }
        // Allocate a new PT
        pt_t* pt = alloc_pt();
        if (!pt) {
            return NULL; // Allocation failed
        }
        // Set up the PD entry
        pd_entry->present = 1;
        pd_entry->rw = 1; // Read/write
        pd_entry->user = 0; // Kernel mode
        pd_entry->base_addr = (uint64_t)pt >> 12; // Base address of PT
    }

    // Get the PT entry from pd_entry->base_addr
    pt_t* pt = (pt_t*)(pd_entry->base_addr << 12);

    page_t *page = (page_t *) &pt->pages[pt_index];

    // return page pointer
    return page;
}
I am also using 64 bit bitmap to store information about used or unused 4KB sized Physical Frame .Those Physical frame address allocated to page_t structure so I have written below alloc_frame which set page.present bit and store free physical frame address into corresponding page.frame. So alloc_frame set present bit but also I can set the page.present bit by manually by using page1->present = 1, But unfortunetly it is not set in below test_paging function

Code: Select all

void test_paging() {

    print("\nTest of Paging\n");

    // test following address
    uint64_t va1 = PAGE_ALIGN(KMEM_LOW_BASE) + PHYSICAL_TO_VIRTUAL_OFFSET + 40*PAGE_SIZE;
    // uint64_t va2 = PAGE_ALIGN(KMEM_LOW_BASE) + PHYSICAL_TO_VIRTUAL_OFFSET + PAGE_SIZE; 
 
    // Virtual Pointer on based higher half
    uint64_t *v_ptr1 = (uint64_t *) va1; 
    // uint64_t *v_ptr2 = (uint64_t *) va2; 


    // Get Page pointer from above virtual pointer address
    page_t *page1 = (page_t *) get_page((uint64_t)v_ptr1, 1, current_pml4);
    // page_t *page2 = get_page((uint64_t)v_ptr2, 1, current_pml4); 

    assert(((uint64_t)page1 % sizeof(page_t)) == 0);


    page1->present = 1;
    page1->rw = 1;



    debug_page(page1);
    // debug_page(page2);
    
    // allocate a physical frame address in above page with kernel level and writable
    alloc_frame(page1, 1, 1); 
    // alloc_frame(page2, 1, 1); 

    debug_page(page1);
    // debug_page(page2);  

    // Store a value at the virtual pointer
    *v_ptr1 = 0x567; 
    // *v_ptr2 = 0xABC50;  

    print("Content of v_ptr1: ");
    print_hex(*v_ptr1);
    print("\n");

    // print("Content of v_ptr2: ");
    // print_hex(*v_ptr2);
    // print("\n");

    print("Finish Paging Test\n");
}
But it is showing page is not present as it's present bit never bin set by alloc_frame even trying to manually.

Image

The mysterious thing when I am using 1024 MB Memory the above test run successfully.

The full code is in https://github.com/baponkar/KeblaOS

Re: How can I resolve 4 Level Paging issue in x86 architecture based 64 bit OS?

Posted: Sat Jan 18, 2025 11:26 am
by sebihepp
Hi gamingjam60,

CR3 holds the physical address of PML4 table. As limine doesn't setup identity paging, you cannot do

Code: Select all

dir_entry_t* pml4_entry = &pml4->entry_t[pml4_index];
Limine supports HHDM and you need to convert the CR3 physical address to a virtual address first.

Kind regards
Sebi

Re: How can I resolve 4 Level Paging issue in x86 architecture based 64 bit OS?

Posted: Sat Jan 18, 2025 2:24 pm
by gamingjam60
I do not understand your answer. Here pml4 pointer which should be physical pointer not virtual pointer.

Re: How can I resolve 4 Level Paging issue in x86 architecture based 64 bit OS?

Posted: Sat Jan 18, 2025 8:56 pm
by Octocontrabass
Your code only handles 4kiB pages, but Limine may use 2MiB or 1GiB pages in its page tables.

Re: How can I resolve 4 Level Paging issue in x86 architecture based 64 bit OS?

Posted: Mon Jan 20, 2025 4:18 am
by sebihepp
gamingjam60 wrote: Sat Jan 18, 2025 2:24 pm I do not understand your answer. Here pml4 pointer which should be physical pointer not virtual pointer.
Okay, CR3 contains the physical address -> therefore pml4 also contains the physical address. But if you use limine, you are directly in long mode and therefore paging is already enabled. This means, that all addresses your code uses are virtual.

Example:
Lets say CR3 is 0x101000. The same address is in pml4. But when you dereference this address, the cpu expects a virtual address. So your 0x101000 is translated by the MMU into a physical one, as paging is already enabled. So you don't access physical 0x101000, but whereever the page tables for virtual 0x101000 points to. On my machine, limine doesn't map this virtual address and I get a page fault.

Limine features HHDM (Higher Half Direct Memory) which you can request. To make the physical address a virtual one you need to add the HHDM offset to pml4. Only then you can dereference pml4 and it will point to phyiscal 0x101000.

Best regards
Sebi

Re: How can I resolve 4 Level Paging issue in x86 architecture based 64 bit OS?

Posted: Tue Jan 21, 2025 7:56 am
by gamingjam60
I have found the solution by changing higher haslf virtual address VIRTUAL_BASE from 0xFFFFFFFF80000000 to 0xFFFFFFFF80322000 the middle memory may using by bootloader I just trying to clear frame address from the pages present there now my OS is running perfectly.