process: Rewrite ELF loading, processes
This commit is contained in:
+186
-69
@@ -1,8 +1,11 @@
|
||||
// SPDX-License-Identifier: ISC
|
||||
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/assert.h>
|
||||
#include <zinnia/elf.h>
|
||||
#include <zinnia/exec.h>
|
||||
#include <zinnia/errno.h>
|
||||
#include <zinnia/exec.h>
|
||||
#include <zinnia/initgraph.h>
|
||||
#include <zinnia/mman.h>
|
||||
#include <zinnia/percpu.h>
|
||||
#include <zinnia/pmap.h>
|
||||
@@ -14,13 +17,59 @@
|
||||
#include <zinnia/vmo.h>
|
||||
#include <bits/elf.h>
|
||||
#include <stdckdint.h>
|
||||
#include <uapi/limits.h>
|
||||
|
||||
errno_t elf_load(struct exec_info* info, struct task** out) {
|
||||
constexpr uintptr_t ELF_DYN_BASE = 0x10000;
|
||||
|
||||
struct elf_image {
|
||||
uintptr_t base;
|
||||
uintptr_t entry;
|
||||
uintptr_t phdr;
|
||||
size_t phnum;
|
||||
size_t phent;
|
||||
};
|
||||
|
||||
static bool elf_identify(struct vmo* obj) {
|
||||
elf_ehdr_t ehdr = {};
|
||||
size_t read;
|
||||
if (vmo_read(obj, 0, &ehdr, sizeof(ehdr), &read) || read != sizeof(ehdr))
|
||||
return false;
|
||||
|
||||
return memcmp(ehdr.e_ident, ELF_MAG, sizeof(ELF_MAG)) == 0;
|
||||
}
|
||||
|
||||
static errno_t elf_read_interp(struct vmo* obj, const elf_phdr_t* phdr, char** out) {
|
||||
if (phdr->p_filesz < 2 || phdr->p_filesz > __PATH_MAX)
|
||||
return ENOEXEC;
|
||||
|
||||
char* path = mem_alloc(phdr->p_filesz, ALLOC_NOZERO);
|
||||
if (!path)
|
||||
return ENOMEM;
|
||||
|
||||
size_t read;
|
||||
errno_t err = vmo_read(obj, phdr->p_offset, path, phdr->p_filesz, &read);
|
||||
if (!err && (read != phdr->p_filesz || path[phdr->p_filesz - 1] != '\0'))
|
||||
err = ENOEXEC;
|
||||
if (err) {
|
||||
mem_free(path);
|
||||
return err;
|
||||
}
|
||||
*out = path;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static errno_t elf_map(
|
||||
struct vma* space,
|
||||
struct vmo* obj,
|
||||
uintptr_t dyn_base,
|
||||
struct elf_image* out,
|
||||
char** out_interp
|
||||
) {
|
||||
errno_t err;
|
||||
|
||||
size_t read;
|
||||
elf_ehdr_t ehdr = {};
|
||||
err = vmo_read(info->file_obj, 0, &ehdr, sizeof(ehdr), &read);
|
||||
err = vmo_read(obj, 0, &ehdr, sizeof(ehdr), &read);
|
||||
if (err)
|
||||
return err;
|
||||
if (read != sizeof(ehdr))
|
||||
@@ -37,9 +86,12 @@ errno_t elf_load(struct exec_info* info, struct task** out) {
|
||||
return EINVAL;
|
||||
if (ehdr.e_machine != ELF_ARCH_MACHINE)
|
||||
return EINVAL;
|
||||
if (ehdr.e_type != ET_EXEC)
|
||||
if (ehdr.e_type != ET_EXEC && ehdr.e_type != ET_DYN)
|
||||
return EINVAL;
|
||||
if (!arch_mem_addr_is_user(ehdr.e_entry))
|
||||
|
||||
const uintptr_t base = ehdr.e_type == ET_DYN ? dyn_base : 0;
|
||||
uintptr_t entry;
|
||||
if (ckd_add(&entry, ehdr.e_entry, base) || !arch_mem_addr_is_user(entry))
|
||||
return ENOEXEC;
|
||||
|
||||
const size_t phdrs_size = (size_t)ehdr.e_phnum * ehdr.e_phentsize;
|
||||
@@ -54,14 +106,22 @@ errno_t elf_load(struct exec_info* info, struct task** out) {
|
||||
|
||||
for (size_t i = 0; i < ehdr.e_phnum; i++) {
|
||||
elf_phdr_t phdr = {};
|
||||
err = vmo_read(info->file_obj, (ehdr.e_phoff + (i * ehdr.e_phentsize)), &phdr, sizeof(phdr), &read);
|
||||
err = vmo_read(obj, (ehdr.e_phoff + (i * ehdr.e_phentsize)), &phdr, sizeof(phdr), &read);
|
||||
if (err)
|
||||
return err;
|
||||
if (read != sizeof(phdr))
|
||||
return ENOEXEC;
|
||||
|
||||
if (phdr.p_type == PT_PHDR)
|
||||
phdr_addr = phdr.p_vaddr;
|
||||
phdr_addr = phdr.p_vaddr + base;
|
||||
|
||||
if (phdr.p_type == PT_INTERP && out_interp) {
|
||||
if (*out_interp)
|
||||
return ENOEXEC;
|
||||
err = elf_read_interp(obj, &phdr, out_interp);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (phdr.p_type == PT_LOAD) {
|
||||
if (phdr.p_memsz == 0)
|
||||
@@ -69,17 +129,17 @@ errno_t elf_load(struct exec_info* info, struct task** out) {
|
||||
if (phdr.p_filesz > phdr.p_memsz)
|
||||
return ENOEXEC;
|
||||
|
||||
uintptr_t seg_end;
|
||||
if (ckd_add(&seg_end, phdr.p_vaddr, phdr.p_memsz))
|
||||
uintptr_t vaddr, seg_end;
|
||||
if (ckd_add(&vaddr, phdr.p_vaddr, base) || ckd_add(&seg_end, vaddr, phdr.p_memsz))
|
||||
return ENOEXEC;
|
||||
if (!arch_mem_addr_is_user(phdr.p_vaddr) || !arch_mem_addr_is_user(seg_end - 1))
|
||||
if (!arch_mem_addr_is_user(vaddr) || !arch_mem_addr_is_user(seg_end - 1))
|
||||
return ENOEXEC;
|
||||
|
||||
if (phdr.p_align > 1 && phdr.p_offset % phdr.p_align != phdr.p_vaddr % phdr.p_align)
|
||||
return ENOEXEC;
|
||||
|
||||
if (phdr_addr == 0 && ehdr.e_phoff >= phdr.p_offset && phdrs_end - phdr.p_offset <= phdr.p_filesz)
|
||||
phdr_addr = phdr.p_vaddr + (ehdr.e_phoff - phdr.p_offset);
|
||||
phdr_addr = vaddr + (ehdr.e_phoff - phdr.p_offset);
|
||||
|
||||
enum prot_flags prot = 0;
|
||||
if (phdr.p_flags & PF_R)
|
||||
@@ -89,44 +149,102 @@ errno_t elf_load(struct exec_info* info, struct task** out) {
|
||||
if (phdr.p_flags & PF_X)
|
||||
prot |= PROT_EXEC;
|
||||
|
||||
const uintptr_t misalign = phdr.p_vaddr & (page_size - 1);
|
||||
const uintptr_t map_address = phdr.p_vaddr - misalign;
|
||||
const uintptr_t misalign = vaddr & (page_size - 1);
|
||||
const uintptr_t map_address = vaddr - misalign;
|
||||
const size_t backed_map_size = (phdr.p_filesz + misalign + page_size - 1) & ~(page_size - 1);
|
||||
const size_t total_map_size = (phdr.p_memsz + misalign + page_size - 1) & ~(page_size - 1);
|
||||
|
||||
// Copy the file data into its own mapping.
|
||||
struct vmo* phdr_obj;
|
||||
err = vmo_new_anon(&phdr_obj);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = vmo_copy(phdr_obj, phdr.p_offset, info->file_obj, phdr.p_offset, phdr.p_filesz, nullptr);
|
||||
if (err) {
|
||||
vmo_unref(phdr_obj);
|
||||
return err;
|
||||
if (backed_map_size) {
|
||||
err = vma_map(space, obj, map_address, backed_map_size, prot, 0, phdr.p_offset - misalign);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
if (total_map_size > backed_map_size) {
|
||||
err = vma_map(
|
||||
space,
|
||||
nullptr,
|
||||
map_address + backed_map_size,
|
||||
total_map_size - backed_map_size,
|
||||
prot,
|
||||
0,
|
||||
0
|
||||
);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
// We map more than we copied so the rest is filled with zeroed pages.
|
||||
err = vma_map(info->space, phdr_obj, phdr.p_vaddr, phdr.p_memsz, prot, 0, phdr.p_offset);
|
||||
vmo_unref(phdr_obj);
|
||||
if (err)
|
||||
return err;
|
||||
const uintptr_t file_end = vaddr + phdr.p_filesz;
|
||||
if (phdr.p_memsz > phdr.p_filesz && file_end % page_size != 0) {
|
||||
const uintptr_t zero_end = MIN(ALIGN_UP(file_end, page_size), seg_end);
|
||||
err = vma_zero(space, file_end, zero_end - file_end);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*out = (struct elf_image){
|
||||
.base = base,
|
||||
.entry = entry,
|
||||
.phdr = phdr_addr,
|
||||
.phnum = ehdr.e_phnum,
|
||||
.phent = ehdr.e_phentsize,
|
||||
};
|
||||
return 0;
|
||||
}
|
||||
|
||||
static errno_t elf_load(struct process* proc, struct exec_info* info, uintptr_t* out_ip, uintptr_t* out_sp) {
|
||||
struct elf_image exe = {};
|
||||
char* interp_path = nullptr;
|
||||
errno_t err = elf_map(info->space, info->file_obj, ELF_DYN_BASE, &exe, &interp_path);
|
||||
|
||||
uintptr_t entry = exe.entry;
|
||||
uintptr_t interp_base = 0;
|
||||
if (!err && interp_path) {
|
||||
struct vmo* interp;
|
||||
err = exec_open(proc, interp_path, &interp);
|
||||
if (!err) {
|
||||
struct elf_image ld;
|
||||
if (!elf_identify(interp))
|
||||
err = ELIBBAD;
|
||||
else
|
||||
err = elf_map(info->space, interp, (uintptr_t)1 << (mem_high_shift() - 2), &ld, nullptr);
|
||||
if (!err) {
|
||||
entry = ld.entry;
|
||||
interp_base = ld.base;
|
||||
}
|
||||
vmo_unref(interp);
|
||||
}
|
||||
}
|
||||
mem_free(interp_path);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
const size_t page_size = arch_mem_page_size();
|
||||
|
||||
const uintptr_t highest = ((uintptr_t)1 << (mem_high_shift() - 1)) - page_size;
|
||||
const size_t stack_size = 2 * 1024 * 1024; // 2MiB
|
||||
const uintptr_t stack_start = highest - stack_size;
|
||||
|
||||
// Allocate stack.
|
||||
struct vmo* stack;
|
||||
err = vmo_new_anon(&stack);
|
||||
err = vma_map(info->space, nullptr, stack_start, stack_size, PROT_READ | PROT_WRITE, 0, 0);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
// Fill in stack info.
|
||||
uintptr_t stack_off = stack_size;
|
||||
|
||||
uintptr_t execfn = 0;
|
||||
if (info->exec_path) {
|
||||
const char nul = 0;
|
||||
stack_off -= 1;
|
||||
vma_write(info->space, stack_start + stack_off, &nul, sizeof(nul));
|
||||
|
||||
const size_t len = strlen(info->exec_path);
|
||||
stack_off -= len;
|
||||
vma_write(info->space, stack_start + stack_off, info->exec_path, len);
|
||||
execfn = stack_start + stack_off;
|
||||
}
|
||||
|
||||
// TODO: Instead of allocating, calculate it on the fly.
|
||||
size_t num_envp = 0;
|
||||
for (const char** ptr = info->envp; *ptr != nullptr; ptr++) {
|
||||
@@ -148,18 +266,17 @@ errno_t elf_load(struct exec_info* info, struct task** out) {
|
||||
if ((num_envp && !envp_offsets) || (num_argv && !argv_offsets)) {
|
||||
mem_free(envp_offsets);
|
||||
mem_free(argv_offsets);
|
||||
vmo_unref(stack);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
for (size_t env = 0; env < num_envp; env++) {
|
||||
const char nul = 0;
|
||||
stack_off -= 1;
|
||||
vmo_write(stack, stack_off, &nul, sizeof(nul), nullptr);
|
||||
vma_write(info->space, stack_start + stack_off, &nul, sizeof(nul));
|
||||
|
||||
const size_t len = strlen(info->envp[env]);
|
||||
stack_off -= len;
|
||||
vmo_write(stack, stack_off, info->envp[env], len, nullptr);
|
||||
vma_write(info->space, stack_start + stack_off, info->envp[env], len);
|
||||
|
||||
envp_offsets[env] = stack_start + stack_off;
|
||||
}
|
||||
@@ -167,11 +284,11 @@ errno_t elf_load(struct exec_info* info, struct task** out) {
|
||||
for (size_t arg = 0; arg < num_argv; arg++) {
|
||||
const char nul = 0;
|
||||
stack_off -= 1;
|
||||
vmo_write(stack, stack_off, &nul, sizeof(nul), nullptr);
|
||||
vma_write(info->space, stack_start + stack_off, &nul, sizeof(nul));
|
||||
|
||||
const size_t len = strlen(info->argv[arg]);
|
||||
stack_off -= len;
|
||||
vmo_write(stack, stack_off, info->argv[arg], len, nullptr);
|
||||
vma_write(info->space, stack_start + stack_off, info->argv[arg], len);
|
||||
|
||||
argv_offsets[arg] = stack_start + stack_off;
|
||||
}
|
||||
@@ -182,43 +299,48 @@ errno_t elf_load(struct exec_info* info, struct task** out) {
|
||||
if ((1 + num_argv + num_envp) & 1) {
|
||||
stack_off -= sizeof(uintptr_t);
|
||||
uintptr_t zero = 0;
|
||||
vmo_write(stack, stack_off, &zero, sizeof(zero), nullptr);
|
||||
vma_write(info->space, stack_start + stack_off, &zero, sizeof(zero));
|
||||
}
|
||||
|
||||
#define WRITE_AUXV(auxv, value) \
|
||||
do { \
|
||||
stack_off -= sizeof(uintptr_t); \
|
||||
uintptr_t auxv_val = value; \
|
||||
vmo_write(stack, stack_off, &auxv_val, sizeof(uintptr_t), nullptr); \
|
||||
vma_write(info->space, stack_start + stack_off, &auxv_val, sizeof(uintptr_t)); \
|
||||
stack_off -= sizeof(uintptr_t); \
|
||||
auxv_val = auxv; \
|
||||
vmo_write(stack, stack_off, &auxv_val, sizeof(uintptr_t), nullptr); \
|
||||
vma_write(info->space, stack_start + stack_off, &auxv_val, sizeof(uintptr_t)); \
|
||||
} while (0)
|
||||
|
||||
// Write auxiliary values.
|
||||
WRITE_AUXV(AT_NULL, 0);
|
||||
WRITE_AUXV(AT_SECURE, 0);
|
||||
WRITE_AUXV(AT_PHNUM, ehdr.e_phnum);
|
||||
WRITE_AUXV(AT_PHENT, ehdr.e_phentsize);
|
||||
WRITE_AUXV(AT_ENTRY, ehdr.e_entry);
|
||||
if (phdr_addr)
|
||||
WRITE_AUXV(AT_PHDR, phdr_addr);
|
||||
WRITE_AUXV(AT_PAGESZ, page_size);
|
||||
WRITE_AUXV(AT_PHNUM, exe.phnum);
|
||||
WRITE_AUXV(AT_PHENT, exe.phent);
|
||||
WRITE_AUXV(AT_ENTRY, exe.entry);
|
||||
if (exe.phdr)
|
||||
WRITE_AUXV(AT_PHDR, exe.phdr);
|
||||
if (interp_base)
|
||||
WRITE_AUXV(AT_BASE, interp_base);
|
||||
if (execfn)
|
||||
WRITE_AUXV(AT_EXECFN, execfn);
|
||||
|
||||
// envp pointers.
|
||||
stack_off -= sizeof(uintptr_t);
|
||||
const uintptr_t zero = 0;
|
||||
vmo_write(stack, stack_off, &zero, sizeof(zero), nullptr);
|
||||
vma_write(info->space, stack_start + stack_off, &zero, sizeof(zero));
|
||||
for (size_t env = num_envp; env > 0; env--) {
|
||||
stack_off -= sizeof(uintptr_t);
|
||||
vmo_write(stack, stack_off, &envp_offsets[env - 1], sizeof(envp_offsets[env - 1]), nullptr);
|
||||
vma_write(info->space, stack_start + stack_off, &envp_offsets[env - 1], sizeof(envp_offsets[env - 1]));
|
||||
}
|
||||
|
||||
// argv pointers.
|
||||
stack_off -= sizeof(uintptr_t);
|
||||
vmo_write(stack, stack_off, &zero, sizeof(zero), nullptr);
|
||||
vma_write(info->space, stack_start + stack_off, &zero, sizeof(zero));
|
||||
for (size_t arg = num_argv; arg > 0; arg--) {
|
||||
stack_off -= sizeof(uintptr_t);
|
||||
vmo_write(stack, stack_off, &argv_offsets[arg - 1], sizeof(argv_offsets[arg - 1]), nullptr);
|
||||
vma_write(info->space, stack_start + stack_off, &argv_offsets[arg - 1], sizeof(argv_offsets[arg - 1]));
|
||||
}
|
||||
|
||||
mem_free(envp_offsets);
|
||||
@@ -227,27 +349,22 @@ errno_t elf_load(struct exec_info* info, struct task** out) {
|
||||
// argc
|
||||
stack_off -= sizeof(uintptr_t);
|
||||
uintptr_t argc = num_argv;
|
||||
vmo_write(stack, stack_off, &argc, sizeof(argc), nullptr);
|
||||
vma_write(info->space, stack_start + stack_off, &argc, sizeof(argc));
|
||||
|
||||
err = vma_map(info->space, stack, stack_start, stack_size, PROT_READ | PROT_WRITE, 0, 0);
|
||||
vmo_unref(stack);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
struct task_start_info* start_info = mem_alloc(sizeof(struct task_start_info), 0);
|
||||
if (!start_info)
|
||||
return ENOMEM;
|
||||
start_info->ip = ehdr.e_entry;
|
||||
start_info->sp = highest - stack_size + stack_off;
|
||||
start_info->arg = 0;
|
||||
|
||||
struct task* result;
|
||||
err = task_create(info->argv[0], info->space, sched_to_user, (uintptr_t)start_info, &result);
|
||||
if (err) {
|
||||
mem_free(start_info);
|
||||
return err;
|
||||
}
|
||||
|
||||
*out = result;
|
||||
*out_ip = entry;
|
||||
*out_sp = highest - stack_size + stack_off;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct exec_format elf_format = {
|
||||
.name = "elf",
|
||||
.identify = elf_identify,
|
||||
.load = elf_load,
|
||||
};
|
||||
|
||||
static void elf_stage_init(struct boot_info*) {
|
||||
exec_register(&elf_format);
|
||||
}
|
||||
INIT_TASK(elf_stage, "fs.exec.elf", elf_stage_init);
|
||||
INIT_DEPENDS(elf_stage, mem_stage);
|
||||
INIT_ENTAILS(elf_stage, vfs_stage);
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// SPDX-License-Identifier: ISC
|
||||
|
||||
#include <zinnia/exec.h>
|
||||
#include <zinnia/mutex.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <zinnia/process.h>
|
||||
#include <zinnia/tailq.h>
|
||||
#include <zinnia/vfs.h>
|
||||
|
||||
static mutex_t formats_lock;
|
||||
static tailq_head(struct exec_format) formats = {nullptr, &formats.tqh_first};
|
||||
|
||||
struct exec_format* exec_identify(struct vmo* obj) {
|
||||
mutex_lock(&formats_lock);
|
||||
struct exec_format* format = tailq_first(&formats);
|
||||
while (format != nullptr && !format->identify(obj))
|
||||
format = tailq_next(format, link);
|
||||
mutex_unlock(&formats_lock);
|
||||
return format;
|
||||
}
|
||||
|
||||
void exec_register(struct exec_format* format) {
|
||||
mutex_lock(&formats_lock);
|
||||
tailq_insert_tail(&formats, format, link);
|
||||
mutex_unlock(&formats_lock);
|
||||
|
||||
log("exec: Registered new exec format \"%s\"\n", format->name);
|
||||
}
|
||||
|
||||
errno_t exec_open(struct process* proc, const char* path, struct vmo** out) {
|
||||
mutex_lock(&proc->lock);
|
||||
struct vfs_path root = vfs_path_clone(proc->root);
|
||||
struct vfs_path cwd = vfs_path_clone(proc->cwd);
|
||||
mutex_unlock(&proc->lock);
|
||||
|
||||
struct vfs_file* file;
|
||||
errno_t status = vfs_file_open(root, cwd, path, O_RDONLY | O_EXEC, 0, &proc->identity, &file);
|
||||
vfs_path_drop(root);
|
||||
vfs_path_drop(cwd);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
status = exec_file_object(proc, file, out);
|
||||
vfs_file_unref(file);
|
||||
return status;
|
||||
}
|
||||
|
||||
errno_t exec_file_object(struct process* proc, struct vfs_file* file, struct vmo** out) {
|
||||
errno_t status = vfs_node_check_exec(file->vnode, &proc->identity);
|
||||
if (!status)
|
||||
status = vfs_file_get_object(file, out);
|
||||
return status;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
kernel_sources += files(
|
||||
'elf.c',
|
||||
'exec.c',
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user