:: commit 4240256074723f4770ed825f2ad6c521b7d740d7

mintsuki <mintsuki@protonmail.com> — 2022-03-30 12:04

parents: abc3b309a4

limine: Initial support for non-ELF executables

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;
 };
tab: 248 wrap: offon