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:
