Identity mapping dosen't work after enabling paging

0

I try to implement my own OS, and now I try to implement paging mechanism.
I created a page directory, and created identity mapping of the kernel code. However, after storing the physical address of the first page table and enabling paging, my code seems lost - meaning, the virtual address not mapped to a physical address, or mapped to a wrong one. I can see this behaviour in GDB: my code opcodes turn to add [eax], al.

My structs are:

#define MAX_PAGES_PER_TABLE 1024
#define MAX_TABLES_PER_DIR 1024
typedef struct {
    uint32_t present    : 1;   // Page present in memory
    uint32_t rw         : 1;   // Read-only if clear, readwrite if set
    uint32_t user       : 1;   // Supervisor level only if clear
    uint32_t accessed   : 1;   // Has the page been accessed since last refresh?
    uint32_t dirty      : 1;   // Has the page been written to since last refresh?
    uint32_t unused     : 7;   // Amalgamation of unused and reserved bits
    uint32_t frame      : 20;  // Frame address (shifted right 12 bits)
} page_t;

typedef struct {
    page_t pages[MAX_PAGES_PER_TABLE];
} page_table_t;

typedef struct {
    page_table_t* page_tables[MAX_TABLES_PER_DIR]; // Array of pointers to pagetables.
    /*
       Array of pointers to the pagetables above, but gives their *physical*
       location, for loading into the CR3 register.
    */
    uint32_t page_tables_physical[MAX_TABLES_PER_DIR];
    /*
       The physical address of page_tables_physical. This comes into play
       when we get our kernel heap allocated and the directory
       may be in a different location in virtual memory.
    */
    uint32_t physical_address;
} page_directory_t;

Here is my enabling paging code:

page_directory_t* current_page_dir;

void switch_page_directory(page_directory_t* new_page_directory)
{
    current_page_dir = new_page_directory;
    // let the cpu know the physical address of the page tables
    asm volatile("mov %0, %%cr3" : : "r"(&new_page_directory->page_tables_physical));
    uint32_t cr0;
    asm volatile("mov %%cr0, %0" : "=r"(cr0));
    cr0 |= 0x80000000; // set the PG flag of cr0 - enable paging
    asm volatile("mov %0, %%cr0" : : "r" (cr0));
}

I double checked that the page directory looks ok, but maybe I am wrong so here is the identity mapping code:

void alloc_frame(page_t *page, int is_kernel, int is_writeable)
{
    if (page->frame != 0) {
        // there is already a frame associated with the page
        return;
    }
    uint32_t first_free_frame_address = first_not_set(frames);
    if (first_free_frame_address == frames.len + 1) {
        panic("no free pages");
    }

    set_bit(first_free_frame_address, frames);
    page->frame = first_free_frame_address * ALIGNMENT;
    page->present = 1;
    page->rw = is_writeable ? 1: 0;
    page->user = is_kernel ? 0 : 1;
}

page_t* get_page(uint32_t address, int create, page_directory_t* dir)
{
    // when dividing the address by alignment, the index of the page is received
    uint32_t address_index = address / ALIGNMENT;
    // according to the index of the address,
    // the index of the table, and the index of the page in the table are calculated
    uint32_t table_index = address_index / MAX_PAGES_PER_TABLE;
    uint32_t page_index = address_index % MAX_PAGES_PER_TABLE;
    if (dir->page_tables[table_index]) {
        // page table exists
        return &dir->page_tables[table_index]->pages[page_index];
    } else if (create) {
        // page table doesn't exist - creating it
        uint32_t physical_address;
        dir->page_tables[table_index] = (page_table_t*) kmalloc_internal(sizeof(page_table_t), 1, &physical_address);
        // set a first entry in the table
        memory_set((uint8_t*)dir->page_tables[table_index], 0, ALIGNMENT);
        // give the first page, the attributes: Present, Read/Write, Kernel page
        physical_address |= 0x3;
        // save the physical address of the table
        dir->page_tables_physical[table_index] = physical_address;
        return &dir->page_tables[table_index]->pages[page_index];
    } else {
        //page table doesn't exist - page cannot be retrieved
        return 0;
    }
}

void initialize_paging()
{
    initialize_frames();
    page_directory_t* page_dir = (page_directory_t*) kmalloc(sizeof(page_directory_t));
    memory_set((uint8_t*) page_dir, 0, sizeof(page_directory_t));
    // set the physical addresses of the page tables to 0 with attributes:
    // kernel tables, rw, not present
    int j;
    uint8_t* tmp = (uint8_t*) page_dir->page_tables_physical;
    for (j = 0; j < 1024; j++) {
        memory_set(tmp, 2, 1);
        tmp++;
        memory_set(tmp, 0, 3);
        tmp += 3;
    }
    current_page_dir = page_dir;

    // We need to identity map (phys addr = virt addr) from
    // 0x0 to the end of used memory, so we can access this
    // transparently, as if paging wasn't enabled.
    // NOTE that we use a while loop here deliberately.
    // inside the loop body we actually change free_physical_address
    // by calling kmalloc(). A while loop causes this to be
    // computed on-the-fly rather than once at the start.
    uint32_t free_physical_address = get_current_physicall_address();
    uint32_t i = 0;
    while (i <= free_physical_address)
    {
        // Kernel code is readable but not writeable from userspace.
        alloc_frame(get_page(i, 1, page_dir), 1, 1);
        i += ALIGNMENT;
        // get_page() may allocate more space for new tables
        free_physical_address = get_current_physicall_address();
    }

That's a lot of code so I will sum up:
alloc_frame() finds the next free frame - using a bitset (I don't put the bitset code here because it works correctly.
get_page(), returns the page entry corresponding to the given address, and creates the page table if it doesn't exist.
initialize_paging() creates the page directory and does the identity mapping.

The following functions are not here but that's their summary:
kmalloc() allocates memory aligned to 4096.
get_current_physicall_address() returns the address which kmalloc will allocate on the next run.
memory_set() is my implementation of memset().

Any help will be appreciated!
Thanks in advance!

gcc
x86
paging
inline-assembly
osdev
asked on Stack Overflow Apr 21, 2020 by Gili Jacobi • edited Apr 21, 2020 by Gili Jacobi

1 Answer

2

I SOLVED IT!

The problem was with struct page_t. The struct uses bit fields, therefore when I put a value in the frame member of the struct, the value was shifted right 12 bits.
The problem was that when I set the frame in alloc_frame(), I already shifted the value myself, so the pages pointed to wrong frames.
The reason I thought the pages were correctly mapped to frames was because GDB showed me the original value, and not the one that is actually in the struct.
That must have happened because of the bit fields.

answered on Stack Overflow Apr 22, 2020 by Gili Jacobi

User contributions licensed under CC BY-SA 3.0