Files
LithosAnanake/src/starkernel/boot/elf_loader.c
T

417 lines
14 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* elf_loader.c - ELF64 kernel loader for StarKernel
*
* Loads the StarKernel ELF binary from ESP, parses segments,
* applies relocations, and jumps to entry point.
*/
#include "uefi.h"
#include "elf64.h"
#include "arch.h"
/**
* @brief Fill a memory region with a byte value (freestanding memset).
*
* Writes @p n copies of the low byte of @p c to the memory starting at
* @p s. Used by @c elf_load_segments() to zero-fill BSS regions
* (@c p_memsz > @c p_filesz) without a libc dependency. The byte loop
* is correct for all sizes; a future optimisation could use SIMD or
* word-width stores for large BSS regions.
*
* @param s Pointer to the start of the destination region.
* @param c Value to fill (only the low 8 bits are used).
* @param n Number of bytes to write.
* @return @p s (matches standard @c memset signature).
*/
static void *elf_memset(void *s, int c, uint64_t n)
{
unsigned char *p = (unsigned char *)s;
while (n--) *p++ = (unsigned char)c;
return s;
}
/**
* @brief Copy a non-overlapping memory region (freestanding memcpy).
*
* Copies @p n bytes from @p src to @p dest using a byte-at-a-time loop.
* Behaviour is undefined if the source and destination ranges overlap.
* Used by @c elf_load_segments() to copy each PT_LOAD segment's
* @c p_filesz bytes from the ELF image in memory to its load address.
*
* @param dest Destination buffer; must not overlap with @p src.
* @param src Source buffer.
* @param n Number of bytes to copy.
* @return @p dest (matches standard @c memcpy signature).
*/
static void *elf_memcpy(void *dest, const void *src, uint64_t n)
{
unsigned char *d = (unsigned char *)dest;
const unsigned char *s = (const unsigned char *)src;
while (n--) *d++ = *s++;
return dest;
}
/**
* @brief Validate an ELF64 header for the current target architecture.
*
* Checks the following fields in the ELF identification block and header:
* - Magic bytes @c e_ident[0..3]: must be @c 0x7F 'E' 'L' 'F'.
* - Class @c e_ident[EI_CLASS]: must be @c ELFCLASS64 (64-bit).
* - Data encoding @c e_ident[EI_DATA]: must be @c ELFDATA2LSB
* (little-endian, the only byte order used by all three supported ISAs).
* - Version @c e_ident[EI_VERSION]: must be @c EV_CURRENT (1).
* - Type @c e_type: must be @c ET_EXEC (statically linked) or
* @c ET_DYN (position-independent executable).
* - Machine @c e_machine: must match the build target —
* @c EM_X86_64 for @c ARCH_AMD64,
* @c EM_AARCH64 for @c ARCH_AARCH64,
* @c EM_RISCV for @c ARCH_RISCV64.
*
* @param ehdr Pointer to the ELF64 header at the start of the image.
* @return 1 if the header is valid for this architecture, 0 otherwise.
*/
static int elf_validate_header(const Elf64_Ehdr *ehdr)
{
/* Check magic number */
if (ehdr->e_ident[EI_MAG0] != ELFMAG0 ||
ehdr->e_ident[EI_MAG1] != ELFMAG1 ||
ehdr->e_ident[EI_MAG2] != ELFMAG2 ||
ehdr->e_ident[EI_MAG3] != ELFMAG3)
{
return 0; /* Not an ELF file */
}
/* Check class (64-bit) */
if (ehdr->e_ident[EI_CLASS] != ELFCLASS64) {
return 0;
}
/* Check data encoding (little-endian) */
if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB) {
return 0;
}
/* Check version */
if (ehdr->e_ident[EI_VERSION] != EV_CURRENT) {
return 0;
}
/* Check type (ET_EXEC or ET_DYN) */
if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN) {
return 0;
}
/* Check machine type matches architecture */
#if defined(ARCH_AMD64)
if (ehdr->e_machine != EM_X86_64) {
return 0;
}
#elif defined(ARCH_AARCH64)
if (ehdr->e_machine != EM_AARCH64) {
return 0;
}
#elif defined(ARCH_RISCV64)
if (ehdr->e_machine != EM_RISCV) {
return 0;
}
#else
#error "Unknown architecture"
#endif
return 1;
}
/**
* @brief Map all PT_LOAD segments from an ELF64 image into memory.
*
* Iterates the program header table (@c e_phnum entries starting at
* offset @c e_phoff) and processes every entry with @c p_type == @c PT_LOAD:
*
* 1. Computes the destination address as @p load_base + @c p_vaddr.
* 2. Copies @c p_filesz bytes from @p elf_data + @c p_offset using
* @c elf_memcpy().
* 3. If @c p_memsz > @c p_filesz, zero-fills the remaining
* (@c p_memsz - @c p_filesz) bytes with @c elf_memset() to
* initialise the BSS region within the segment.
*
* The function does not check for address-range overlaps or that
* destination memory is physically available; the UEFI loader is
* responsible for allocating pages before calling @c elf_load_kernel().
*
* @param elf_data Pointer to the start of the ELF64 file in memory.
* @param load_base Base address at which to load the ELF image
* (0 for @c ET_EXEC, 0x400000 for @c ET_DYN).
* @return 1 always (errors are silently ignored at this milestone; a
* future revision should return 0 on a failed memory access).
*/
static int elf_load_segments(const uint8_t *elf_data, Elf64_Addr load_base)
{
const Elf64_Ehdr *ehdr = (const Elf64_Ehdr *)elf_data;
const Elf64_Phdr *phdr = (const Elf64_Phdr *)(elf_data + ehdr->e_phoff);
/* Iterate through program headers */
for (uint16_t i = 0; i < ehdr->e_phnum; i++) {
if (phdr[i].p_type == PT_LOAD) {
/* Calculate load address */
uint8_t *dest = (uint8_t *)(load_base + phdr[i].p_vaddr);
const uint8_t *src = elf_data + phdr[i].p_offset;
/* Copy file contents to memory */
elf_memcpy(dest, src, phdr[i].p_filesz);
/* Zero out BSS (p_memsz > p_filesz) */
if (phdr[i].p_memsz > phdr[i].p_filesz) {
elf_memset(dest + phdr[i].p_filesz, 0,
phdr[i].p_memsz - phdr[i].p_filesz);
}
}
}
return 1;
}
/**
* @brief Apply all RELA relocation sections from an ELF64 image.
*
* Scans the section header table for sections of type @c SHT_RELA. For
* each such section, resolves the associated symbol table (via
* @c sh_link) and applies every relocation entry to the loaded image.
*
* Supported relocation types are ISA-specific and compile-time selected:
*
* - **amd64 (@c ARCH_AMD64)**:
* - @c R_X86_64_NONE — no-op.
* - @c R_X86_64_RELATIVE — absolute address = @c load_base + @c r_addend.
* - @c R_X86_64_64 — @c load_base + symbol value + @c r_addend (64-bit).
* - @c R_X86_64_32 / @c R_X86_64_32S — same, truncated to 32 bits.
*
* - **aarch64 (@c ARCH_AARCH64)**:
* - @c R_AARCH64_NONE — no-op.
* - @c R_AARCH64_RELATIVE — @c load_base + @c r_addend.
* - @c R_AARCH64_ABS64 — @c load_base + symbol + @c r_addend.
*
* - **riscv64 (@c ARCH_RISCV64)**:
* - @c R_RISCV_NONE — no-op.
* - @c R_RISCV_RELATIVE — @c load_base + @c r_addend.
* - @c R_RISCV_64 — @c load_base + symbol + @c r_addend.
*
* Returns 0 (failure) if a symbol index in a relocation entry is out
* of bounds, if the symbol table entry size is zero, or if an unknown
* relocation type is encountered. Returns 1 on success.
*
* @param elf_data Pointer to the ELF64 file image.
* @param load_base Load base address applied during @c elf_load_segments().
* @return 1 on success, 0 on any relocation error.
*/
static int elf_apply_relocations(const uint8_t *elf_data, Elf64_Addr load_base)
{
const Elf64_Ehdr *ehdr = (const Elf64_Ehdr *)elf_data;
const Elf64_Shdr *shdr = (const Elf64_Shdr *)(elf_data + ehdr->e_shoff);
for (uint16_t i = 0; i < ehdr->e_shnum; i++) {
const Elf64_Shdr *rela_sh = &shdr[i];
if (rela_sh->sh_type != SHT_RELA) {
continue;
}
if (rela_sh->sh_link >= ehdr->e_shnum) {
return 0;
}
const Elf64_Shdr *symtab_sh = &shdr[rela_sh->sh_link];
if (symtab_sh->sh_entsize == 0) {
return 0;
}
const Elf64_Rela *rela = (const Elf64_Rela *)(elf_data + rela_sh->sh_offset);
uint64_t num_rela = rela_sh->sh_size / sizeof(Elf64_Rela);
const Elf64_Sym *symtab = (const Elf64_Sym *)(elf_data + symtab_sh->sh_offset);
uint64_t sym_count = symtab_sh->sh_size / symtab_sh->sh_entsize;
for (uint64_t j = 0; j < num_rela; j++) {
uint32_t type = ELF64_R_TYPE(rela[j].r_info);
uint32_t sym_index = ELF64_R_SYM(rela[j].r_info);
uint64_t reloc_addr = load_base + rela[j].r_offset;
uint64_t sym_value = 0;
if (sym_index < sym_count) {
sym_value = symtab[sym_index].st_value;
} else if (sym_index != 0) {
return 0;
}
#if defined(ARCH_AMD64)
switch (type) {
case R_X86_64_NONE:
break;
case R_X86_64_RELATIVE: {
uint64_t *target = (uint64_t *)reloc_addr;
*target = load_base + rela[j].r_addend;
break;
}
case R_X86_64_64: {
uint64_t *target = (uint64_t *)reloc_addr;
*target = load_base + sym_value + rela[j].r_addend;
break;
}
case R_X86_64_32:
case R_X86_64_32S: {
uint32_t *target32 = (uint32_t *)reloc_addr;
uint64_t value = load_base + sym_value + rela[j].r_addend;
*target32 = (uint32_t)value;
break;
}
default:
return 0;
}
#elif defined(ARCH_AARCH64)
switch (type) {
case R_AARCH64_NONE:
break;
case R_AARCH64_RELATIVE: {
uint64_t *target = (uint64_t *)reloc_addr;
*target = load_base + rela[j].r_addend;
break;
}
case R_AARCH64_ABS64: {
uint64_t *target = (uint64_t *)reloc_addr;
*target = load_base + sym_value + rela[j].r_addend;
break;
}
default:
return 0;
}
#elif defined(ARCH_RISCV64)
switch (type) {
case R_RISCV_NONE:
break;
case R_RISCV_RELATIVE: {
uint64_t *target = (uint64_t *)reloc_addr;
*target = load_base + rela[j].r_addend;
break;
}
case R_RISCV_64: {
uint64_t *target = (uint64_t *)reloc_addr;
*target = load_base + sym_value + rela[j].r_addend;
break;
}
default:
return 0;
}
#endif
}
}
return 1;
}
/**
* @brief Load, relocate, and return the entry point of a StarKernel ELF64 binary.
*
* Top-level ELF loader called by @c efi_main() (in the split-build path)
* after @c ExitBootServices(). Performs three operations in sequence:
*
* 1. **Header validation** via @c elf_validate_header() — checks magic,
* class, data encoding, version, type, and machine-specific architecture.
* Returns 0 immediately on failure.
*
* 2. **Segment loading** via @c elf_load_segments() — maps all @c PT_LOAD
* segments to their target addresses, initialises BSS to zero.
* The @p load_base is:
* - 0 for @c ET_EXEC (absolute-address kernel; typical for StarKernel).
* - 0x400000 (4 MB) for @c ET_DYN (position-independent kernel).
*
* 3. **Relocation processing** via @c elf_apply_relocations() — applies
* @c SHT_RELA sections to patch in the correct runtime addresses.
* Returns 0 on any relocation error.
*
* On success, stores @c load_base + @c e_entry in @c *entry_out — the
* virtual address of the kernel's C entry function @c kernel_main() — and
* returns 1. The caller performs an indirect call to this address.
*
* @c elf_size is currently unused (reserved for future bounds-checking of
* program-header and section-header offsets against the file boundary).
*
* @param elf_data Pointer to the ELF64 file image in UEFI loader memory;
* must remain valid until after the call returns.
* @param elf_size Size of the ELF image in bytes (currently unchecked).
* @param entry_out Output: receives the kernel entry point address on success.
* @return 1 on success, 0 if validation, segment loading, or relocation fails.
*/
int elf_load_kernel(const uint8_t *elf_data, uint64_t elf_size,
Elf64_Addr *entry_out)
{
(void)elf_size; /* Reserved for future bounds checking */
const Elf64_Ehdr *ehdr = (const Elf64_Ehdr *)elf_data;
/* Validate ELF header */
if (!elf_validate_header(ehdr)) {
return 0;
}
/* Determine load base address */
Elf64_Addr load_base = 0;
if (ehdr->e_type == ET_DYN) {
/* Position-independent: use a fixed base for now */
load_base = 0x400000; /* 4MB */
} else if (ehdr->e_type == ET_EXEC) {
/* Absolute addresses: load at 0 (segments have absolute vaddr) */
load_base = 0;
}
/* Load segments */
if (!elf_load_segments(elf_data, load_base)) {
return 0;
}
/* Apply relocations */
if (!elf_apply_relocations(elf_data, load_base)) {
return 0;
}
/* Calculate entry point */
*entry_out = load_base + ehdr->e_entry;
return 1;
}