diff --git a/PROTOCOL.md b/PROTOCOL.md index 2de5d0c3..e2b3a199 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -19,6 +19,14 @@ languages. All pointers are 64-bit wide. All pointers point to the object with the higher half direct map offset already added to them, unless otherwise noted. +### Executable formats + +The Limine protocol does not enforce any specific executable format, but +kernels using formats not supported by the bootloader, or using flat binaries, +*must* provide a Executable Layout Feature (see below). + +Compliant bootloader must support at least the ELF 64-bit executable format. + ## Features The protocol is centered around the concept of request/response - collectively @@ -71,7 +79,7 @@ revisions do. This is all there is to features. For a list of official Limine features, read the "Feature List" section below. -## Executable memory layout +## Entry memory layout The protocol mandates kernels to load themselves at or above `0xffffffff80000000`. Lower half kernels are *not supported*. @@ -209,6 +217,60 @@ struct limine_stack_size_response { }; ``` +### Executable Layout Feature + +ID: +```c +#define LIMINE_EXECUTABLE_LAYOUT_REQUEST { LIMINE_COMMON_MAGIC, 0xbbd4597377e1fdbb, 0x17540007cfa435ad } +``` + +Request: +```c +typedef void (*limine_entry_point)(void); + +struct limine_executable_layout_request { + uint64_t id[4]; + uint64_t revision; + struct limine_executable_layout_response *response; + limine_entry_point entry_point; + uint64_t alignment; + uint64_t text_offset; + uint64_t text_address; + uint64_t text_size; + uint64_t data_offset; + uint64_t data_address; + uint64_t data_size; + uint64_t rodata_offset; + uint64_t rodata_address; + uint64_t rodata_size; + uint64_t bss_address; + uint64_t bss_size; +}; +``` + +* `entry_point` - The virtual address of the entry point of the kernel. It is +equivalent to an entry point specified in an executable format, and thus it can +be overridden by the Entry Point Feature. +* `alignment` - The requested alignment for the physical base address of the +kernel. It *must* be a power of 2. An alignment of 0 means 4096. +* `{text,data,rodata}_offset` - The offset within the file where the segment +begins. +* `{text,data,rodata,bss}_address` - The virtual address to which to load the +segment to. +* `{text,data,rodata,bss}_size` - The size of the segment both in the file and +in memory, except for bss, where it is only the in-memory size. + +Response: +```c +struct limine_executable_layout_response { + uint64_t revision; +}; +``` + +Notes: This request is parsed if the bootloader does not support the executable +format of the kernel. It is otherwise ignored. If it is parsed and used for +loading the kernel, then the response will be set to a vaild pointer. + ### HHDM (Higher Half Direct Map) Feature ID: diff --git a/common/protos/limine.c b/common/protos/limine.c index 174ac1a0..bb754f43 100644 --- a/common/protos/limine.c +++ b/common/protos/limine.c @@ -131,12 +131,6 @@ bool limine_load(char *config, char *cmdline) { int bits = elf_bits(kernel); - if (bits == -1 || bits == 32) { - printv("limine: Kernel in unrecognised format"); - return false; - } - - // ELF loading uint64_t entry_point = 0; struct elf_range *ranges; uint64_t ranges_count; @@ -144,12 +138,106 @@ bool limine_load(char *config, char *cmdline) { uint64_t image_size; bool is_reloc; - if (elf64_load(kernel, &entry_point, NULL, &slide, - MEMMAP_KERNEL_AND_MODULES, kaslr, false, - &ranges, &ranges_count, - true, &physical_base, &virtual_base, &image_size, - &is_reloc)) { - return false; + bool flat = false; + + if (bits == -1 || bits == 32) { + struct limine_executable_layout_request *exec_layout = NULL; + uint64_t exec_layout_id[4] = LIMINE_EXECUTABLE_LAYOUT_REQUEST; + + for (size_t i = 0; i < ALIGN_DOWN(kernel_file->size, 8); i += 8) { + uint64_t *p = (void *)(uintptr_t)kernel + i; + + if (p[0] != exec_layout_id[0]) { + continue; + } + if (p[1] != exec_layout_id[1]) { + continue; + } + if (p[2] != exec_layout_id[2]) { + continue; + } + if (p[3] != exec_layout_id[3]) { + continue; + } + + exec_layout = (void *)p; + break; + } + + if (exec_layout == NULL) { + printv("limine: Kernel in unrecognised format\n"); + return false; + } + + entry_point = exec_layout->entry_point; + + if (exec_layout->text_address % 4096 + || exec_layout->data_address % 4096 + || exec_layout->rodata_address % 4096 + || exec_layout->bss_address % 4096) { + panic(true, "limine: Address of an executable segment is not page aligned"); + } + + ranges_count = 4; + ranges = ext_mem_alloc(sizeof(struct elf_range) * ranges_count); + + ranges[0].base = exec_layout->text_address; + ranges[0].length = exec_layout->text_size; + ranges[0].permissions = ELF_PF_X | ELF_PF_R; + + ranges[1].base = exec_layout->data_address; + ranges[1].length = exec_layout->data_size; + ranges[1].permissions = ELF_PF_R | ELF_PF_W; + + ranges[2].base = exec_layout->rodata_address; + ranges[2].length = exec_layout->rodata_size; + ranges[2].permissions = ELF_PF_R; + + ranges[3].base = exec_layout->bss_address; + ranges[3].length = exec_layout->bss_size; + ranges[3].permissions = ELF_PF_R | ELF_PF_W; + + uint64_t min_addr = (uint64_t)-1; + uint64_t max_addr = 0; + for (size_t i = 0; i < ranges_count; i++) { + if (ranges[i].base < min_addr) { + min_addr = ranges[i].base; + } + if (ranges[i].base + ranges[i].length > max_addr) { + max_addr = ranges[i].base + ranges[i].length; + } + } + + image_size = max_addr - min_addr; + + is_reloc = false; + slide = 0; + + virtual_base = min_addr; + + void *image = ext_mem_alloc_type_aligned(image_size, + MEMMAP_KERNEL_AND_MODULES, exec_layout->alignment ?: 4096); + + physical_base = (uintptr_t)image; + + memcpy(image + (exec_layout->text_address - min_addr), + kernel + exec_layout->text_offset, exec_layout->text_size); + memcpy(image + (exec_layout->data_address - min_addr), + kernel + exec_layout->data_offset, exec_layout->data_size); + memcpy(image + (exec_layout->rodata_address - min_addr), + kernel + exec_layout->rodata_offset, exec_layout->rodata_size); + memset(image + (exec_layout->bss_address - min_addr), 0, exec_layout->bss_size); + + flat = true; + } else { + // ELF loading + if (elf64_load(kernel, &entry_point, NULL, &slide, + MEMMAP_KERNEL_AND_MODULES, kaslr, false, + &ranges, &ranges_count, + true, &physical_base, &virtual_base, &image_size, + &is_reloc)) { + return false; + } } kaslr = is_reloc; @@ -234,7 +322,7 @@ FEAT_START entry_point = entrypoint_request->entry; - print("limine: Entry point at %X\n", entry_point); + printv("limine: Entry point at %X\n", entry_point); struct limine_entry_point_response *entrypoint_response = ext_mem_alloc(sizeof(struct limine_entry_point_response)); @@ -242,6 +330,23 @@ FEAT_START entrypoint_request->response = reported_addr(entrypoint_response); FEAT_END + // Executable layout feature +FEAT_START + if (!flat) { + break; + } + + struct limine_executable_layout_request *exec_layout_request = get_request(LIMINE_EXECUTABLE_LAYOUT_REQUEST); + if (exec_layout_request == NULL) { + panic(true, "limine: How did this even happen?"); + } + + struct limine_executable_layout_response *exec_layout_response = + ext_mem_alloc(sizeof(struct limine_executable_layout_response)); + + exec_layout_request->response = reported_addr(exec_layout_response); +FEAT_END + // Bootloader info feature FEAT_START struct limine_bootloader_info_request *bootloader_info_request = get_request(LIMINE_BOOTLOADER_INFO_REQUEST); diff --git a/limine.h b/limine.h index bb7a4091..311bb61c 100644 --- a/limine.h +++ b/limine.h @@ -40,6 +40,8 @@ struct limine_file { struct limine_uuid part_uuid; }; +typedef void (*limine_entry_point)(void); + /* Boot info */ #define LIMINE_BOOTLOADER_INFO_REQUEST { LIMINE_COMMON_MAGIC, 0xf55038d8e2a1202f, 0x279426fcf5f59740 } @@ -71,6 +73,33 @@ struct limine_stack_size_request { uint64_t stack_size; }; +/* Executable layout */ + +#define LIMINE_EXECUTABLE_LAYOUT_REQUEST { LIMINE_COMMON_MAGIC, 0xbbd4597377e1fdbb, 0x17540007cfa435ad } + +struct limine_executable_layout_response { + uint64_t revision; +}; + +struct limine_executable_layout_request { + uint64_t id[4]; + uint64_t revision; + LIMINE_PTR(struct limine_executable_layout_response *) response; + LIMINE_PTR(limine_entry_point) entry_point; + uint64_t alignment; + uint64_t text_offset; + uint64_t text_address; + uint64_t text_size; + uint64_t data_offset; + uint64_t data_address; + uint64_t data_size; + uint64_t rodata_offset; + uint64_t rodata_address; + uint64_t rodata_size; + uint64_t bss_address; + uint64_t bss_size; +}; + /* HHDM */ #define LIMINE_HHDM_REQUEST { LIMINE_COMMON_MAGIC, 0x48dcf1cb8ad2b852, 0x63984e959a98244b } @@ -239,8 +268,6 @@ struct limine_memmap_request { #define LIMINE_ENTRY_POINT_REQUEST { LIMINE_COMMON_MAGIC, 0x13d86c035a1cd3e1, 0x2b0caa89d8f3026a } -typedef void (*limine_entry_point)(void); - struct limine_entry_point_response { uint64_t revision; };