limine: Initial support for non-ELF executables

This commit is contained in:
mintsuki 2022-03-30 14:04:04 +02:00
parent abc3b309a4
commit 4240256074
3 changed files with 210 additions and 16 deletions

View File

@ -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:

View File

@ -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);

View File

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