diff options
Diffstat (limited to 'boot/elf.c')
-rw-r--r-- | boot/elf.c | 98 |
1 files changed, 98 insertions, 0 deletions
diff --git a/boot/elf.c b/boot/elf.c new file mode 100644 index 0000000..b8328b0 --- /dev/null +++ b/boot/elf.c @@ -0,0 +1,98 @@ +#include <elf.h> +#include <stddef.h> + +#include "bootloader.h" + +static int cmp(const char *a, const char *b, size_t len) +{ + while (len--) if (*a++ != *b++) return 1; + return 0; +} + +static void load_segment(char *dst, const char *src, size_t outlen, size_t inlen) +{ + outlen -= inlen; + while (inlen--) *dst++ = *src++; + while (outlen--) *dst++ = 0; +} + +static const char e_ident[] = { + 0x7f, 'E', 'L', 'F', /* magic number */ + ELFCLASS32, /* 32-bit */ + ELFDATA2MSB, /* big endian */ + 1, /* ELF version 1 */ +}; + +_Noreturn void bootloader(struct systemid *boot) +{ + if (!boot->load_addr || boot->load_addr & 3) fail(); + if (boot->stack_master & 3) fail(); + + char *base = (void*) boot->load_addr; + long *stack = (long*)(boot->stack_master ? boot->stack_master : 0x06002000); + + Elf32_Ehdr *ehdr = (void*)base; + if (cmp(ehdr->e_ident, e_ident, sizeof e_ident) + || ehdr->e_type != ET_EXEC + || ehdr->e_machine != EM_SH + || ehdr->e_version != EV_CURRENT + || !ehdr->e_entry) + fail(); + + void *entry = (void*)ehdr->e_entry; + char *phdrs = base + ehdr->e_phoff; + size_t phentsize = ehdr->e_phentsize; + size_t phnum = ehdr->e_phnum; + + /* find load address for program headers, immediately before first segment */ + char *phdr_out = NULL, *p; + size_t n; + for (p = phdrs, n = phnum; n--; p += phentsize) { + Elf32_Phdr *ph = (void*)p; + if (ph->p_type == PT_LOAD) { + phdr_out = (char*)ph->p_vaddr - phentsize * phnum; + break; + } + } + if (!phdr_out) fail(); + + /* fixup PT_PHDR program header (if present) to point to where we're loading it */ + for (p = phdrs, n = phnum; n--; p += phentsize) { + Elf32_Phdr *ph = (void*)p; + if (ph->p_type == PT_PHDR) { + ph->p_vaddr = (Elf32_Word)phdr_out; + break; + } + } + + /* load program headers */ + load_segment(phdr_out, phdrs, phentsize * phnum, phentsize * phnum); + + /* load all the PT_LOAD segments */ + for (p = phdrs, n = phnum; n--; p += phentsize) { + Elf32_Phdr *ph = (void*)p; + if (ph->p_type != PT_LOAD) continue; + load_segment((void*)ph->p_vaddr, base + ph->p_offset, ph->p_memsz, ph->p_filesz); + } + +#define PUSH(v) do { *--stack = (long)(v); } while(0) +#define PUSH_AUX(a,v) do { PUSH(v); PUSH(a); } while(0) + + /* push aux vector onto target process stack */ + long *emptystr = stack - 1; + PUSH_AUX(AT_NULL, 0); + PUSH_AUX(AT_PHDR, phdr_out); + PUSH_AUX(AT_PHENT, phentsize); + PUSH_AUX(AT_PHNUM, phnum); + PUSH_AUX(AT_ENTRY, entry); + + /* push environment and arguments */ + PUSH(0); /* envp[0] */ + PUSH(0); /* argv[1] */ + PUSH(emptystr); /* argv[0] */ + PUSH(1); /* argc */ + + jump(entry, stack); + fail(); +} + |