Files
kernel/sys/mod.c
T
2020-07-26 23:02:13 +03:00

468 lines
14 KiB
C

#include "sys/mem/vmalloc.h"
#include "arch/amd64/cpu.h"
#include "sys/mem/phys.h"
#include "sys/mem/slab.h"
#include "sys/snprintf.h"
#include "user/module.h"
#include "user/errno.h"
#include "sys/string.h"
#include "user/fcntl.h"
#include "sys/thread.h"
#include "sys/assert.h"
#include "sys/debug.h"
#include "sys/panic.h"
#include "fs/sysfs.h"
#include "sys/heap.h"
#include "fs/ofile.h"
#include "sys/elf.h"
#include "sys/mm.h"
#include "fs/vfs.h"
// Beyond 4GiB identity mapping
#define MOD_LINK_BASE 0x100000000
#define MOD_VIRT_BASE (KERNEL_VIRT_BASE + 4 * (1UL << 30))
#define MOD_ENTRY_SYM "_mod_entry"
#define MOD_EXIT_SYM "_mod_exit"
#define MOD_DESC_SYM "_mod_desc"
enum module_state {
MOD_ACTIVE,
MOD_ERROR
};
struct module {
struct list_head list;
enum module_state state;
int (*entry_func) (void);
void (*exit_func) (void);
// TODO: maybe clone this? Only valid as long as the module
// is mapped
struct module_desc *desc;
uintptr_t base;
size_t page_count;
};
static LIST_HEAD(module_list);
static struct slab_cache *module_cache = NULL;
static int elf_map_section(mm_space_t space, uintptr_t base, uintptr_t addr, size_t size) {
// Convert size to pages, align addr down to page boundary
size = (size + MM_PAGE_SIZE - 1) / MM_PAGE_SIZE;
addr &= MM_PAGE_MASK;
// Apply module offset
addr += base;
for (size_t i = 0; i < size; ++i) {
uintptr_t virt = addr + i * MM_PAGE_SIZE;
if (mm_map_get(space, virt, NULL) == MM_NADDR) {
uintptr_t phys = mm_phys_alloc_page();
_assert(phys != MM_NADDR);
kdebug("%p: mapping to %p\n", virt, phys);
_assert(mm_map_single(space, virt, phys, MM_PAGE_WRITE | MM_PAGE_GLOBAL, PU_KERNEL) == 0);
} else {
kdebug("%p: already mapped\n", virt);
}
}
return 0;
}
static int elf_sym_lookup(const char *name, int64_t *result) {
return debug_symbol_find_by_name(name, (uintptr_t *) result);
}
static int elf_sym_get(void *image, uintptr_t base, Elf64_Shdr *shdrs, size_t table, size_t index, int64_t *value) {
if (table == SHN_UNDEF || index == SHN_UNDEF) {
return -1;
}
Elf64_Shdr *shdr_symtab = &shdrs[table];
size_t count = shdr_symtab->sh_size / sizeof(Elf64_Sym);
if (index >= count) {
kerror("Symbol index is outside of the table\n");
return -1;
}
Elf64_Sym *sym = image + shdr_symtab->sh_offset + index * sizeof(Elf64_Sym);
if (sym->st_shndx == SHN_UNDEF) {
// Lookup external reference
Elf64_Shdr *shdr_strtab = &shdrs[shdr_symtab->sh_link];
const char *name = image + shdr_strtab->sh_offset + sym->st_name;
if (elf_sym_lookup(name, value) != 0) {
kerror("Undefined reference to %s\n", name);
return -1;
} else {
return 0;
}
} else if (sym->st_shndx == SHN_ABS) {
// TODO
panic("1234\n");
} else {
// Internally defined symbol
Elf64_Shdr *target = &shdrs[sym->st_shndx];
*value = sym->st_value + target->sh_addr + base - MOD_LINK_BASE;
return 0;
}
}
#define R_X86_64_64 1
#define R_X86_64_PC32 2
#define R_X86_64_PLT32 4
#define R_X86_64_32 10
static int elf_rela_apply(void *image, uintptr_t base, Elf64_Shdr *shdrs, Elf64_Shdr *shdr_relatab) {
Elf64_Rela *relatab = image + shdr_relatab->sh_offset;
Elf64_Shdr *target = &shdrs[shdr_relatab->sh_info];
Elf64_Ehdr *ehdr = image;
(void) elf_sym_get;
if (target->sh_flags & SHF_ALLOC) {
for (size_t i = 0; i < shdr_relatab->sh_size / sizeof(Elf64_Rela); ++i) {
Elf64_Rela *rela = &relatab[i];
void *value_ptr = image + target->sh_offset + rela->r_offset;
intptr_t value = 0;
const char *target_name = image + shdrs[ehdr->e_shstrndx].sh_offset + target->sh_name;
if (ELF64_R_SYM(rela->r_info) != SHN_UNDEF) {
if (elf_sym_get(image,
base,
shdrs,
shdr_relatab->sh_link,
ELF64_R_SYM(rela->r_info),
&value) != 0) {
return -1;
}
}
switch (ELF64_R_TYPE(rela->r_info)) {
case R_X86_64_64:
{
uint64_t src = (uint64_t) (value + (intptr_t) rela->r_addend);
// value_ptr most likely will be unaligned
memcpy(value_ptr, &src, sizeof(uint64_t));
}
break;
case R_X86_64_PLT32: // Treat this the same as PC32
case R_X86_64_PC32:
// Symbol + Offset - Section Offset
{
intptr_t off = value +
(intptr_t) rela->r_offset -
(base + target->sh_addr - MOD_LINK_BASE);
if (!!(off & 0x8000000000000000) != !!(off & 0x80000000)) {
kfatal("From: %p <%04x + %s>\n",
rela->r_offset + base + target->sh_addr - MOD_LINK_BASE,
rela->r_offset,
target_name);
kfatal("Against: %p\n", value);
panic("Cannot fit relocation: %ld (%p)\n", off, off);
}
*(int32_t *) value_ptr = (int32_t) off;
}
break;
default:
panic("Unsupported relocation type: %02x\n", ELF64_R_TYPE(rela->r_info));
}
}
}
return 0;
}
static int elf_mod_load(struct module *mod, struct vfs_ioctx *ctx, struct ofile *fd) {
Elf64_Ehdr *ehdr;
Elf64_Shdr *shdrs;
Elf64_Shdr *shdr_symtab = NULL,
*shdr_strtab = NULL,
*shdr_shstrtab = NULL;
char *strtab, *shstrtab;
Elf64_Sym *symtab;
uintptr_t base;
size_t virt_size = 0;
struct stat st;
void *image;
int res = 0;
// Load the whole module image into memory
if ((res = vfs_fstatat(ctx, fd->file.vnode, NULL, &st, AT_EMPTY_PATH)) != 0) {
return res;
}
kdebug("Image size: %S\n", st.st_size);
image = kmalloc(st.st_size);
if (!image) {
kerror("Failed to allocate memory for module image\n");
return -ENOMEM;
}
size_t total = 0;
size_t rem = st.st_size;
ssize_t bread;
// Chunked read (1024 bytes per read)
while ((bread = vfs_read(ctx, fd, image + total, MIN(rem, 1024))) > 0) {
total += bread;
rem -= bread;
}
if (rem != 0) {
kerror("Failed to load module image\n");
res = -EINVAL;
goto end;
}
ehdr = image;
// TODO: check magic and whatever
shdrs = ehdr->e_shoff + image;
shdr_shstrtab = &shdrs[ehdr->e_shstrndx];
shstrtab = image + shdr_shstrtab->sh_offset;
// Find relevant sections
for (size_t i = 0; i < ehdr->e_shnum; ++i) {
Elf64_Shdr *shdr = &shdrs[i];
const char *name = &shstrtab[shdr->sh_name];
if (shdr->sh_type == SHT_STRTAB && !strcmp(name, ".strtab")) {
_assert(!shdr_strtab);
shdr_strtab = shdr;
}
if (shdr->sh_type == SHT_SYMTAB) {
_assert(!shdr_symtab);
shdr_symtab = shdr;
}
}
_assert(shdr_symtab);
_assert(shdr_strtab);
strtab = shdr_strtab->sh_offset + image;
symtab = shdr_symtab->sh_offset + image;
// Find out virtual memory length for module
for (size_t i = 0; i < ehdr->e_shnum; ++i) {
Elf64_Shdr *shdr = &shdrs[i];
if (shdr->sh_flags & SHF_ALLOC) {
if (shdr->sh_addr >= MOD_LINK_BASE && shdr->sh_size + shdr->sh_addr - MOD_LINK_BASE > virt_size) {
virt_size = shdr->sh_size + shdr->sh_addr - MOD_LINK_BASE;
}
}
}
// Convert to page count
virt_size = (virt_size + MM_PAGE_SIZE - 1) / MM_PAGE_SIZE;
// Allocate a virtual memory region for the module
base = vmfind(mm_kernel, MOD_VIRT_BASE, MOD_VIRT_BASE + 4 * (1UL << 30), virt_size);
if (base == MM_NADDR) {
panic("Failed to allocate memory for module\n");
}
// Module is now based
mod->base = base;
mod->page_count = virt_size;
// Perform symbol relocations
for (size_t i = 0; i < ehdr->e_shnum; ++i) {
Elf64_Shdr *shdr = &shdrs[i];
if (shdr->sh_type == SHT_RELA) {
if (elf_rela_apply(image, base, shdrs, shdr) != 0) {
res = -EINVAL;
goto end;
}
}
}
// Load BITS sections
for (size_t i = 0; i < ehdr->e_shnum; ++i) {
Elf64_Shdr *shdr = &shdrs[i];
if (shdr->sh_flags & SHF_ALLOC) {
const char *name = &shstrtab[shdr->sh_name];
_assert(shdr->sh_addr >= MOD_LINK_BASE);
_assert(elf_map_section(mm_kernel, base, shdr->sh_addr - MOD_LINK_BASE, shdr->sh_size) == 0);
switch (shdr->sh_type) {
case SHT_PROGBITS:
memcpy((void *) (shdr->sh_addr + base - MOD_LINK_BASE), image + shdr->sh_offset, shdr->sh_size);
break;
case SHT_NOBITS:
memset((void *) (shdr->sh_addr + base - MOD_LINK_BASE), 0, shdr->sh_size);
break;
}
}
}
// Find relevant symbols
for (size_t i = 0; i < shdr_symtab->sh_size / sizeof(Elf64_Sym); ++i) {
Elf64_Sym *sym = &symtab[i];
if (sym->st_name > shdr_strtab->sh_size) {
continue;
}
const char *name = &strtab[sym->st_name];
Elf64_Shdr *shdr = &shdrs[sym->st_shndx];
uintptr_t addr = sym->st_value + base - MOD_LINK_BASE + shdr->sh_addr;
if (!strcmp(name, MOD_ENTRY_SYM)) {
if (ELF_ST_TYPE(sym->st_info) != STT_FUNC) {
panic("%s: not a function symbol\n", name);
}
mod->entry_func = (void *) addr;
} else if (!strcmp(name, MOD_EXIT_SYM)) {
if (ELF_ST_TYPE(sym->st_info) != STT_FUNC) {
panic("%s: not a function symbol\n", name);
}
mod->exit_func = (void *) addr;
} else if (!strcmp(name, MOD_DESC_SYM)) {
mod->desc = (void *) addr;
}
}
end:
kfree(image);
return res;
}
static int module_enter(struct module *mod) {
return mod->entry_func();
}
static void module_destroy(struct module *mod) {
if (mod->base) {
for (size_t i = 0; i < mod->page_count; ++i) {
uintptr_t mapping = mm_map_get(mm_kernel, mod->base + i * MM_PAGE_SIZE, NULL);
if (mapping != MM_NADDR) {
kdebug("Releasing module page: %p\n", mapping);
_assert(mm_umap_single(mm_kernel, mod->base + i * MM_PAGE_SIZE, 1) == mapping);
}
}
}
slab_free(module_cache, mod);
}
int sys_module_unload(const char *name) {
struct thread *thr = thread_self;
struct process *proc = thr->proc;
_assert(proc);
if (proc->ioctx.uid != 0) {
return -EACCES;
}
struct module *mod = NULL;
struct module *it;
list_for_each_entry(it, &module_list, list) {
if (!strcmp(it->desc->name, name)) {
mod = it;
break;
}
}
if (!mod) {
return -ENOENT;
}
kinfo("Unloading %s\n", mod->desc->name);
if (mod->exit_func) {
mod->exit_func();
}
list_del(&mod->list);
module_destroy(mod);
return 0;
}
int sys_module_load(const char *path, const char *params) {
struct vfs_ioctx kernel_ioctx = {0, 0, 0};
int res;
struct ofile fd = {0};
struct thread *thr = thread_self;
struct process *proc = thr->proc;
_assert(proc);
if (proc->ioctx.uid != 0) {
return -EACCES;
}
// Create a new module
if (!module_cache) {
_assert(module_cache = slab_cache_get(sizeof(struct module)));
}
struct module *mod = slab_calloc(module_cache);
if (!mod) {
kerror("Failed to allocate module struct\n");
return -ENOMEM;
}
list_head_init(&mod->list);
userptr_check(path);
if (params) {
userptr_check(params);
} else {
params = "";
}
kinfo("Load module %s\n", path);
if ((res = vfs_openat(&kernel_ioctx, &fd, NULL, path, O_RDONLY, 0)) != 0) {
kerror("%s: %s\n", path, kstrerror(res));
slab_free(module_cache, mod);
return res;
}
if ((res = elf_mod_load(mod, &kernel_ioctx, &fd)) != 0) {
vfs_close(&kernel_ioctx, &fd);
kerror("elf load failed: %s\n", kstrerror(res));
slab_free(module_cache, mod);
return res;
}
vfs_close(&kernel_ioctx, &fd);
if (!mod->desc) {
kerror("%s: module doesn't have description structure\n", path);
module_destroy(mod);
return -EINVAL;
}
if (!mod->entry_func) {
kerror("%s: module doesn't have an entry function\n", path);
module_destroy(mod);
return -EINVAL;
}
if ((res = module_enter(mod)) != 0) {
module_destroy(mod);
return res;
}
list_add(&mod->list, &module_list);
kdebug("Loaded %s\n", mod->desc->name);
return 0;
}
int mod_list(void *ctx, char *buf, size_t lim) {
struct module *mod;
list_for_each_entry(mod, &module_list, list) {
sysfs_buf_printf(buf, lim, "%s %s %p %u\n",
mod->desc->name,
mod->state == MOD_ACTIVE ? "active" : "error",
mod->base,
mod->page_count);
}
return 0;
}