:: commit b92d48e44fe5c363dfa9d9970813b355c94919a6

mintsuki <mintsuki@protonmail.com> — 2022-06-27 08:48

parents: ee688073be

multiboot(2): Initial support for arbitrary kernel load addresses

diff --git a/common/lib/elf.c b/common/lib/elf.c
index 482a5cb7..90e00fb7 100644
--- a/common/lib/elf.c
+++ b/common/lib/elf.c
@@ -568,6 +568,11 @@ int elf64_load(uint8_t *elf, uint64_t *entry_point, uint64_t *top, uint64_t *_sl
         }
     }
 
+    if (use_paddr) {
+        simulation = true;
+        goto final;
+    }
+
     if (!elf64_is_relocatable(elf, &hdr)) {
         simulation = false;
         goto final;
@@ -678,7 +683,7 @@ final:
             memset(ptr, 0, to_zero);
         }
 
-        if (elf64_apply_relocations(elf, &hdr, (void *)(uintptr_t)load_addr, phdr.p_vaddr, phdr.p_memsz, slide)) {
+        if (!use_paddr && elf64_apply_relocations(elf, &hdr, (void *)(uintptr_t)load_addr, phdr.p_vaddr, phdr.p_memsz, slide)) {
             panic(true, "elf: Failed to apply relocations");
         }
 
diff --git a/common/mm/pmm.h b/common/mm/pmm.h
index e06cebcb..100d6179 100644
--- a/common/mm/pmm.h
+++ b/common/mm/pmm.h
@@ -43,7 +43,7 @@ void init_memmap(void);
 struct e820_entry_t *get_memmap(size_t *entries);
 struct e820_entry_t *get_raw_memmap(size_t *entry_count);
 void print_memmap(struct e820_entry_t *mm, size_t size);
-bool memmap_alloc_range(uint64_t base, uint64_t length, uint32_t type, bool free_only, bool panic, bool simulation, bool new_entry);
+bool memmap_alloc_range(uint64_t base, uint64_t length, uint32_t type, uint32_t overlay_type, bool panic, bool simulation, bool new_entry);
 void pmm_randomise_memory(void);
 
 void *ext_mem_alloc(size_t count);
diff --git a/common/mm/pmm.s2.c b/common/mm/pmm.s2.c
index 8ccb7e9b..5a93c276 100644
--- a/common/mm/pmm.s2.c
+++ b/common/mm/pmm.s2.c
@@ -808,7 +808,7 @@ static bool pmm_new_entry(uint64_t base, uint64_t length, uint32_t type) {
     return true;
 }
 
-bool memmap_alloc_range(uint64_t base, uint64_t length, uint32_t type, bool free_only, bool do_panic, bool simulation, bool new_entry) {
+bool memmap_alloc_range(uint64_t base, uint64_t length, uint32_t type, uint32_t overlay_type, bool do_panic, bool simulation, bool new_entry) {
     if (length == 0)
         return true;
 
@@ -819,7 +819,7 @@ bool memmap_alloc_range(uint64_t base, uint64_t length, uint32_t type, bool free
     uint64_t top = base + length;
 
     for (size_t i = 0; i < memmap_entries; i++) {
-        if (free_only && memmap[i].type != MEMMAP_USABLE)
+        if (overlay_type != 0 && memmap[i].type != overlay_type)
             continue;
 
         uint64_t entry_base = memmap[i].base;
diff --git a/common/protos/multiboot2.32.c b/common/protos/multiboot2.32.c
index c61207a2..e4c79ff6 100644
--- a/common/protos/multiboot2.32.c
+++ b/common/protos/multiboot2.32.c
@@ -7,7 +7,19 @@
 #  include <sys/idt.h>
 #endif
 
-noreturn void multiboot2_spinup_32(uint32_t entry_point, uint32_t multiboot2_info) {
+struct reloc_stub {
+    char jmp[4];
+    uint32_t magic;
+    uint32_t entry_point;
+    uint32_t mb_info_target;
+};
+
+noreturn void multiboot2_spinup_32(uint32_t entry_point,
+                                   uint32_t multiboot2_info, uint32_t mb_info_target,
+                                   uint32_t mb_info_size,
+                                   uint32_t elf_ranges, uint32_t elf_ranges_count,
+                                   uint32_t slide,
+                                   struct reloc_stub *reloc_stub) {
 #if bios == 1
     struct idtr idtr;
 
@@ -22,22 +34,16 @@ noreturn void multiboot2_spinup_32(uint32_t entry_point, uint32_t multiboot2_inf
     );
 #endif
 
-    asm volatile (
-        "cld\n\t"
-
-        "push %2\n\t"
+    reloc_stub->magic = 0x36d76289;
+    reloc_stub->entry_point = entry_point;
+    reloc_stub->mb_info_target = mb_info_target;
 
-        "xor %%ecx, %%ecx\n\t"
-        "xor %%edx, %%edx\n\t"
-        "xor %%esi, %%esi\n\t"
-        "xor %%edi, %%edi\n\t"
-        "xor %%ebp, %%ebp\n\t"
-
-        "ret\n\t"
+    asm volatile (
+        "jmp *%%ebx"
         :
-        : "a" (0x36d76289),
-          "b" (multiboot2_info),
-          "r" (entry_point)
+        : "b"(reloc_stub), "S"(multiboot2_info),
+          "c"(mb_info_size), "a"(elf_ranges), "d"(elf_ranges_count),
+          "D"(slide)
         : "memory"
     );
 
diff --git a/common/protos/multiboot2.c b/common/protos/multiboot2.c
index fd2adf6e..b8cebc72 100644
--- a/common/protos/multiboot2.c
+++ b/common/protos/multiboot2.c
@@ -20,6 +20,8 @@
 #include <lib/blib.h>
 #include <drivers/vga_textmode.h>
 
+extern symbol multiboot_reloc_stub, multiboot_reloc_stub_end;
+
 #define LIMINE_BRAND "Limine " LIMINE_VERSION
 
 /// Returns the size required to store the multiboot2 info.
@@ -57,6 +59,12 @@ static size_t get_multiboot2_info_size(
 
 static uint32_t kernel_top;
 
+static bool mb2_overlap_check(uint64_t base1, uint64_t top1,
+                              uint64_t base2, uint64_t top2) {
+    return ((base1 >= base2 && base1 <  top2)
+         || (top1  >  base2 && top1  <= top2));
+}
+
 static void *mb2_alloc(size_t size) {
     void *ret = (void *)(uintptr_t)ALIGN_UP(kernel_top, 4096);
 
@@ -189,6 +197,9 @@ bool multiboot2_load(char *config, char* cmdline) {
         }
     }
 
+    struct elf_range *elf_ranges;
+    uint64_t elf_ranges_count, slide;
+
     if (addresstag != NULL) {
         if (addresstag->load_addr > addresstag->header_addr)
             panic(true, "multiboot2: Illegal load address");
@@ -226,12 +237,12 @@ bool multiboot2_load(char *config, char* cmdline) {
 
         switch (bits) {
             case 32:
-                if (elf32_load(kernel, (uint32_t *)&e, (uint32_t *)&t, MEMMAP_KERNEL_AND_MODULES))
+                if (elf32_load(kernel, (uint32_t *)&e, (uint32_t *)&t, MEMMAP_BOOTLOADER_RECLAIMABLE))
                     panic(true, "multiboot2: ELF32 load failure");
 
                 break;
             case 64: {
-                if (elf64_load(kernel, &e, &t, NULL, MEMMAP_KERNEL_AND_MODULES, false, true, NULL, NULL, false, NULL, NULL, NULL, NULL))
+                if (elf64_load(kernel, &e, &t, &slide, MEMMAP_BOOTLOADER_RECLAIMABLE, false, true, &elf_ranges, &elf_ranges_count, false, NULL, NULL, NULL, NULL))
                     panic(true, "multiboot2: ELF64 load failure");
 
                 break;
@@ -240,10 +251,17 @@ bool multiboot2_load(char *config, char* cmdline) {
                 panic(true, "multiboot2: Invalid ELF file bitness");
         }
 
+        e -= slide;
         if (entry_point == 0xffffffff) {
             entry_point = e;
         }
-        kernel_top = t;
+
+        t -= slide;
+        if (t < 0x100000) {
+            kernel_top = 0x100000;
+        } else {
+            kernel_top = t;
+        }
     }
 
     struct elf_section_hdr_info *section_hdr_info = NULL;
@@ -292,7 +310,42 @@ bool multiboot2_load(char *config, char* cmdline) {
     );
 
     size_t info_idx = 0;
-    uint8_t *mb2_info = conv_mem_alloc(mb2_info_size);
+
+    // GRUB allocates boot info at 0x10000, *except* if the kernel happens
+    // to overlap this region, then it gets moved to right after the
+    // kernel, or whichever PHDR happens to sit at 0x10000.
+    // Allocate it wherever, then move it to where GRUB puts it
+    // afterwards.
+    uint8_t *mb2_info = ext_mem_alloc(mb2_info_size);
+    uint64_t mb2_info_final_loc = 0x10000;
+retry_mb2_info_reloc:
+    for (size_t i = 0; i < elf_ranges_count; i++) {
+        uint64_t mb2_info_top = mb2_info_final_loc + mb2_info_size;
+
+        uint64_t base = elf_ranges[i].base - slide;
+        uint64_t length = elf_ranges[i].length - slide;
+        uint64_t top = base + length;
+
+        // Do they overlap?
+        if (mb2_overlap_check(base, top, mb2_info_final_loc, mb2_info_top)) {
+            mb2_info_final_loc = top;
+            goto retry_mb2_info_reloc;
+        }
+
+        // Make sure it is memory that actually exists.
+        if (!memmap_alloc_range(mb2_info_final_loc, mb2_info_size, MEMMAP_BOOTLOADER_RECLAIMABLE,
+                                MEMMAP_USABLE, false, true, false)) {
+            if (!memmap_alloc_range(mb2_info_final_loc, mb2_info_size, MEMMAP_BOOTLOADER_RECLAIMABLE,
+                                    MEMMAP_BOOTLOADER_RECLAIMABLE, false, true, false)) {
+                mb2_info_final_loc += 0x1000;
+                goto retry_mb2_info_reloc;
+            }
+        }
+    }
+
+    if (mb2_info_final_loc + mb2_info_size > kernel_top) {
+        kernel_top = mb2_info_final_loc + mb2_info_size;
+    }
 
     struct multiboot2_start_tag *mbi_start = (struct multiboot2_start_tag *)mb2_info;
     info_idx += sizeof(struct multiboot2_start_tag);
@@ -592,6 +645,11 @@ bool multiboot2_load(char *config, char* cmdline) {
     }
 #endif
 
+    // Load relocation stub where it won't get overwritten
+    size_t reloc_stub_size = (size_t)multiboot_reloc_stub_end - (size_t)multiboot_reloc_stub;
+    void *reloc_stub = mb2_alloc(reloc_stub_size);
+    memcpy(reloc_stub, multiboot_reloc_stub, reloc_stub_size);
+
 #if uefi == 1
     efi_exit_boot_services();
 #endif
@@ -685,6 +743,11 @@ bool multiboot2_load(char *config, char* cmdline) {
 
     irq_flush_type = IRQ_PIC_ONLY_FLUSH;
 
-    common_spinup(multiboot2_spinup_32, 2,
-                    entry_point, (uint32_t)(uintptr_t)mbi_start);
+    common_spinup(multiboot2_spinup_32, 8,
+                  entry_point,
+                  (uint32_t)(uintptr_t)mb2_info, (uint32_t)mb2_info_final_loc,
+                  (uint32_t)mb2_info_size,
+                  (uint32_t)(uintptr_t)elf_ranges, (uint32_t)elf_ranges_count,
+                  (uint32_t)slide,
+                  (uint32_t)(uintptr_t)reloc_stub);
 }
diff --git a/common/protos/multiboot_reloc.asm_ia32 b/common/protos/multiboot_reloc.asm_ia32
new file mode 100644
index 00000000..8ae1d423
--- /dev/null
+++ b/common/protos/multiboot_reloc.asm_ia32
@@ -0,0 +1,56 @@
+section .data
+
+global multiboot_reloc_stub
+multiboot_reloc_stub:
+    jmp .code
+
+    times 4-($-multiboot_reloc_stub) db 0
+
+  .magic_value:    dd 0
+  .entry_point:    dd 0
+  .mb_info_target: dd 0
+
+    ; EBX = self
+    ; ESI = multiboot info (original)
+    ; ECX = multiboot info (size)
+
+    ; EAX = elf ranges
+    ; EDX = elf ranges count
+    ; EDI = slide
+
+  .code:
+    mov ebp, edi
+
+    mov edi, [ebx + (.mb_info_target - multiboot_reloc_stub)]
+
+    ; Copy multiboot info; frees ESI, EDI, and ECX
+    rep movsb
+
+ .elf_ranges_loop:
+    mov esi, [eax]   ; ESI = elf_range.base
+    mov edi, esi     ; EDI = elf_range.base - slide
+    sub edi, ebp
+    mov ecx, [eax+8] ; ECX = elf_range.length
+    rep movsb        ; Copy range to target location
+
+    add eax, 24      ; Move to the next elf_range
+
+    dec edx          ; Loop until we're done
+    jnz .elf_ranges_loop
+
+    ; We're done relocating!
+
+    push dword [ebx + (.entry_point - multiboot_reloc_stub)]
+
+    mov eax, [ebx + (.magic_value - multiboot_reloc_stub)]
+    mov ebx, [ebx + (.mb_info_target - multiboot_reloc_stub)]
+    xor ecx, ecx
+    xor edx, edx
+    xor esi, esi
+    xor edi, edi
+    xor ebp, ebp
+
+    ret
+
+global multiboot_reloc_stub_end
+multiboot_reloc_stub_end:
tab: 248 wrap: offon