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;
