:: commit 38af3f9843d24a6cee5d913ebc91c34f20f4e7fa

mintsuki <mintsuki@protonmail.com> — 2023-12-06 00:49

parents: 05db4b937c

multiboot2: Initial support for relocatable header tag

diff --git a/common/lib/elf.c b/common/lib/elf.c
index 3628dae5..eeeb0197 100644
--- a/common/lib/elf.c
+++ b/common/lib/elf.c
@@ -617,8 +617,7 @@ again:
 }
 
 bool elf32_load_elsewhere(uint8_t *elf, uint64_t *entry_point,
-                          struct elsewhere_range **ranges,
-                          uint64_t *ranges_count) {
+                          struct elsewhere_range **ranges) {
     struct elf32_hdr *hdr = (void *)elf;
 
     if (strncmp((char *)hdr->ident, "\177ELF", 4)) {
@@ -643,19 +642,32 @@ bool elf32_load_elsewhere(uint8_t *elf, uint64_t *entry_point,
         panic(true, "elf: phdr_size < sizeof(struct elf32_phdr)");
     }
 
-    *ranges_count = 0;
+    size_t image_size = 0;
+    uint64_t min_vaddr = (uint64_t)-1;
+    uint64_t max_vaddr = 0;
     for (uint16_t i = 0; i < hdr->ph_num; i++) {
         struct elf32_phdr *phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size);
 
         if (phdr->p_type != PT_LOAD)
             continue;
 
-        *ranges_count += 1;
+        if (phdr->p_vaddr < min_vaddr) {
+            min_vaddr = phdr->p_vaddr;
+        }
+
+        if (phdr->p_vaddr + phdr->p_memsz > max_vaddr) {
+            max_vaddr = phdr->p_vaddr + phdr->p_memsz;
+        }
     }
+    image_size = max_vaddr - min_vaddr;
+
+    void *elsewhere = ext_mem_alloc(image_size);
 
-    *ranges = ext_mem_alloc(sizeof(struct elsewhere_range) * *ranges_count);
+    *ranges = ext_mem_alloc(sizeof(struct elsewhere_range));
 
-    size_t cur_entry = 0;
+    (*ranges)->elsewhere = (uintptr_t)elsewhere;
+    (*ranges)->target = min_vaddr;
+    (*ranges)->length = image_size;
 
     for (uint16_t i = 0; i < hdr->ph_num; i++) {
         struct elf32_phdr *phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size);
@@ -668,9 +680,7 @@ bool elf32_load_elsewhere(uint8_t *elf, uint64_t *entry_point,
             panic(true, "elf: p_filesz > p_memsz");
         }
 
-        void *elsewhere = ext_mem_alloc(phdr->p_memsz);
-
-        memcpy(elsewhere, elf + phdr->p_offset, phdr->p_filesz);
+        memcpy(elsewhere + (phdr->p_vaddr - min_vaddr), elf + phdr->p_offset, phdr->p_filesz);
 
         if (!entry_adjusted
          && *entry_point >= phdr->p_vaddr
@@ -679,20 +689,13 @@ bool elf32_load_elsewhere(uint8_t *elf, uint64_t *entry_point,
             *entry_point += phdr->p_paddr;
             entry_adjusted = true;
         }
-
-        (*ranges)[cur_entry].elsewhere = (uintptr_t)elsewhere;
-        (*ranges)[cur_entry].target = phdr->p_paddr;
-        (*ranges)[cur_entry].length = phdr->p_memsz;
-
-        cur_entry++;
     }
 
     return true;
 }
 
 bool elf64_load_elsewhere(uint8_t *elf, uint64_t *entry_point,
-                          struct elsewhere_range **ranges,
-                          uint64_t *ranges_count) {
+                          struct elsewhere_range **ranges) {
     struct elf64_hdr *hdr = (void *)elf;
 
     if (strncmp((char *)hdr->ident, "\177ELF", 4)) {
@@ -717,19 +720,32 @@ bool elf64_load_elsewhere(uint8_t *elf, uint64_t *entry_point,
         panic(true, "elf: phdr_size < sizeof(struct elf64_phdr)");
     }
 
-    *ranges_count = 0;
+    size_t image_size = 0;
+    uint64_t min_vaddr = (uint64_t)-1;
+    uint64_t max_vaddr = 0;
     for (uint16_t i = 0; i < hdr->ph_num; i++) {
         struct elf64_phdr *phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size);
 
         if (phdr->p_type != PT_LOAD)
             continue;
 
-        *ranges_count += 1;
+        if (phdr->p_vaddr < min_vaddr) {
+            min_vaddr = phdr->p_vaddr;
+        }
+
+        if (phdr->p_vaddr + phdr->p_memsz > max_vaddr) {
+            max_vaddr = phdr->p_vaddr + phdr->p_memsz;
+        }
     }
+    image_size = max_vaddr - min_vaddr;
+
+    void *elsewhere = ext_mem_alloc(image_size);
 
-    *ranges = ext_mem_alloc(sizeof(struct elsewhere_range) * *ranges_count);
+    *ranges = ext_mem_alloc(sizeof(struct elsewhere_range));
 
-    size_t cur_entry = 0;
+    (*ranges)->elsewhere = (uintptr_t)elsewhere;
+    (*ranges)->target = min_vaddr;
+    (*ranges)->length = image_size;
 
     for (uint16_t i = 0; i < hdr->ph_num; i++) {
         struct elf64_phdr *phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size);
@@ -742,9 +758,7 @@ bool elf64_load_elsewhere(uint8_t *elf, uint64_t *entry_point,
             panic(true, "elf: p_filesz > p_memsz");
         }
 
-        void *elsewhere = ext_mem_alloc(phdr->p_memsz);
-
-        memcpy(elsewhere, elf + phdr->p_offset, phdr->p_filesz);
+        memcpy(elsewhere + (phdr->p_vaddr - min_vaddr), elf + phdr->p_offset, phdr->p_filesz);
 
         if (!entry_adjusted
          && *entry_point >= phdr->p_vaddr
@@ -753,12 +767,6 @@ bool elf64_load_elsewhere(uint8_t *elf, uint64_t *entry_point,
             *entry_point += phdr->p_paddr;
             entry_adjusted = true;
         }
-
-        (*ranges)[cur_entry].elsewhere = (uintptr_t)elsewhere;
-        (*ranges)[cur_entry].target = phdr->p_paddr;
-        (*ranges)[cur_entry].length = phdr->p_memsz;
-
-        cur_entry++;
     }
 
     return true;
diff --git a/common/lib/elf.h b/common/lib/elf.h
index 8f1e0f13..8cf1a3b6 100644
--- a/common/lib/elf.h
+++ b/common/lib/elf.h
@@ -33,11 +33,9 @@ bool elf64_load_section(uint8_t *elf, void *buffer, const char *name, size_t lim
 bool elf64_load(uint8_t *elf, uint64_t *entry_point, uint64_t *_slide, uint32_t alloc_type, bool kaslr, struct elf_range **ranges, uint64_t *ranges_count, uint64_t *physical_base, uint64_t *virtual_base, uint64_t *image_size, uint64_t *image_size_before_bss, bool *is_reloc);
 
 bool elf32_load_elsewhere(uint8_t *elf, uint64_t *entry_point,
-                          struct elsewhere_range **ranges,
-                          uint64_t *ranges_count);
+                          struct elsewhere_range **ranges);
 bool elf64_load_elsewhere(uint8_t *elf, uint64_t *entry_point,
-                          struct elsewhere_range **ranges,
-                          uint64_t *ranges_count);
+                          struct elsewhere_range **ranges);
 
 struct elf64_hdr {
     uint8_t  ident[16];
diff --git a/common/mm/pmm.c b/common/mm/pmm.c
index e64fa5b0..b7d2a476 100644
--- a/common/mm/pmm.c
+++ b/common/mm/pmm.c
@@ -1,7 +1,30 @@
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
 #include <mm/pmm.h>
 #include <lib/rand.h>
 #include <lib/print.h>
 
+static bool full_overlap_check(uint64_t base1, uint64_t top1,
+                               uint64_t base2, uint64_t top2) {
+    return ((base1 >= base2 && base1 <  top2)
+         && (top1  >  base2 && top1  <= top2));
+}
+
+bool check_usable_memory(uint64_t base, uint64_t top) {
+    for (size_t i = 0; i < memmap_entries; i++) {
+        if (memmap[i].type != MEMMAP_USABLE) {
+            continue;
+        }
+
+        if (full_overlap_check(base, top, memmap[i].base, memmap[i].base + memmap[i].length)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 void pmm_randomise_memory(void) {
     print("pmm: Randomising memory contents...");
 
diff --git a/common/mm/pmm.h b/common/mm/pmm.h
index 00aab406..997be105 100644
--- a/common/mm/pmm.h
+++ b/common/mm/pmm.h
@@ -72,4 +72,6 @@ void pmm_free(void *ptr, size_t length);
 void pmm_release_uefi_mem(void);
 #endif
 
+bool check_usable_memory(uint64_t base, uint64_t top);
+
 #endif
diff --git a/common/protos/multiboot1.c b/common/protos/multiboot1.c
index 77174313..ffd8a031 100644
--- a/common/protos/multiboot1.c
+++ b/common/protos/multiboot1.c
@@ -88,7 +88,7 @@ noreturn void multiboot1_load(char *config, char *cmdline) {
 
     uint64_t entry_point;
     struct elsewhere_range *ranges;
-    uint64_t ranges_count;
+    uint64_t ranges_count = 1;
 
     if (header.flags & (1 << 16)) {
         if (header.load_addr > header.header_addr)
@@ -118,7 +118,6 @@ noreturn void multiboot1_load(char *config, char *cmdline) {
 
         entry_point = header.entry_addr;
 
-        ranges_count = 1;
         ranges = ext_mem_alloc(sizeof(struct elsewhere_range));
 
         ranges->elsewhere = (uintptr_t)elsewhere;
@@ -129,14 +128,14 @@ noreturn void multiboot1_load(char *config, char *cmdline) {
 
         switch (bits) {
             case 32:
-                if (!elf32_load_elsewhere(kernel, &entry_point, &ranges, &ranges_count))
+                if (!elf32_load_elsewhere(kernel, &entry_point, &ranges))
                     panic(true, "multiboot1: ELF32 load failure");
 
                 section_hdr_info = elf32_section_hdr_info(kernel);
                 section_hdr_info_valid = true;
                 break;
             case 64: {
-                if (!elf64_load_elsewhere(kernel, &entry_point, &ranges, &ranges_count))
+                if (!elf64_load_elsewhere(kernel, &entry_point, &ranges))
                     panic(true, "multiboot1: ELF64 load failure");
 
                 section_hdr_info = elf64_section_hdr_info(kernel);
diff --git a/common/protos/multiboot2.c b/common/protos/multiboot2.c
index b1830601..a40eef35 100644
--- a/common/protos/multiboot2.c
+++ b/common/protos/multiboot2.c
@@ -105,6 +105,7 @@ noreturn void multiboot2_load(char *config, char* cmdline) {
     struct multiboot_header_tag_framebuffer *fbtag = NULL;
 
     bool has_reloc_header = false;
+    struct multiboot_header_tag_relocatable reloc_tag;
 
     bool is_new_acpi_required = false;
     bool is_old_acpi_required = false;
@@ -190,9 +191,12 @@ noreturn void multiboot2_load(char *config, char* cmdline) {
             case MULTIBOOT_HEADER_TAG_EFI_BS:
                 break;
 
-            case MULTIBOOT_HEADER_TAG_RELOCATABLE:
+            case MULTIBOOT_HEADER_TAG_RELOCATABLE: {
                 has_reloc_header = true;
+                struct multiboot_header_tag_relocatable *reloc_tag_ptr = (void *)tag;
+                reloc_tag = *reloc_tag_ptr;
                 break;
+            }
 
             default:
                 if (is_required)
@@ -204,7 +208,7 @@ noreturn void multiboot2_load(char *config, char* cmdline) {
     struct elf_section_hdr_info section_hdr_info = {0};
 
     struct elsewhere_range *ranges;
-    uint64_t ranges_count;
+    uint64_t ranges_count = 1;
 
     if (addresstag != NULL) {
         size_t header_offset = (size_t)header - (size_t)kernel;
@@ -249,7 +253,6 @@ noreturn void multiboot2_load(char *config, char* cmdline) {
             panic(true, "multiboot2: Using address tag but entry address tag missing");
         }
 
-        ranges_count = 1;
         ranges = ext_mem_alloc(sizeof(struct elsewhere_range));
 
         ranges->elsewhere = (uintptr_t)elsewhere;
@@ -261,14 +264,14 @@ noreturn void multiboot2_load(char *config, char* cmdline) {
 
         switch (bits) {
             case 32:
-                if (!elf32_load_elsewhere(kernel, &e, &ranges, &ranges_count))
+                if (!elf32_load_elsewhere(kernel, &e, &ranges))
                     panic(true, "multiboot2: ELF32 load failure");
 
                 section_hdr_info = elf32_section_hdr_info(kernel);
                 section_hdr_info_valid = true;
                 break;
             case 64: {
-                if (!elf64_load_elsewhere(kernel, &e, &ranges, &ranges_count))
+                if (!elf64_load_elsewhere(kernel, &e, &ranges))
                     panic(true, "multiboot2: ELF64 load failure");
 
                 section_hdr_info = elf64_section_hdr_info(kernel);
@@ -284,14 +287,62 @@ noreturn void multiboot2_load(char *config, char* cmdline) {
         }
     }
 
-    // Get the load base address (AKA the lowest target in the ranges)
-    uint64_t load_base_addr = (uint64_t)-1;
-    for (size_t i = 0; i < ranges_count; i++) {
-        if (load_base_addr > ranges[i].target) {
-            load_base_addr = ranges[i].target;
+    uint64_t reloc_slide = 0;
+
+    if (has_reloc_header) {
+        bool reloc_ascend;
+        uint64_t relocated_base;
+
+        switch (reloc_tag.preference) {
+            default:
+            case 0: case 1: // Prefer lowest to highest
+                reloc_ascend = true;
+                relocated_base = ALIGN_UP(reloc_tag.align, reloc_tag.min_addr);
+                if (relocated_base + ranges->length > reloc_tag.max_addr) {
+                    goto reloc_fail;
+                }
+            case 2: // Prefer highest to lowest
+                reloc_ascend = false;
+                relocated_base = ALIGN_DOWN(reloc_tag.align, reloc_tag.max_addr - ranges->length);
+                if (relocated_base < reloc_tag.min_addr) {
+                    goto reloc_fail;
+                }
+        }
+
+        for (;;) {
+            if (check_usable_memory(relocated_base, relocated_base + ranges->length)) {
+                break;
+            }
+
+            if (reloc_ascend) {
+                relocated_base += reloc_tag.align;
+                if (relocated_base + ranges->length > reloc_tag.max_addr) {
+                    goto reloc_fail;
+                }
+            } else {
+                relocated_base -= reloc_tag.align;
+                if (relocated_base < reloc_tag.min_addr) {
+                    goto reloc_fail;
+                }
+            }
         }
+
+        reloc_slide = reloc_ascend ?
+            relocated_base - ranges->target : ranges->target - relocated_base;
+
+        entry_point += reloc_slide;
+
+        ranges->target = relocated_base;
+    }
+
+    if (!check_usable_memory(ranges->target, ranges->target + ranges->length)) {
+reloc_fail:
+        panic(true, "multiboot2: Could not find viable load address for kernel");
     }
 
+    // Get the load base address (AKA the lowest target in the ranges)
+    uint64_t load_base_addr = ranges->target;
+
     size_t modules_size = 0;
     size_t n_modules;
 
tab: 248 wrap: offon