kernel: basic elf loading

This commit is contained in:
Matheus
2023-05-07 20:26:21 -03:00
parent 25778838ba
commit 2a0103fc46
2 changed files with 335 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
#ifndef _ELF_H
#define _ELF_H
#include <kernel/scheduler.h>
#include <kernel/vfs.h>
#include <stddef.h>
#include <stdint.h>
#define ELF_MAGIC 0x464c457f
#define ELF_RELOCATABLE 1
#define ELF_EXECUTABLE 2
#define ELF_SHARED 3
#define ELF_CORE 4
#define ELF_SEGMENT_NULL 0
#define ELF_SEGMENT_LOAD 1
#define ELF_SEGMENT_DYNAMIC 2
#define ELF_SEGMENT_INTERP 3
#define ELF_SEGMENT_NOTE 4
#define ELF_FLAGS_EXECUTABLE 1
#define ELF_FLAGS_WRITABLE 2
#define ELF_FLAGS_READABLE 4
#define AT_NULL 0
#define AT_PHDR 3
#define AT_PHENT 4
#define AT_PHNUM 5
#define AT_ENTRY 9
typedef struct {
uint64_t type;
uint64_t val;
} auxv64_t;
typedef struct {
auxv64_t phdr;
auxv64_t phnum;
auxv64_t phent;
auxv64_t entry;
auxv64_t null;
} auxv64list_t;
typedef struct {
uint32_t magic;
uint8_t bits;
uint8_t endianness;
uint8_t headversion;
uint8_t abi;
uint64_t padding;
uint16_t type;
uint16_t isa;
uint32_t elfver;
uint64_t entry;
uint64_t phpos;
uint64_t shpos;
uint32_t flags;
uint16_t headsize;
uint16_t phsize;
uint16_t phcount;
uint16_t shsize;
uint16_t shcount;
uint16_t shnames;
} __attribute__((packed)) elfheader64_t;
#define PH_TYPE_LOAD 1
#define PH_TYPE_DYNAMIC 2
#define PH_TYPE_INTERPRETER 3
#define PH_TYPE_NOTE 4
#define PH_TYPE_SHLIB 5
#define PH_TYPE_PHDR 6
typedef struct{
uint32_t type;
uint32_t flags;
uint64_t offset;
uint64_t memaddr;
uint64_t undefined;
uint64_t fsize;
uint64_t msize;
uint64_t alignment;
} __attribute__((packed)) elfph64_t;
int elf_load(vnode_t *vnode, void *base, void **entry, char **interpreter, auxv64list_t *auxv64);
void *elf_preparestack(void *top, auxv64list_t *auxv64, char **argv, char **envp);
#endif
+247
View File
@@ -0,0 +1,247 @@
#include <kernel/elf.h>
#include <logging.h>
#include <errno.h>
#include <kernel/alloc.h>
#include <util.h>
#include <kernel/vmm.h>
#ifdef __x86_64__
#define ARCH_ELF_BITS 2
#define ARCH_ELF_ISA 0x3e
#define ARCH_ELF_ENDIANNESS 1
#else
#error UNSUPPORTED ARCH
#endif
static bool headerok(elfheader64_t *header) {
if (header->magic != ELF_MAGIC)
return false;
if (header->bits != ARCH_ELF_BITS)
return false;
if (header->endianness != ARCH_ELF_ENDIANNESS)
return false;
if (header->isa != ARCH_ELF_ISA)
return false;
return true;
}
static int readexact(vnode_t *vnode, void *pos, size_t count, uintmax_t offset) {
size_t readc;
int e = vfs_read(vnode, pos, count, offset, &readc, 0);
if (e)
return e;
if (readc != count)
return ENOEXEC;
return 0;
}
static mmuflags_t flagstommuflags(int flags) {
mmuflags_t mmuflags = ARCH_MMU_FLAGS_USER;
if (flags & ELF_FLAGS_READABLE)
mmuflags |= ARCH_MMU_FLAGS_READ;
if (flags & ELF_FLAGS_WRITABLE)
mmuflags |= ARCH_MMU_FLAGS_WRITE;
if ((flags & ELF_FLAGS_READABLE) & ELF_FLAGS_EXECUTABLE)
mmuflags |= ARCH_MMU_FLAGS_NOEXEC;
return mmuflags;
}
static int load(vnode_t *vnode, elfph64_t *ph) {
// TODO verify that the load address doesn't intersect the kernel
printf("loading ph at %p with mem size %lu, disk size %lu and flags %x\n", ph->memaddr, ph->msize, ph->fsize, ph->flags);
int error = 0;
uintmax_t foffset = ph->offset;
uintptr_t mempos = ph->memaddr;
size_t msize = ph->msize;
size_t fsize = ph->fsize;
mmuflags_t mmuflags = flagstommuflags(ph->flags);
uintmax_t firstpageoffset = mempos % PAGE_SIZE;
// start is not page aligned
if (firstpageoffset) {
size_t firstpagecount = PAGE_SIZE - firstpageoffset;
firstpagecount = fsize > firstpagecount ? firstpagecount : fsize;
void *page = (void *)ROUND_DOWN(mempos, PAGE_SIZE);
if (vmm_map(page, PAGE_SIZE, VMM_FLAGS_EXACT | VMM_FLAGS_ALLOCATE, mmuflags | ARCH_MMU_FLAGS_WRITE, NULL) == NULL)
return ENOMEM;
error = readexact(vnode, (void *)mempos, firstpagecount, foffset);
if (error)
return error;
// TODO switch out protection
memset(page, 0, firstpageoffset);
mempos += firstpagecount;
msize -= firstpagecount;
fsize -= firstpagecount;
foffset += firstpagecount;
}
// map middle of file
size_t filepagecount = fsize / PAGE_SIZE;
if (filepagecount) {
vmmfiledesc_t vfd = {
.node = vnode,
.offset = foffset
};
if (vmm_map((void *)mempos, filepagecount, VMM_FLAGS_EXACT | VMM_FLAGS_PAGESIZE | VMM_FLAGS_FILE, mmuflags, &vfd) == NULL)
return ENOMEM;
size_t bytesize = filepagecount * PAGE_SIZE;
mempos += bytesize;
msize -= bytesize;
fsize -= bytesize;
foffset += bytesize;
}
// map end of file
size_t lastpagecount = fsize % PAGE_SIZE;
if (lastpagecount) {
if (vmm_map((void *)mempos, PAGE_SIZE, VMM_FLAGS_EXACT | VMM_FLAGS_ALLOCATE, mmuflags | ARCH_MMU_FLAGS_WRITE, NULL) == NULL)
return ENOMEM;
error = readexact(vnode, (void *)mempos, lastpagecount, foffset);
if (error)
return error;
// TODO switch out protection
memset((void *)(mempos + lastpagecount), 0, PAGE_SIZE - lastpagecount);
mempos += PAGE_SIZE;
msize -= msize > PAGE_SIZE ? PAGE_SIZE : msize;
}
// zero some parts if needed
if (msize) {
if (vmm_map((void *)mempos, msize, VMM_FLAGS_EXACT | VMM_FLAGS_ALLOCATE, mmuflags | ARCH_MMU_FLAGS_WRITE, NULL) == NULL)
return ENOMEM;
memset((void *)mempos, 0, ROUND_UP(msize, PAGE_SIZE));
// TODO switch out protection
}
return 0;
}
int elf_load(vnode_t *vnode, void *base, void **entry, char **interpreter, auxv64list_t *auxv64) {
// TODO return ETXTBUSY if node is open for writing
__assert(vnode->type == V_TYPE_REGULAR);
elfheader64_t header;
int err = readexact(vnode, &header, sizeof(elfheader64_t), 0);
if (err)
return err;
if (headerok(&header) == false)
return ENOEXEC;
auxv64->null.type = AT_NULL;
auxv64->phdr.type = AT_PHDR;
auxv64->phnum.type = AT_PHNUM;
auxv64->phent.type = AT_PHENT;
auxv64->entry.type = AT_ENTRY;
auxv64->phnum.val = header.phcount;
auxv64->phent.val = header.phsize;
auxv64->entry.val = header.entry;
*entry = (void *)header.entry;
__assert(header.phsize == sizeof(elfph64_t));
size_t phtablesize = sizeof(elfph64_t) * header.phcount;
elfph64_t *headers = alloc(phtablesize);
if (headers == NULL)
return ENOMEM;
err = readexact(vnode, headers, phtablesize, header.phpos);
if (err)
goto cleanup;
elfph64_t *interpreterph = NULL;
for (int i = 0; i < header.phcount; ++i) {
switch (headers[i].type) {
case PH_TYPE_INTERPRETER:
interpreterph = &headers[i];
break;
case PH_TYPE_PHDR:
auxv64->phdr.val = headers[i].memaddr;
break;
case PH_TYPE_LOAD:
headers[i].memaddr += (uintptr_t)base;
err = load(vnode, &headers[i]);
if (err)
goto cleanup;
break;
}
}
if (interpreterph) {
printf("dp: %lu dl: %lu mp: %p ml: %lu\n", interpreterph->offset, interpreterph->fsize, interpreterph->memaddr, interpreterph->msize);
__assert(!"UNIMPLEMENTED");
}
cleanup:
free(headers);
return err;
}
#define STACK_MMUFLAGS (ARCH_MMU_FLAGS_READ | ARCH_MMU_FLAGS_WRITE | ARCH_MMU_FLAGS_NOEXEC | ARCH_MMU_FLAGS_USER)
void *elf_preparestack(void *top, auxv64list_t *auxv64, char **argv, char **envp) {
size_t argc;
size_t envc;
size_t argdatasize = 0;
size_t envdatasize = 0;
for (argc = 0; argv[argc]; ++argc)
argdatasize += strlen(argv[argc]) + 1;
for (envc = 0; envp[envc]; ++envc)
envdatasize += strlen(envp[envc]) + 1;
// env data + arg data + pointers to arg and env data + argc
size_t initialsize = argdatasize + envdatasize + (argc + envc) * sizeof(char *) + sizeof(size_t) + sizeof(auxv64list_t);
size_t initialsizeround = ROUND_UP(initialsize, PAGE_SIZE);
void *initialpagebase = (void *)((uintptr_t)top - initialsizeround);
if (vmm_map(initialpagebase, initialsizeround, VMM_FLAGS_ALLOCATE | VMM_FLAGS_EXACT, STACK_MMUFLAGS, NULL) == NULL)
return NULL;
auxv64list_t *auxvstart = (auxv64list_t *)top - 1;
char *argdatastart = (char *)((uintptr_t)auxvstart - argdatasize);
char *envdatastart = (char *)((uintptr_t)argdatastart - envdatasize);
// also leave space for null entry
char **envstart = (char **)envdatastart - envc - 1;
// round down to 16 bytes
envstart = (char **)((uintptr_t)envstart & ~(uintptr_t)0xf);
char **argstart = (char **)envstart - argc - 1;
size_t *argcptr = (size_t *)argstart - 1;
memcpy(auxvstart, auxv64, sizeof(auxv64list_t));
for (int i = 0; i < argc; ++i) {
strcpy(argdatastart, argv[i]);
*argstart++ = argdatastart;
argdatastart += strlen(argv[i]) + 1;
}
for (int i = 0; i < envc; ++i) {
strcpy(envdatastart, envp[i]);
*envstart++ = envdatastart;
envdatastart += strlen(envp[i]) + 1;
}
*argstart = NULL;
*envstart = NULL;
*argcptr = argc;
return argcptr;
}