diff --git a/.gdbinit b/.gdbinit deleted file mode 100644 index ca0c493..0000000 --- a/.gdbinit +++ /dev/null @@ -1,9 +0,0 @@ -# Connect to qemu's gdbserver -target remote :1234 - -# Change layout -layout reg - -# Continue execution until entering the kernel -break kernel_main -continue diff --git a/kernel/arch/i686/linker.ld b/kernel/arch/i686/linker.ld index 56cd477..23229d8 100644 --- a/kernel/arch/i686/linker.ld +++ b/kernel/arch/i686/linker.ld @@ -4,37 +4,71 @@ */ ENTRY(_kernel_start) +_kernel_physical_start = 0x00100000; +_kernel_higher_half_offset = 0xC0000000; +_kernel_virtual_start = (_kernel_physical_start + _kernel_higher_half_offset); + SECTIONS { + /* + * Starting from #6 we now use a higher-half kernel. + * + * The load address of the kernel is different from the relocation address (AT). + * We run the code as if in higher-half, but is should in fact be loaded at the + * beginning of the RAM to be detected by the bootloader. + * + * We still need to run the piece of code that jumps to our main function, + * that is why we have 2 different sections for the code: + * * .text.startup: should be identically mapped (load address == relocation address) + * * .text: should be loaded as higher half + * + * The code inside the .text.startup section can be discarded once we jumped to our + * main C function, we shall never get back to it anyway. + * + * For more information, refer to: https://wiki.osdev.org/Higher_Half_x86_Bare_Bones + */ + /* We place our kernel at 1MiB, it's a conventional place for it to be here. */ - . = 1M; + . = ALIGN(4K); + . = _kernel_physical_start; + + /* Code needed to bootstrap our kernel, cannot be remmaped */ + .startup ALIGN(4K) : + { + KEEP(*(.multiboot)) + *(.text.startup) + } + + /* Everything after this is relocated to the end of the address space (> 3GiB) and needs paging to be activated */ + + . = ALIGN(4K); + . += _kernel_higher_half_offset; - kernel_code_start_address = .; + _kernel_code_start = .; - .text BLOCK(4K) : ALIGN(4K) + .text ALIGN(4K) : AT (ADDR (.text) - _kernel_higher_half_offset) { - *(.multiboot) *(.text) } /* Read-only data */ - .rodata BLOCK(4K) : ALIGN(4K) + .rodata ALIGN(4K) : AT (ADDR (.rodata) - _kernel_higher_half_offset) { *(.rodata) } - /* Initailized data (RW) */ - .data BLOCK(4K) : ALIGN(4K) + /* Initialized data (RW) */ + .data ALIGN(4K) : AT (ADDR (.data) - _kernel_higher_half_offset) { *(.data) } /* Uninitialized data and stack (RW) */ - .bss BLOCK(4K) : ALIGN(4K) + .bss ALIGN(4K) : AT (ADDR (.bss) - _kernel_higher_half_offset) { *(COMMON) *(.bss) } - kernel_code_end_address = .; + _kernel_code_end = .; } diff --git a/kernel/arch/i686/memory/mmu.c b/kernel/arch/i686/memory/mmu.c index ec209b4..2903702 100644 --- a/kernel/arch/i686/memory/mmu.c +++ b/kernel/arch/i686/memory/mmu.c @@ -1,9 +1,10 @@ #include +#include #include #include +#include #include -#include #include #include #include @@ -61,6 +62,10 @@ typedef struct PACKED { static __attribute__((__aligned__(PAGE_SIZE))) mmu_pde_entry kernel_page_directory[MMU_PDE_COUNT]; +// Keep track whether paging has been fully enabled. +// This doesn't count temporarily enabling it to jump into higher half. +static bool paging_enabled = false; + static inline void mmu_flush_tlb(u32 tlb_entry) { ASM("invlpg (%0)" ::"r"(tlb_entry) : "memory"); @@ -75,7 +80,8 @@ static inline void mmu_flush_tlb(u32 tlb_entry) // way to set its contentt to be that of the currently running process. bool mmu_start_paging(void) { - static void *page_directory = kernel_page_directory; + static void *page_directory = + (void *)KERNEL_HIGHER_HALF_PHYSICAL(kernel_page_directory); // Set CR3 to point to our page directory write_cr3((u32)page_directory); @@ -89,9 +95,18 @@ bool mmu_start_paging(void) u32 cr0 = read_cr0(); write_cr0(BIT_SET(cr0, 31)); // PG = bit 32 + paging_enabled = true; + return true; } +/// @brief Remap an address range given an offset. +/// +/// example: +/// mmu_offset_map(0x0000, 0x00FF, 0xFF) will remap the physical range +/// [0x0000; 0x00FF] to the virtual range [0xFF00; 0xFFFF] +static void mmu_offset_map(uint32_t start, uint32_t end, int64_t offset); + bool mmu_init(void) { log_info("MMU", "Initializing MMU"); @@ -105,10 +120,17 @@ bool mmu_init(void) // @link https://medium.com/@connorstack/recursive-page-tables-ad1e03b20a85 kernel_page_directory[MMU_PDE_COUNT - 1].present = 1; kernel_page_directory[MMU_PDE_COUNT - 1].page_table = - MMU_PAGE_ADDRESS(kernel_page_directory); + MMU_PAGE_ADDRESS(KERNEL_HIGHER_HALF_PHYSICAL(kernel_page_directory)); - // For more simplicity, we identity map the content of our kernel. - mmu_identity_map(align(KERNEL_CODE_START, PAGE_SIZE), KERNEL_CODE_END); + // We remap our higher-half kernel. + // The addresses over 0xC0000000 will point to our kernel's code (0x00000000 + // in physical) + mmu_offset_map(KERNEL_HIGHER_HALF_PHYSICAL(KERNEL_CODE_START), + KERNEL_HIGHER_HALF_PHYSICAL(KERNEL_CODE_END), + KERNEL_HIGHER_HALF_OFFSET); + + // FIXME: Check conflict with PPM + // FIXME: Is it really necessary ? // We also map the first 1M of physical memory, it will be reserved for // hardware structs. mmu_identity_map(0x0, 0x100000); @@ -121,6 +143,8 @@ bool mmu_map(u32 virtual, u32 pageframe) u16 pde_index = virtual >> 22; // bits 31-22 u16 pte_index = (virtual >> 12) & ((1 << 10) - 1); // bits 21-12 + // TODO: ASSERT alignment + // TODO: Hard-coded to work with kernel page tables only // This should take the pagedir/pagetables as input later on // @@ -135,12 +159,15 @@ bool mmu_map(u32 virtual, u32 pageframe) } // Virtual address of the corresponding page table (physical if CR0.PG=0) - bool paging_enabled = BIT(read_cr0(), 31); - mmu_pte_entry *page_table = - (mmu_pte_entry *)(paging_enabled - ? MMU_RECURSIVE_PAGE_TABLE_ADDRESS(pde_index) - : (kernel_page_directory[pde_index].page_table - << 12)); + mmu_pte_entry *page_table; + + if (paging_enabled) { + page_table = + (mmu_pte_entry *)MMU_RECURSIVE_PAGE_TABLE_ADDRESS(pde_index); + } else { + page_table = (mmu_pte_entry *)KERNEL_HIGHER_HALF_VIRTUAL( + kernel_page_directory[pde_index].page_table << 12); + } if (page_table[pte_index].present) { log_err("MMU", @@ -180,9 +207,14 @@ void mmu_unmap(u32 virtual) mmu_flush_tlb(virtual); } -void mmu_identity_map(uint32_t start, uint32_t end) +static void mmu_offset_map(uint32_t start, uint32_t end, int64_t offset) { for (; start < end; start += PAGE_SIZE) { - mmu_map(start, start); + mmu_map(start + offset, start); } } + +void mmu_identity_map(uint32_t start, uint32_t end) +{ + mmu_offset_map(start, end, 0); +} diff --git a/kernel/include/kernel/memory.h b/kernel/include/kernel/memory.h new file mode 100644 index 0000000..d4d0589 --- /dev/null +++ b/kernel/include/kernel/memory.h @@ -0,0 +1,74 @@ +/** @file memory.h + * + * Constants related to the kernel's memory layout. + */ + +#ifndef KERNEL_MEMORY_H +#define KERNEL_MEMORY_H + +#ifndef KERNEL_STACK_SIZE +#define KERNEL_STACK_SIZE 0x4000 +#endif + +// NOTE: This maybe belong inside the arch directory ? (cf. #5) + +// The size of a single page +#define PAGE_SIZE (4096) + +// 32-bit address bus -> 4GiB of addressable memory +#define ADDRESS_SPACE_SIZE (0x100000000UL) +#define ADDRESS_SPACE_END (ADDRESS_SPACE_SIZE - 1) + +/// Starting from #6, our kernel uses the higher-half design. +/// +/// This means that the kernel code's virtual address differs from its physical +/// one. The following constants are used by the linker script and should be +/// used by the functions that depend on them instead of hardcoding values. + +#define KERNEL_IS_HIGHER_HALF 1 + +// TODO: These values are duplicated inside the linkerscript +// We should maybe find a way to include this header before linking the +// kernel to avoid conflicts + +#define KERNEL_PHYSICAL_START 0x00100000UL +#define KERNEL_HIGHER_HALF_OFFSET 0xC0000000UL +#define KERNEL_VIRTUAL_START (KERNEL_PHYSICAL_START + KERNEL_HIGHER_HALF_OFFSET) + +#ifdef __ASSEMBLER__ + +#define KERNEL_HIGHER_HALF_PHYSICAL(_virtual) \ + ((_virtual)-KERNEL_HIGHER_HALF_OFFSET) +#define KERNEL_HIGHER_HALF_VIRTUAL(_physical) \ + ((_physical) + KERNEL_HIGHER_HALF_OFFSET) + +#else + +#define KERNEL_HIGHER_HALF_PHYSICAL(_virtual) \ + ((u32)(_virtual)-KERNEL_HIGHER_HALF_OFFSET) +#define KERNEL_HIGHER_HALF_VIRTUAL(_physical) \ + ((u32)(_physical) + KERNEL_HIGHER_HALF_OFFSET) + +#include + +/// @brief Address of the byte located just before the end of the kernel's code +/// +/// Any byte written after this address **WILL** overwrite our kernel's +/// executable binary. +/// +/// @info this address is defined inside the kernel's linker scrpit. +extern u32 _kernel_code_start; +#define KERNEL_CODE_START ((u32)&_kernel_code_start) + +/// @brief Address of the byte located just after the end of the kernel's code +/// +/// Any byte written after this address will not overwrite our kernel's +/// executable binary. +/// +/// @info this address is defined inside the kernel's linker scrpit. +extern u32 _kernel_code_end; +#define KERNEL_CODE_END ((u32)&_kernel_code_end) + +#endif /* __ASSEMBLER__ */ + +#endif /* KERNEL_MEMORY_H */ diff --git a/kernel/include/kernel/pmm.h b/kernel/include/kernel/pmm.h index 8196d01..8dd5a69 100644 --- a/kernel/include/kernel/pmm.h +++ b/kernel/include/kernel/pmm.h @@ -14,17 +14,12 @@ #ifndef KERNEL_PMM_H #define KERNEL_PMM_H +#include + #include #include #include -// The size of a single page -#define PAGE_SIZE (4096) - -// 32-bit address bus -> 4GiB of addressable memory -#define ADDRESS_SPACE_SIZE (0x100000000UL) -#define ADDRESS_SPACE_END (ADDRESS_SPACE_SIZE - 1) - // Error value returned by the PMM in case of errors #define PMM_INVALID_PAGEFRAME (0xFFFFFFFFUL) @@ -39,24 +34,6 @@ // reference value (e.g. the physical memory manager's bit map size). #define TOTAL_PAGEFRAMES_COUNT (ADDRESS_SPACE_SIZE / PAGE_SIZE) -/// @brief Address of the byte located just after the end of the kernel's code -/// -/// Any byte written after this address will not overwrite our kernel's -/// executable binary. -/// -/// @info this address is defined inside the kernel's linker scrpit. -extern u32 kernel_code_end_address; -#define KERNEL_CODE_END ((u32)&kernel_code_end_address) - -/// @brief Address of the byte located just before the end of the kernel's code -/// -/// Any byte written after this address **WILL** overwrite our kernel's -/// executable binary. -/// -/// @info this address is defined inside the kernel's linker scrpit. -extern u32 kernel_code_start_address; -#define KERNEL_CODE_START ((u32)&kernel_code_start_address) - /// \defgroup pmm_allocation_flags /// /// Flags used when allocating a page frame to specify that the allocation must diff --git a/kernel/meson.build b/kernel/meson.build index 7c996c3..870e2cb 100644 --- a/kernel/meson.build +++ b/kernel/meson.build @@ -1,7 +1,6 @@ arch = target_machine.cpu() kernel_compile_flags = [ - '-DSTACK_SIZE=16384', '-fomit-frame-pointer', ] diff --git a/kernel/src/crt0.S b/kernel/src/crt0.S index 59a226c..6e69fb9 100644 --- a/kernel/src/crt0.S +++ b/kernel/src/crt0.S @@ -1,5 +1,6 @@ -#include +#include +#include /* * Define the multiboot_header, following the GNU multiboot specifications, @@ -15,10 +16,6 @@ .long MULTIBOOT_HEADER_FLAGS .long -(MULTIBOOT_HEADER_FLAGS + MULTIBOOT_HEADER_MAGIC) -#ifndef STACK_SIZE -#error You must define STACK_SIZE -#endif - /* * The stack, required by C functions and not defined by default. * Should be placed inside the stack pointer register (esp) inside @@ -30,9 +27,34 @@ .section .bss .align 16 stack_bottom: - .skip STACK_SIZE + .skip KERNEL_STACK_SIZE stack_top: +/* + * Temporary paging structure used by the startup code to temporarily activate paging. + + * This is needed as we use a higher-half kernel design, meaning the kernel's code + * has been virtually relocated into the higher-half of the address space by the linker. + * + * Refer to #6 for more info. + * + * WARNING: Each and every block of 4MiB inside the address space are mapped to the first 1024 pageframes. + * This only works if the kernel is smaller than 3MiB in size. + */ + +.section .data +.align PAGE_SIZE +startup_page_directory: + .rept 1024 + .long (startup_page_table - 0xC0000000) + 1 + .endr +startup_page_table: + .set addr, 0 + .rept 1024 + .long addr | 3 + .set addr, addr + 0x1000 + .endr + /* * The entry point of our kernel (as defined in our linker script). * @@ -43,11 +65,28 @@ stack_top: * no paging, and complete control over the hardware. The wild wild west ... */ -.section .text +.section .text.startup .global _kernel_start .type _kernel_start, @function _kernel_start: + // install temporary bootstrap paging structure + movl $KERNEL_HIGHER_HALF_PHYSICAL(startup_page_directory), %ecx + movl %ecx, %cr3 + // activate paging + movl %cr0, %ecx + orl $0x80000000, %ecx + movl %ecx, %cr0 + // jump to higher half + lea higher_half, %ecx + jmp *%ecx + +.size _kernel_start, . - _kernel_start + +.section .text +higher_half: + + // Starting from here we are running in higher-half ! // Initialize stack pointer mov $stack_top, %esp @@ -55,6 +94,7 @@ _kernel_start: // The bootloader stores information about our system (memory map, ...) // inside these registers. We push them onto the stack to be able to access them // as arguments of our main function. + addl $KERNEL_HIGHER_HALF_OFFSET, %ebx push %eax push %ebx @@ -68,5 +108,4 @@ _kernel_exit_loop: hlt jmp _kernel_exit_loop -// Set the size of the _kernel_start symbol for debugging purposes -.size _kernel_start, . - _kernel_start +.size higher_half, . - higher_half diff --git a/kernel/src/memory/pmm.c b/kernel/src/memory/pmm.c index 453b4a1..c044027 100644 --- a/kernel/src/memory/pmm.c +++ b/kernel/src/memory/pmm.c @@ -38,13 +38,13 @@ typedef struct { static pmm_frame_allocator g_pmm_user_allocator = { .start = 0, - .end = (u32)&kernel_code_start_address, + .end = KERNEL_CODE_START, .first_available = PMM_INVALID_PAGEFRAME, .initialized = false, }; static pmm_frame_allocator g_pmm_kernel_allocator = { - .start = (u32)&kernel_code_start_address, + .start = KERNEL_CODE_START, .end = ADDRESS_SPACE_END, .first_available = PMM_INVALID_PAGEFRAME, .initialized = false, @@ -108,14 +108,16 @@ static bool pmm_initialize_bitmap(struct multiboot_info *mbt) // We still need to check whether the pages are located inside // our kernel's code, or we would be succeptible to overwrite // its code. - if (IN_RANGE(addr, KERNEL_CODE_START, KERNEL_CODE_END)) + if (IN_RANGE(KERNEL_HIGHER_HALF_VIRTUAL(addr), + KERNEL_CODE_START, KERNEL_CODE_END)) continue; pmm_bitmap_set(addr, PMM_AVAILABLE); available_pageframes += 1; - pmm_frame_allocator *allocator = (addr >= KERNEL_CODE_START) - ? &g_pmm_kernel_allocator - : &g_pmm_user_allocator; + pmm_frame_allocator *allocator = + (KERNEL_HIGHER_HALF_VIRTUAL(addr) >= KERNEL_CODE_START) + ? &g_pmm_kernel_allocator + : &g_pmm_user_allocator; if (allocator->first_available == PMM_INVALID_PAGEFRAME) { allocator->first_available = addr; @@ -212,7 +214,8 @@ u32 pmm_allocate(int flags) void pmm_free(u32 pageframe) { - if (IN_RANGE(pageframe, KERNEL_CODE_END, KERNEL_CODE_START)) { + if (IN_RANGE(KERNEL_HIGHER_HALF_VIRTUAL(pageframe), KERNEL_CODE_END, + KERNEL_CODE_START)) { log_err("PMM", "Trying to free kernel code pages: " LOG_FMT_32, pageframe); return; @@ -226,9 +229,10 @@ void pmm_free(u32 pageframe) return; } - pmm_frame_allocator *allocator = (pageframe >= KERNEL_CODE_START) - ? &g_pmm_kernel_allocator - : &g_pmm_user_allocator; + pmm_frame_allocator *allocator = + (KERNEL_HIGHER_HALF_VIRTUAL(pageframe) >= KERNEL_CODE_START) + ? &g_pmm_kernel_allocator + : &g_pmm_user_allocator; pmm_bitmap_set(pageframe, PMM_AVAILABLE); diff --git a/scripts/debug.sh b/scripts/debug.sh new file mode 100755 index 0000000..5fda148 --- /dev/null +++ b/scripts/debug.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +# Start a GDB session connected to our kernel. +# You can also pass in symbols to set breakpoints on as arguments. +# +# example: ./debug.sh kernel_main mmu_init + +# Absolute path to this script +SCRIPT=$(readlink -f "$0") +SCRIPTDIR=$(dirname "$SCRIPT") +GITROOT="$SCRIPTDIR/.." + +if ! command -v gdb > /dev/null; then + echo "[ERROR] GDB not found in path" >&2 + exit 127 +fi + +cd "$GITROOT" || exit + +BREAKPOINTS=() +for symbol in "$@"; do + BREAKPOINTS+=("-ex" "break $symbol") +done + +if [ ! -d "build" ]; then + echo "[INFO] build dir not found, setting up meson project" + meson setup build --cross-file "scripts/meson_cross.ini" --reconfigure -Dbuildtype=debug +fi + +echo "[INFO] Starting a debugging session" +ninja -C build qemu-server || exit +gdb --symbol ./build/kernel/kernel.sym \ + -iex "set pagination of" \ + -iex "target remote localhost:1234" \ + "${BREAKPOINTS[@]}" \ + -ex "continue" + +echo "[INFO] Terminating debugging session" +pkill qemu