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;
};
