Paging problems

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
nexlink
Posts: 1
Joined: Sat Jun 17, 2023 11:58 pm

Paging problems

Post by nexlink »

Hello! I'm trying to implement virtual memory paging in my kernel, but I'm experiencing an issue (a #PF page fault), and was wondering if anyone here could see anything obviously wrong in my code. This is my code for the VMM:

Code: Select all

#include "paging.h"
#include "../allocator/allocator.h"
#include "../../util/memutil.h"
#include "../../system14.h"
#include "../../serial.h"

struct PT pml4 = { 0 };

/*
    * paging.c
    * Exhibits the ability to map physical addresses to virtual addresses
    * in the future we can swap to disk?
    * copyright (C) 2023 danthedev123
    * Resources used:
    *   -> Reference implementation ilobilix: https://github.com/ilobilo/ilobilix/blob/master/kernel/arch/x86_64/mm/vmm.cpp
    * Thanks ilobilo!
    * 
    * Macros KSTATUS, KSTATUS_FAIL, KSTATUS_SUCCESSFUL:
    *   -> Signal to kernel caller wether operation has been successful or not.
    *   -> Defined in system14.h
*/

/*

-------------------------------------------
|    Page Map Level 4 (PML4)              |
-------------------------------------------
|    Page Directory Pointer Table         |
|------------------------------------------
|    Page Directory Entry                 |
|------------------------------------------
|    Page Table Entry                     |
|------------------------------------------

*/

// page fault, instruction fetch, page not present
/* current warning */

struct PT *GetEntryNextLevel(struct PT *curr_level, size_t entry)
{
    if (curr_level == NULL)
    {
        return NULL;
    }

    // if i == 0 twice, 
    if (!curr_level->values[entry].Present)
    {
        void *newalloc = PageAlloc().addr;

        curr_level->values[entry].PhysAddr = (uint64_t)newalloc;

        memset(newalloc, 0x1000, 0);

        curr_level->values[entry].Present = 0b1;
        curr_level->values[entry].RW = 0b1;
        //curr_level->values[entry].UserSupervisor = 0b1;

        return (struct PT *)newalloc;
    }
    else
    {
        return (struct PT *)&curr_level->values[entry];
    }

    /* unreachable */
    return NULL;
}

KSTATUS MapMemory(uint64_t virt, uint64_t phys)
{
    size_t pml4_entry = (virt & (0x1FFULL << 39)) >> 39; // this = 0 is not the problem(!)
    size_t pdpt_entry = (virt & (0x1FFULL << 30)) >> 30;
    size_t pd_entry = (virt & (0x1FFULL << 21)) >> 21;
    size_t pt_entry = (virt & (0x1FFULL << 12)) >> 12;


    /* PML 3 */
    struct PT* pdpt = GetEntryNextLevel(&pml4, pml4_entry);
    /* PML 2 */
    struct PT* pd = GetEntryNextLevel(pdpt, pdpt_entry);
    /* PML 1 -> Lowest level */
    struct PT* pt = GetEntryNextLevel(pd, pd_entry);

    if (pt == NULL)
    {
        /* Failed to get page table */
        return KSTATUS_FAIL; // someone else will deal with it
    }

    pt->values[pt_entry].Present = 0b1;
    pt->values[pt_entry].RW = 0b1;
    pt->values[pt_entry].PhysAddr = (uint64_t)phys;
    //pt->values[pt_entry].UserSupervisor = 0b1;
    
    return KSTATUS_SUCCESSFUL;

}

void InitializePaging(/* struct PML4 pml4 */ uint64_t memLength)
{
    memset((void*)&pml4, sizeof(struct PT), 0);

    for (uint64_t i = 0; i < memLength; i+= 4096)
    {
        KSTATUS m = MapMemory(i, i);
        if (m == KSTATUS_FAIL)
        {
            // Crashes on second iteration ( {i} = 4096 )
            sprint("ERROR! Unable to map page");
            sprint("We are at {i} = ");
            sprint(itoa(i));
            while(1);
        }
    }

    cr3load((void*)&pml4);
}
The cr3load function is an ASM function that puts the value from the parameter and puts it into CR3.
This is how I define PTE and PT:

Code: Select all

struct PTE
{
    uint64_t Present : 1; // Must be 1 to be allowed
    uint64_t RW : 1;
    uint64_t UserSupervisor : 1;
    uint64_t PLWriteThrough : 1;
    uint64_t PLCacheDisable : 1;
    uint64_t Accessed : 1;
    uint64_t Ignored : 1;
    uint64_t PageSize : 1; // We use 4KiB pages so this must be ignored.
    uint64_t Ignored2 : 3;
    uint64_t Ignored3 : 1;
    uint64_t PhysAddr : 40;
    uint64_t Reserved : 12;
}__attribute__((packed));

/* Page Table */
struct PT
{
    struct PTE values[512];
}__attribute__((aligned(0x1000)));

If anyone can see anything wrong with my code please let me know here. P.S I read somewhere it was a good idea to implement a "page table walker". What is that and how can it be used to debug paging?
Thanks :D
Octocontrabass
Member
Member
Posts: 5560
Joined: Mon Mar 25, 2013 7:01 pm

Re: Paging problems

Post by Octocontrabass »

This question was also posted elsewhere. (Doesn't look like all of the bugs have been found yet...)
nullplan
Member
Member
Posts: 1789
Joined: Wed Aug 30, 2017 8:24 am

Re: Paging problems

Post by nullplan »

Why do people keep using bitfields? It never works out well, but everyone thinks they are so much easier than just using flags and bitwise operations, and then they muck it up, and then they cry for help.

In this case, you define struct PT::PhysAddr to be a 40 bit bit field that's 12 bits into the structure. If you do that, then the field corresponds to the page number of the physical page it maps to, not the page itself. So you must assign it "phys_addr >> 12". Also, if you have 64-bit mode, then you can just assume the existence of the NX bit.

Or you do as I do, get rid of the overcomplicated bitfield cruft, and define a page table entry as simply a 64-bit integer. Then a PTE is really just the physical address of the next stage, and some flags ORed in.
Carpe diem!
Post Reply