Page 1 of 1

James Molloy paging tutorial crashing x86 OS

Posted: Sun Mar 12, 2023 4:43 pm
by movlebxeax
Hello everyone, I am currently writing an x86 OS using Grub and initially was following some tutorials on here, then went on my own for a while, now I am following some tutorials on James Molloy's website: http://www.jamesmolloy.co.uk/tutorial_html/.

I tried to implement his paging tutorial (http://www.jamesmolloy.co.uk/tutorial_h ... aging.html) into my OS after reading it and the paging article on here. For some reason, my code is crashing right when enabling paging (filling the %cr0 register after ORing it).

Code: Select all

void switch_page_directory(page_directory_t *dir)
{ 
    current_directory = dir;
    _write_cr3(&(dir->tablesPhysical));
    for(;;);
    _enable_paging(); // PROBLEM HAPPENS HERE
}
Now, as for the _enable_paging function, that is done in assembler:

Code: Select all

.globl _enable_paging
_enable_paging:
	push %ebp
	mov %esp, %ebp
	mov %cr0, %eax
	or $0x80000000, %eax
	mov %eax, %cr0 // OS CRASHES HERE
	pop %ebp
	ret
Now why this is I have a few guesses, maybe my memory map isn't exactly the same as his but other than that I don't know.

Here is "paging.c"

Code: Select all

#include "lib/stdio.h"
#include "paging.h"
#include "kheap.h"
#include <stdint.h>


// The kernel's page directory
page_directory_t *kernel_directory=0;

// The current page directory;
page_directory_t *current_directory=0;

// A bitset of frames - used or free.
uint32_t *frames;
uint32_t nframes;

// Defined in kheap.c
extern uint32_t placement_address;

// Macros used in the bitset algorithms.
#define INDEX_FROM_BIT(a) (a/(8*4))
#define OFFSET_FROM_BIT(a) (a%(8*4))

// Static function to set a bit in the frames bitset
static void set_frame(uint32_t frame_addr)
{
    uint32_t frame = frame_addr/0x1000;
    uint32_t idx = INDEX_FROM_BIT(frame);
    uint32_t off = OFFSET_FROM_BIT(frame);
    frames[idx] |= (0x1 << off);
}

// Static function to clear a bit in the frames bitset
static void clear_frame(uint32_t frame_addr)
{
    uint32_t frame = frame_addr/0x1000;
    uint32_t idx = INDEX_FROM_BIT(frame);
    uint32_t off = OFFSET_FROM_BIT(frame);
    frames[idx] &= ~(0x1 << off);
}

// Static function to test if a bit is set.
static uint32_t test_frame(uint32_t frame_addr)
{
    uint32_t frame = frame_addr/0x1000;
    uint32_t idx = INDEX_FROM_BIT(frame);
    uint32_t off = OFFSET_FROM_BIT(frame);
    return (frames[idx] & (0x1 << off));
}

// Static function to find the first free frame.
static uint32_t first_frame()
{
    uint32_t i, j;
    for (i = 0; i < INDEX_FROM_BIT(nframes); i++)
    {
        if (frames[i] != 0xFFFFFFFF) // nothing free, exit early.
        {
            // at least one bit is free here.
            for (j = 0; j < 32; j++)
            {
                uint32_t toTest = 0x1 << j;
                if ( !(frames[i]&toTest) )
                {
                    return i*4*8+j;
                }
            }
        }
    }
}
void test_page_fault()
{
    // Allocate a page of memory
    void* page = kmalloc(0x1000);

    // Map the page to virtual address 0x1000
    page_t* page_table_entry = get_page(0x1000, 1, kernel_directory);

    // Clear the present bit to trigger a page fault
    clear_frame(page_table_entry->frame);

    // Try to access the virtual address 0x1000
    uint32_t* ptr = (uint32_t*)0x1000;
    *ptr = 0xDEADBEEF;
}

// Function to allocate a frame.
void alloc_frame(page_t *page, int is_kernel, int is_writeable)
{
    if (page->frame != 0)
    {
        return;
    }
    else
    {
        uint32_t idx = first_frame();
        if (idx == (uint32_t)-1)
        {
            // PANIC! no free frames!!
        }
        set_frame(idx*0x1000);
        page->present = 1;
        page->rw = (is_writeable)?1:0;
        page->user = (is_kernel)?0:1;
        page->frame = idx;
    }
}

// Function to deallocate a frame.
void free_frame(page_t *page)
{
    uint32_t frame;
    if (!(frame=page->frame))
    {
        return;
    }
    else
    {
        clear_frame(frame);
        page->frame = 0x0;
    }
}

void initialize_paging()
{
    // The size of physical memory. For the moment we 
    // assume it is 16MB big.
    uint32_t mem_end_page = 0x1000000;
    
    nframes = mem_end_page / 0x1000;
    frames = (uint32_t*)kmalloc(INDEX_FROM_BIT(nframes));
    memset(frames, 0, INDEX_FROM_BIT(nframes));
    
    // Let's make a page directory.
    kernel_directory = (page_directory_t*)kmalloc_a(sizeof(page_directory_t));
    current_directory = kernel_directory;

    // 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 placement_address
    // by calling kmalloc(). A while loop causes this to be
    // computed on-the-fly rather than once at the start.
    int i = 0;
    while (i < placement_address)
    {
        // Kernel code is readable but not writeable from userspace.
        alloc_frame( get_page(i, 1, kernel_directory), 0, 0);
        i += 0x1000;
    }
    // Before we enable paging, we must register our page fault handler.
    register_interrupt_handler(14, page_fault);

    // Now, enable paging!
    switch_page_directory(kernel_directory);
}

void switch_page_directory(page_directory_t *dir)
{ 
    current_directory = dir;
    _write_cr3(&(dir->tablesPhysical));
    for(;;);
    _enable_paging();
}

page_t *get_page(uint32_t address, int make, page_directory_t *dir)
{
    // Turn the address into an index.
    address /= 0x1000;
    // Find the page table containing this address.
    uint32_t table_idx = address / 1024;
    if (dir->tables[table_idx]) // If this table is already assigned
    {
        return &dir->tables[table_idx]->pages[address%1024];
    }
    else if(make)
    {
        uint32_t tmp;
        dir->tables[table_idx] = (page_table_t*)kmalloc_ap(sizeof(page_table_t), &tmp);
        dir->tablesPhysical[table_idx] = tmp | 0x7; // PRESENT, RW, US.
        return &dir->tables[table_idx]->pages[address%1024];
    }
    else
    {
        return 0;
    }
}




void page_fault(registers_t regs)
{
    // A page fault has occurred.
    // The faulting address is stored in the CR2 register.
    uint32_t faulting_address;
    asm volatile("mov %%cr2, %0" : "=r" (faulting_address));
    
    // The error code gives us details of what happened.
    int present   = !(regs.err_code & 0x1); // Page not present
    int rw = regs.err_code & 0x2;           // Write operation?
    int us = regs.err_code & 0x4;           // Processor was in user-mode?
    int reserved = regs.err_code & 0x8;     // Overwritten CPU-reserved bits of page entry?
    int id = regs.err_code & 0x10;          // Caused by an instruction fetch?

    // Output an error message.
    nanos_printf("Page fault! ( ");
    if (present) {nanos_printf("present ");}
    if (rw) {nanos_printf("read-only ");}
    if (us) {nanos_printf("user-mode ");}
    if (reserved) {nanos_printf("reserved ");}
    nanos_printf(") at 0x%h\n", faulting_address);
    PANIC("Page fault");
}
And "paging.h"

Code: Select all

#ifndef PAGING_H
#define PAGING_H

#include <stdint.h>
#include "idt.h"
#include "lib/stdio.h"
#include "lib/stdlib.h"

typedef struct page
{
   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_table
{
   page_t pages[1024];
} page_table_t;

typedef struct page_directory
{
   /**
      Array of pointers to pagetables.
   **/
   page_table_t *tables[1024];
   /**
      Array of pointers to the pagetables above, but gives their *physical*
      location, for loading into the CR3 register.
   **/
   uint32_t tablesPhysical[1024];
   /**
      The physical address of tablesPhysical. 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 physicalAddr;
} page_directory_t;



/**
  Sets up the environment, page directories etc and
  enables paging.
**/
void initialize_paging();

/**
  Causes the specified page directory to be loaded into the
  CR3 register.
**/
void switch_page_directory(page_directory_t *new);

/**
  Retrieves a pointer to the page required.
  If make == 1, if the page-table in which this page should
  reside isn't created, create it!
**/
page_t *get_page(uint32_t address, int make, page_directory_t *dir);

/**
  Handler for page faults.
**/
void page_fault(registers_t regs); 

/**
Tester to examine whether the page fault system is working
IS SOON TO BE REMOVED
**/
void test_page_fault();

/**
Stubs from paging_stubs.s
**/
extern void _write_cr0(uint32_t data);
extern void _write_cr3(uint32_t data);
extern uint32_t _read_cr0();
extern uint32_t _read_cr3();
extern void _enable_paging();

#endif
And my linker script:

Code: Select all


ENTRY(_start)
 
SECTIONS
{

	. = 1M;
 
	.text BLOCK(4K) : ALIGN(4K)
	{
		*(.multiboot)
		*(.text)
	}
 
	.rodata BLOCK(4K) : ALIGN(4K)
	{
		*(.rodata)
	}

	.data BLOCK(4K) : ALIGN(4K)
	{
		*(.data)
	}
 
	.bss BLOCK(4K) : ALIGN(4K)
	{
		*(COMMON)
		*(.bss)
	}
 
    /DISCARD/ : {
        *(.comment)
        *(.eh_frame)
        *(.note*)
        *(.iplt)
        *(.igot)
    }

    end = .; _end = .; __end = .;
}
What happens is immediately upon startup, it returns to the BIOS screen, then again and again and again until I close QEMU.

Re: James Molloy paging tutorial crashing x86 OS

Posted: Mon Apr 03, 2023 6:08 pm
by Octocontrabass
movlebxeax wrote:now I am following some tutorials on James Molloy's website
Those tutorials have quite a few bugs that have never been fixed. You've been fixing them along the way, right?
movlebxeax wrote:QEMU
QEMU has built-in debugging utilities that can help you here. For example, if you use your debugger to halt right after enabling paging, you can open QEMU's monitor and use "info tlb" and "info mem" to check your page tables. You can add "-d int" to your QEMU command line to see exactly what the CPU thinks is wrong when it crashes. (Also try "-no-reboot" and "-D debug.log".)