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!

  • 2
    You don't show enough code, but since you claim you started executing instructions that look like `add [eax], al` I will assume that you mapped the page with your code to the wrong physical address and started executing memory filled with zero bytes. It happens to be zero bytes when executed translates to the 32-bit instruction `add [eax], al`. As to why you have the wrong mapping I can't tell from your code as it isn't an [mcve] – Michael Petch Apr 21 '20 at 14:05
  • @MichaelPetch I updated my question with some more code samples. Unfortunately, it's not quite minimal because I don't know what in my code causes the problem. Anyway, I will appreciate it if you could take a look and maybe you will find what I am doing wrong. Thanks in advance :) – Gili Jacobi Apr 21 '20 at 14:21
  • Have you single-stepped your code in BOCHS? It has a built-in debugger that might help decode some GDT or other structures for you to make sure you set up what you meant. – Peter Cordes Apr 21 '20 at 20:34
  • @PeterCordes I use QEMU, but I will try to use BOCHS, if you say it could help – Gili Jacobi Apr 21 '20 at 21:03
  • I've heard its built-in debugger is better at sorting out early-boot stuff, yes. A good debugger sounds like it would be super helpful in sorting out what your code actually did to the machine state. – Peter Cordes Apr 21 '20 at 21:09
  • 1
    Bochs can easily tell you what physical address maps to a given linear address (command `page`) based on current page tables. It also has a convenient dump of the page mappings (`info tab` command) – Michael Petch Apr 21 '20 at 21:13
  • @MichaelPetch Can you please refer me to a guide on how to configure, install and emulate my own kernel with bochs? I tried to google it, but couldn't find a proper working guide for linux... – Gili Jacobi Apr 22 '20 at 09:55
  • Did you use GRUB or did you write your own bootloader? – Michael Petch Apr 22 '20 at 12:38
  • @MichaelPetch wrote my own bootloader. It is in the same image as my kernel code – Gili Jacobi Apr 22 '20 at 13:32
  • @MichaelPetch I solved the problem without bochs. You can see my answer – Gili Jacobi Apr 22 '20 at 13:42

1 Answers1

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.