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 #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 + reloc_stub->magic = 0x36d76289; + reloc_stub->entry_point = entry_point; + reloc_stub->mb_info_target = mb_info_target; + asm volatile ( - "cld\n\t" - - "push %2\n\t" - - "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" + "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 #include +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: