Newer
Older
minerva / Kernel / Prekernel / init.cpp
@minerva minerva on 13 Jul 11 KB Initial commit
/*
 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
 * Copyright (c) 2021, Gunnar Beutner <gbeutner@serenityos.org>
 * Copyright (c) 2021-2024, Liav A. <liavalb@hotmail.co.il>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/Types.h>
#include <Kernel/Boot/Multiboot.h>
#include <Kernel/Memory/PhysicalAddress.h>
#include <Kernel/Memory/VirtualAddress.h>
#include <Kernel/Prekernel/DebugOutput.h>
#include <Kernel/Prekernel/Prekernel.h>
#include <Kernel/Prekernel/Random.h>
#include <Kernel/Prekernel/Runtime.h>
#include <LibELF/ELFABI.h>
#include <LibELF/Relocation.h>

// Defined in the linker script
extern uintptr_t __stack_chk_guard;
uintptr_t __stack_chk_guard __attribute__((used));
extern "C" [[noreturn]] void __stack_chk_fail();

extern "C" u8 start_of_prekernel_image[];
extern "C" u8 end_of_prekernel_image[];

extern "C" u8 _binary_Kernel_standalone_start[];
extern "C" u8 end_of_prekernel_image_after_kernel_image[];

extern "C" u64 boot_pml4t[512];
extern "C" u64 boot_pdpt[512];
extern "C" u64 boot_pd0[512];
extern "C" u64 boot_pd0_pts[512 * (MAX_KERNEL_SIZE >> 21 & 0x1ff)];
extern "C" u64 boot_pd_kernel[512];
extern "C" u64 boot_pd_kernel_pt0[512];
extern "C" u64 boot_pd_kernel_image_pts[512 * (MAX_KERNEL_SIZE >> 21 & 0x1ff)];
extern "C" u64 boot_pd_kernel_pt1023[512];
extern "C" char const kernel_cmdline[4096];

extern "C" void reload_cr3();

extern "C" {
multiboot_info_t* multiboot_info_ptr;
}

void __stack_chk_fail()
{
    halt();
}

namespace Kernel {

// boot.S expects these functions to exactly have the following signatures.
// We declare them here to ensure their signatures don't accidentally change.
extern "C" [[noreturn]] void init();

// Minerva Pre-Kernel Environment C++ entry point :^)
//
// This is where C++ execution begins, after boot.S transfers control here.
//

static void memmove_virt(void* dest_virt, FlatPtr dest_phys, void* src, size_t n)
{
    if (dest_phys < (FlatPtr)src) {
        u8* pd = (u8*)dest_virt;
        u8 const* ps = (u8 const*)src;
        for (; n--;)
            *pd++ = *ps++;
        return;
    }

    u8* pd = (u8*)dest_virt;
    u8 const* ps = (u8 const*)src;
    for (pd += n, ps += n; n--;)
        *--pd = *--ps;
}

extern "C" [[noreturn]] void init()
{
    u32 initrd_module_start = 0;
    u32 initrd_module_end = 0;
    if (multiboot_info_ptr->mods_count > 0) {
        // We only consider the first specified multiboot module, and ignore
        // the rest of the modules.
        multiboot_module_entry_t* initrd_module = (multiboot_module_entry_t*)(FlatPtr)multiboot_info_ptr->mods_addr;
        VERIFY(initrd_module->start < initrd_module->end);

        initrd_module_start = initrd_module->start;
        initrd_module_end = initrd_module->end;
    }

    u8* kernel_image = _binary_Kernel_standalone_start;
    // copy the ELF header and program headers because we might end up overwriting them
    Elf_Ehdr kernel_elf_header = *(Elf_Ehdr*)kernel_image;
    Elf_Phdr kernel_program_headers[16];
    VERIFY(kernel_elf_header.e_phnum < array_size(kernel_program_headers));
    __builtin_memcpy(kernel_program_headers, kernel_image + kernel_elf_header.e_phoff, sizeof(Elf_Phdr) * kernel_elf_header.e_phnum);

    FlatPtr kernel_physical_base = (FlatPtr)0x200000;
    FlatPtr default_kernel_load_base = KERNEL_MAPPING_BASE + kernel_physical_base;

    FlatPtr kernel_load_base = default_kernel_load_base;

    if (__builtin_strstr(kernel_cmdline, "disable_kaslr") == nullptr) {
        FlatPtr maximum_offset = (FlatPtr)KERNEL_PD_SIZE - MAX_KERNEL_SIZE - 2 * MiB; // The first 2 MiB are used for mapping the pre-kernel
#ifdef KERNEL_ADDRESS_SANITIZER_ENABLED
        // To allow for easy mapping between the kernel virtual addresses and KASAN shadow memory,
        // we map shadow memory at the very end of the virtual range, so that we can index into it
        // using just an offset. To ensure this range is free when needed, we restrict the possible
        // KASLR range when KASAN is enabled to make sure we don't use the end of the virtual range.
        maximum_offset -= ceil_div(maximum_offset, 9ul);
#endif
        kernel_load_base += (generate_secure_seed() % maximum_offset);
        kernel_load_base &= ~(2 * MiB - 1);
    }

    FlatPtr kernel_load_end = 0;
    for (size_t i = 0; i < kernel_elf_header.e_phnum; i++) {
        auto& kernel_program_header = kernel_program_headers[i];
        if (kernel_program_header.p_type != PT_LOAD)
            continue;
        auto start = kernel_load_base + kernel_program_header.p_vaddr;
        auto end = start + kernel_program_header.p_memsz;
        VERIFY(start > (FlatPtr)end_of_prekernel_image);
        VERIFY(kernel_physical_base + kernel_program_header.p_paddr > (FlatPtr)end_of_prekernel_image);
        if (end > kernel_load_end)
            kernel_load_end = end;
    }

    // align to 1GB
    FlatPtr kernel_mapping_base = kernel_load_base & ~(FlatPtr)0x3fffffff;

    VERIFY(kernel_load_base % 0x1000 == 0);
    VERIFY(kernel_load_base >= kernel_mapping_base + kernel_physical_base);

    int pdpt_flags = 0x3;

    boot_pdpt[(kernel_mapping_base >> 30) & 0x1ffu] = (FlatPtr)boot_pd_kernel | pdpt_flags;

    boot_pd_kernel[0] = (FlatPtr)boot_pd_kernel_pt0 | 0x3;

    for (FlatPtr vaddr = kernel_load_base; vaddr <= kernel_load_end; vaddr += PAGE_SIZE * 512)
        boot_pd_kernel[(vaddr - kernel_mapping_base) >> 21] = (FlatPtr)(&boot_pd_kernel_image_pts[(vaddr - kernel_load_base) >> 12]) | 0x3;

    __builtin_memset(boot_pd_kernel_pt0, 0, sizeof(boot_pd_kernel_pt0));

    VERIFY((size_t)end_of_prekernel_image < array_size(boot_pd_kernel_pt0) * PAGE_SIZE);

    /* pseudo-identity map 0M - end_of_prekernel_image */
    for (size_t i = 0; i < (FlatPtr)end_of_prekernel_image / PAGE_SIZE; i++)
        boot_pd_kernel_pt0[i] = i * PAGE_SIZE | 0x3;

    __builtin_memset(boot_pd_kernel_image_pts, 0, sizeof(boot_pd_kernel_image_pts));

    for (size_t i = 0; i < kernel_elf_header.e_phnum; i++) {
        auto& kernel_program_header = kernel_program_headers[i];
        if (kernel_program_header.p_type != PT_LOAD)
            continue;
        for (FlatPtr offset = 0; offset < kernel_program_header.p_memsz; offset += PAGE_SIZE) {
            auto pte_index = ((kernel_load_base & 0x1fffff) + kernel_program_header.p_vaddr + offset) >> 12;
            boot_pd_kernel_image_pts[pte_index] = (kernel_physical_base + kernel_program_header.p_paddr + offset) | 0x3;
        }
    }

    boot_pd_kernel[511] = (FlatPtr)boot_pd_kernel_pt1023 | 0x3;

    // Fill-in multiboot-related info before loading kernel as to avoid accidentally
    // overwriting mbi end as to avoid to check whether it's mapped after reloading page tables.
    BootInfo info {};

    auto adjust_by_mapping_base = [kernel_mapping_base](auto ptr) {
        return (decltype(ptr))((FlatPtr)ptr + kernel_mapping_base);
    };

    info.boot_method = BootMethod::Multiboot1;
    info.boot_method_specific.pre_init.~PreInitBootInfo();
    new (&info.boot_method_specific.multiboot1) Multiboot1BootInfo;

    info.boot_method_specific.multiboot1.flags = multiboot_info_ptr->flags;
    info.boot_method_specific.multiboot1.memory_map = bit_cast<multiboot_memory_map_t const*>(adjust_by_mapping_base((FlatPtr)multiboot_info_ptr->mmap_addr));
    info.boot_method_specific.multiboot1.memory_map_count = multiboot_info_ptr->mmap_length / sizeof(multiboot_memory_map_t);

    if (initrd_module_start != 0 && initrd_module_end != 0) {
        info.boot_method_specific.multiboot1.module_physical_ptr = PhysicalAddress { initrd_module_start };
        info.boot_method_specific.multiboot1.module_length = initrd_module_end - initrd_module_start;
    }

    if ((multiboot_info_ptr->flags & MULTIBOOT_INFO_FRAMEBUFFER_INFO) != 0) {
        info.boot_framebuffer.paddr = PhysicalAddress { multiboot_info_ptr->framebuffer_addr };
        info.boot_framebuffer.pitch = multiboot_info_ptr->framebuffer_pitch;
        info.boot_framebuffer.width = multiboot_info_ptr->framebuffer_width;
        info.boot_framebuffer.height = multiboot_info_ptr->framebuffer_height;
        info.boot_framebuffer.bpp = multiboot_info_ptr->framebuffer_bpp;

        if (multiboot_info_ptr->framebuffer_type == MULTIBOOT_FRAMEBUFFER_TYPE_RGB)
            info.boot_framebuffer.type = BootFramebufferType::BGRx8888;
        else
            info.boot_framebuffer.type = BootFramebufferType::None;
    }

    reload_cr3();

    int backwards = kernel_physical_base >= (FlatPtr)kernel_image;

    for (ssize_t i = 0; i < kernel_elf_header.e_phnum; i++) {
        auto& kernel_program_header = kernel_program_headers[backwards ? kernel_elf_header.e_phnum - 1 - i : i];
        if (kernel_program_header.p_type != PT_LOAD)
            continue;
        memmove_virt((u8*)kernel_load_base + kernel_program_header.p_vaddr,
            kernel_physical_base + kernel_program_header.p_vaddr,
            kernel_image + kernel_program_header.p_offset, kernel_program_header.p_filesz);
    }

    for (ssize_t i = kernel_elf_header.e_phnum - 1; i >= 0; i--) {
        auto& kernel_program_header = kernel_program_headers[i];
        if (kernel_program_header.p_type != PT_LOAD)
            continue;
        __builtin_memset((u8*)kernel_load_base + kernel_program_header.p_vaddr + kernel_program_header.p_filesz, 0, kernel_program_header.p_memsz - kernel_program_header.p_filesz);
    }

    info.boot_method_specific.multiboot1.start_of_prekernel_image = PhysicalAddress { bit_cast<PhysicalPtr>(+start_of_prekernel_image) };
    info.boot_method_specific.multiboot1.end_of_prekernel_image = PhysicalAddress { bit_cast<PhysicalPtr>(+end_of_prekernel_image) };
    info.physical_to_virtual_offset = kernel_load_base - kernel_physical_base;
    info.kernel_mapping_base = kernel_mapping_base;
    info.kernel_load_base = kernel_load_base;
#if ARCH(X86_64)
    info.boot_pml4t = PhysicalAddress { bit_cast<PhysicalPtr>(+boot_pml4t) };
#endif
    info.boot_pdpt = PhysicalAddress { bit_cast<PhysicalPtr>(+boot_pdpt) };
    info.arch_specific.boot_pd0 = PhysicalAddress { bit_cast<PhysicalPtr>(+boot_pd0) };
    info.boot_pd_kernel = PhysicalAddress { bit_cast<PhysicalPtr>(+boot_pd_kernel) };
    info.boot_pd_kernel_pt1023 = bit_cast<Memory::PageTableEntry*>(adjust_by_mapping_base(boot_pd_kernel_pt1023));

    char const* cmdline_ptr = bit_cast<char const*>(adjust_by_mapping_base(kernel_cmdline));
    info.cmdline = StringView { cmdline_ptr, __builtin_strlen(cmdline_ptr) };

    asm(
        "mov %0, %%rax\n"
        "add %%rax, %%rsp" ::"g"(kernel_mapping_base)
        : "ax");

    // unmap the 0-1MB region
    for (size_t i = 0; i < 256; i++)
        boot_pd0_pts[i] = 0;

    // unmap the end_of_prekernel_image - MAX_KERNEL_SIZE region
    for (FlatPtr vaddr = (FlatPtr)end_of_prekernel_image; vaddr < MAX_KERNEL_SIZE; vaddr += PAGE_SIZE)
        boot_pd0_pts[vaddr >> 12] = 0;

    reload_cr3();

    ELF::perform_relative_relocations(kernel_load_base);

    void (*entry)(BootInfo const&) = (void (*)(BootInfo const&))(kernel_load_base + kernel_elf_header.e_entry);
    entry(*adjust_by_mapping_base(&info));

    __builtin_unreachable();
}

// Define some Itanium C++ ABI methods to stop the linker from complaining.
// If we actually call these something has gone horribly wrong
void* __dso_handle __attribute__((visibility("hidden")));

}