Davide Della Giustina
Davide Della Giustina

Reputation: 110

OS dev: triple fault when trying to enable paging

I am building a simple OS for learning purposes and I am (currently; I followed different tutorials earlier and customized something by myself) following this tutorial for enabling paging. I'm using QEMU instead of Bochs as my emulator.

If I keep paging disabled everything works fine (even the very basic kmalloc() I implemented), but as soon as I set the PG bit in the cr0 register (i.e. enable paging), everything crashes and QEMU reboots: I suspect that some of the structures (i.e. page directory, page tables, etc.) I have are not created or loaded properly, but I have no way of checking.

I've been trying to solve this problem since a while now, but haven't found a solution. Can anyone see where my mistake is?

Here you can find my complete code: https://github.com/davidedellagiustina/ScratchOS (commit 83b5c8c). Paging code is located in src/cpu/paging.*.

Edit: Setting up a super-basic page directory following exactly this tutorial results in working code. Basing on this simple example, I'm trying to build up the more complex structures (i.e. page_t, page_table_t, page_directory_t) in order to understand the mistake.

Upvotes: 1

Views: 1473

Answers (2)

Davide Della Giustina
Davide Della Giustina

Reputation: 110

I found out I was missing all the flags in the page directory entries (and especially the read/write and the kernel mode ones), as I was putting there just the page table address. I will keep my repository public and I will continue the development from now on, in case anyone needs it in the future.

Edit: Also, I forgot to initialize all the pages (with address and presence bit) when I created a new page table.

Upvotes: 1

Brendan
Brendan

Reputation: 37232

In general:

  • pointers should be for virtual addresses only (and should never be used for physical addresses)

  • physical addresses should probably be using a typedef (e.g. like typedef uint32_t phys_address_t) so that later (when you want to support PAE/Physical Address Extensions) you can change the type (e.g. use typedef uint64_t phys_address_t instead) without breaking everything. This also means you get compile-time warnings/errors when you make silly mistakes (e.g. using a virtual address/pointer where you need a physical address/unsigned integer).

  • almost all of the kernel should be using pointers/virtual addresses for everything. Physical addresses are only used by some device drivers (for bus mastering/DMA) and for the physical memory management itself (to allocate physical pages for page tables, etc; before mapping them into a virtual address space). This includes high level memory management ("kmalloc()" should return a void * pointer and not a physical address).

  • during boot, there's a small period of time when none of the kernel's normal code can work because it uses virtual addresses and paging hasn't been initialized yet. To minimize the size of this period of time (and code duplication caused by having 2 versions of functions - one for "before paging initialized" and another for "after paging initialized") you want to initialize paging as soon as possible; either with a dedicated piece of assembly language startup code that's executed before "main()" (possibly using "statically allocated at compile time" memory in the kernel's ".bss" section for the page directory and page tables), or in the boot loader itself (which is cleaner and more powerful/flexible). Things like setting up a valid kernel stack, and initializing (physical, virtual then heap) memory management, can/should wait until after paging has been initialized.

  • for identity mapping; you'd only need 2 loops (one to create page directory entries and another to create all page table entries), where both loops can be like this (just with different initial values in eax, ecx and edi):

    .nextEntry:
        stosd
        add eax,0x00001000
        loop .nextEntry
    
  • identity mapping isn't great. Normally you want the kernel at a high virtual address (e.g. 0xC0000000) with an area of "deliberately not used to catch NULL pointers" at 0x0000000, and user-space (processes, etc) using normal virtual addresses between them (e.g. maybe starting at virtual address 0x00400000). This makes it annoying for the code that initializes paging and the kernel's linker script (which is why it's cleaner to initialize paging in the boot loader and avoid the mess in the kernel). For this case; you will need to temporarily identity map one page (the page containing the final "mov cr0" that enables paging and the jmp kernel_entry that transfers control to code/the kernel at a higher address), and will want to delete that temporarily identity mapped page after kernel's main is started.

  • you will need to become "very familiar" with the debugging capabilities of your emulator. Qemu has a log that can provide very useful clues, and includes a built in monitor that offers a variety of commands (see https://en.wikibooks.org/wiki/QEMU/Monitor ). You should be able to replace the "mov cr0" (that enables paging) with an endless loop (.die: jmp die), then use the monitor to stop the emulator after it reaches the endless loop and inspect everything (contents of cr3, contents of physical memory) and find out what is wrong with the page directory or page table entries (and do similar immediately after paging is enabled to inspect the virtual address space before your code does anything with it). Qemu also allows you to attach a remote debugger (GDB).

Upvotes: 3

Related Questions