From 1c76675e40bfcbb3dd8ad53eb65c3bc106790c4d Mon Sep 17 00:00:00 2001 From: apio Date: Sun, 30 Jul 2023 18:25:44 +0200 Subject: [PATCH] kernel: Add a framework to add more executable formats, possibly from userspace This lets us implement shebangs and possibly an interface similar to Linux's binfmt_misc. --- kernel/CMakeLists.txt | 3 +- kernel/src/binfmt/BinaryFormat.cpp | 37 +++++++ kernel/src/binfmt/BinaryFormat.h | 32 ++++++ kernel/src/binfmt/ELF.cpp | 147 ++++++++++++++++++++++++++++ kernel/src/{thread => binfmt}/ELF.h | 22 +++-- kernel/src/main.cpp | 3 + kernel/src/sys/exec.cpp | 12 ++- kernel/src/thread/ELF.cpp | 128 ------------------------ kernel/src/thread/Scheduler.cpp | 7 +- kernel/src/thread/ThreadImage.cpp | 12 +-- kernel/src/thread/ThreadImage.h | 6 +- 11 files changed, 260 insertions(+), 149 deletions(-) create mode 100644 kernel/src/binfmt/BinaryFormat.cpp create mode 100644 kernel/src/binfmt/BinaryFormat.h create mode 100644 kernel/src/binfmt/ELF.cpp rename kernel/src/{thread => binfmt}/ELF.h (77%) delete mode 100644 kernel/src/thread/ELF.cpp diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 8f311108..88cbefa7 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -60,7 +60,8 @@ set(SOURCES src/fs/devices/FramebufferDevice.cpp src/fs/devices/UARTDevice.cpp src/fs/InitRD.cpp - src/thread/ELF.cpp + src/binfmt/ELF.cpp + src/binfmt/BinaryFormat.cpp ) if("${LUNA_ARCH}" MATCHES "x86_64") diff --git a/kernel/src/binfmt/BinaryFormat.cpp b/kernel/src/binfmt/BinaryFormat.cpp new file mode 100644 index 00000000..35ea39c0 --- /dev/null +++ b/kernel/src/binfmt/BinaryFormat.cpp @@ -0,0 +1,37 @@ +#include "binfmt/BinaryFormat.h" +#include "binfmt/ELF.h" + +struct BinaryFormatDescriptor +{ + binfmt_loader_creator_t creator; + void* arg; +}; + +Vector g_binary_formats; + +Result BinaryFormat::init() +{ + TRY(register_binary_format(ELFLoader::create, nullptr)); + + return {}; +} + +Result BinaryFormat::register_binary_format(binfmt_loader_creator_t creator, void* arg) +{ + return g_binary_formats.try_append({ creator, arg }); +} + +Result> BinaryFormat::create_loader(SharedPtr inode) +{ + for (const auto& format : g_binary_formats) + { + auto loader = TRY(format.creator(inode, format.arg)); + if (TRY(loader->sniff())) return loader; + } + + return err(ENOEXEC); +} + +BinaryFormatLoader::BinaryFormatLoader(SharedPtr inode) : m_inode(inode) +{ +} diff --git a/kernel/src/binfmt/BinaryFormat.h b/kernel/src/binfmt/BinaryFormat.h new file mode 100644 index 00000000..b8b5e8ec --- /dev/null +++ b/kernel/src/binfmt/BinaryFormat.h @@ -0,0 +1,32 @@ +#pragma once +#include "fs/VFS.h" +#include "memory/AddressSpace.h" + +class BinaryFormatLoader : public Shareable +{ + public: + virtual Result sniff() = 0; + virtual Result load(AddressSpace* space) = 0; + + virtual StringView format() const = 0; + + virtual Result> cmdline(const String& path, Vector args) = 0; + + virtual ~BinaryFormatLoader() = default; + + BinaryFormatLoader(SharedPtr); + + protected: + SharedPtr m_inode; +}; + +typedef Result> (*binfmt_loader_creator_t)(SharedPtr, void*); + +namespace BinaryFormat +{ + Result init(); + + Result register_binary_format(binfmt_loader_creator_t creator, void* arg); + + Result> create_loader(SharedPtr inode); +} diff --git a/kernel/src/binfmt/ELF.cpp b/kernel/src/binfmt/ELF.cpp new file mode 100644 index 00000000..0d2c39c6 --- /dev/null +++ b/kernel/src/binfmt/ELF.cpp @@ -0,0 +1,147 @@ +#include "binfmt/ELF.h" +#include "Log.h" +#include "arch/CPU.h" +#include "arch/MMU.h" +#include "memory/MemoryManager.h" +#include +#include +#include +#include + +static bool can_execute_segment(u32 flags) +{ + return flags & 1; +} + +static bool can_write_segment(u32 flags) +{ + return flags & 2; +} + +/*static bool can_write_and_execute_segment(u32 flags) +{ + return can_write_segment(flags) && can_execute_segment(flags); +}*/ + +Result ELFLoader::sniff() +{ + u8 buf[SELFMAG]; + usize nread = TRY(m_inode->read(buf, 0, sizeof buf)); + if (nread < SELFMAG) return false; + + return !memcmp(buf, ELFMAG, SELFMAG); +} + +Result ELFLoader::load(AddressSpace* space) +{ + Elf64_Ehdr elf_header; + usize nread = TRY(m_inode->read((u8*)&elf_header, 0, sizeof elf_header)); + + if (nread < sizeof elf_header) + { + kerrorln("Error while loading ELF: ELF header does not fit in file"); + return err(ENOEXEC); + } + + if (memcmp(elf_header.e_ident, ELFMAG, SELFMAG) != 0) + { + kerrorln("Error while loading ELF: ELF header has no valid magic"); + return err(ENOEXEC); + } + + if (elf_header.e_ident[EI_CLASS] != ELFCLASS64) + { + kerrorln("Error while loading ELF: ELF object is not 64-bit"); + return err(ENOEXEC); + } + + if (elf_header.e_ident[EI_DATA] != ELFDATA2LSB) + { + kerrorln("Error while loading ELF: ELF object is not 2's complement little-endian"); + return err(ENOEXEC); + } + + if (elf_header.e_type != ET_EXEC) + { + kerrorln("Error while loading ELF: ELF object is not an executable"); + return err(ENOEXEC); + } + + if (elf_header.e_machine != EM_MACH) + { + kerrorln("Error while loading ELF: ELF object's target architecture does not match the current one (%s)", + CPU::platform_string().chars()); + return err(ENOEXEC); + } + + if (elf_header.e_phnum == 0) + { + kerrorln("Error while loading ELF: ELF object has no program headers"); + return err(ENOEXEC); + } + +#ifdef ELF_DEBUG + kdbgln("ELF: Loading ELF with entry=%#.16lx", elf_header.e_entry); +#endif + + usize i; + Elf64_Phdr program_header; + + for (TRY(m_inode->read((u8*)&program_header, elf_header.e_phoff, sizeof program_header)), i = 0; + i < elf_header.e_phnum; + i++, TRY(m_inode->read((u8*)&program_header, elf_header.e_phoff + (i * elf_header.e_phentsize), + sizeof program_header))) + { + if (program_header.p_type == PT_LOAD) + { +#ifdef ELF_DEBUG + kdbgln("ELF: Loading segment (offset=%zu, base=%#.16lx, filesize=%zu, memsize=%zu)", + program_header.p_offset, program_header.p_vaddr, program_header.p_filesz, program_header.p_memsz); +#endif + + u64 base_vaddr = align_down(program_header.p_vaddr); + u64 vaddr_diff = program_header.p_vaddr - base_vaddr; + /*expect(!can_write_and_execute_segment(program_header.p_flags), + "Segment is both writable and executable");*/ + + int flags = MMU::User | MMU::NoExecute; + if (can_write_segment(program_header.p_flags)) flags |= MMU::ReadWrite; + if (can_execute_segment(program_header.p_flags)) flags &= ~MMU::NoExecute; + + if (!TRY(space->test_and_alloc_region( + base_vaddr, get_blocks_from_size(program_header.p_memsz + vaddr_diff, ARCH_PAGE_SIZE), true))) + return err(ENOMEM); + + // Allocate physical memory for the segment + TRY(MemoryManager::alloc_at( + base_vaddr, get_blocks_from_size(program_header.p_memsz + vaddr_diff, ARCH_PAGE_SIZE), flags)); + + // Zero out unused memory (before the start of the segment) + memset((void*)base_vaddr, 0, vaddr_diff); + + // Load the file section of the segment + m_inode->read((u8*)program_header.p_vaddr, program_header.p_offset, program_header.p_filesz); + + // Fill out the rest of the segment with 0s + memset((void*)(program_header.p_vaddr + program_header.p_filesz), 0, + program_header.p_memsz - program_header.p_filesz); + } + else { kwarnln("ELF: Encountered non-loadable program header, skipping"); } + } + + return elf_header.e_entry; +} + +Result> ELFLoader::cmdline(const String&, Vector args) +{ + return args; +} + +ELFLoader::ELFLoader(SharedPtr inode) : BinaryFormatLoader(inode) +{ +} + +Result> ELFLoader::create(SharedPtr inode, void*) +{ + return (SharedPtr)TRY(make_shared(inode)); +} diff --git a/kernel/src/thread/ELF.h b/kernel/src/binfmt/ELF.h similarity index 77% rename from kernel/src/thread/ELF.h rename to kernel/src/binfmt/ELF.h index c4045b5f..3db1a59e 100644 --- a/kernel/src/thread/ELF.h +++ b/kernel/src/binfmt/ELF.h @@ -1,6 +1,8 @@ #pragma once +#include "binfmt/BinaryFormat.h" #include "fs/VFS.h" #include "memory/AddressSpace.h" +#include #include #define ELFMAG "\177ELF" @@ -47,12 +49,20 @@ typedef struct u64 p_align; /* Segment alignment */ } Elf64_Phdr; -struct ELFData +class ELFLoader : public BinaryFormatLoader { - u64 entry; -}; + public: + Result sniff() override; + Result load(AddressSpace* space) override; -namespace ELFLoader -{ - Result load(SharedPtr inode, AddressSpace* space); + Result> cmdline(const String& path, Vector args) override; + + StringView format() const override + { + return "elf"; + } + + ELFLoader(SharedPtr inode); + + static Result> create(SharedPtr inode, void*); }; diff --git a/kernel/src/main.cpp b/kernel/src/main.cpp index 852afba3..103b0eea 100644 --- a/kernel/src/main.cpp +++ b/kernel/src/main.cpp @@ -1,6 +1,7 @@ #include "Log.h" #include "arch/CPU.h" #include "arch/Timer.h" +#include "binfmt/BinaryFormat.h" #include "boot/Init.h" #include "config.h" #include "fs/InitRD.h" @@ -45,6 +46,8 @@ void reap_thread() mark_critical(InitRD::populate_vfs(), "Failed to load files from the initial ramdisk"); mark_critical(DeviceRegistry::init(), "Failed to register initial devices"); + mark_critical(BinaryFormat::init(), "Failed to register initial binary formats"); + auto init = mark_critical(VFS::resolve_path("/bin/preinit", Credentials {}), "Can't find init in the initial ramfs!"); auto init_thread = diff --git a/kernel/src/sys/exec.cpp b/kernel/src/sys/exec.cpp index 6ca0c021..4bfb5ae5 100644 --- a/kernel/src/sys/exec.cpp +++ b/kernel/src/sys/exec.cpp @@ -1,8 +1,8 @@ #include "Log.h" +#include "binfmt/BinaryFormat.h" #include "fs/VFS.h" #include "memory/MemoryManager.h" #include "sys/Syscall.h" -#include "thread/ELF.h" #include "thread/Scheduler.h" #include "thread/ThreadImage.h" #include @@ -70,9 +70,17 @@ Result sys_execve(Registers* regs, SyscallArgs args) kdbgln("exec: attempting to replace current image with %s", path.chars()); #endif + auto loader = TRY(BinaryFormat::create_loader(inode)); + +#ifdef EXEC_DEBUG + kdbgln("exec: created loader for binary format %s", loader->format().chars()); +#endif + auto guard = make_scope_guard([current] { MMU::switch_page_directory(current->self_directory()); }); - auto image = TRY(ThreadImage::try_load_from_elf(inode)); + auto image = TRY(ThreadImage::try_load_from_binary(loader)); + + argv = TRY(loader->cmdline(move(path), move(argv))); u64 user_argv = TRY(image->push_string_vector_on_stack(argv)); usize user_argc = argv.size(); diff --git a/kernel/src/thread/ELF.cpp b/kernel/src/thread/ELF.cpp deleted file mode 100644 index f7e9e1f8..00000000 --- a/kernel/src/thread/ELF.cpp +++ /dev/null @@ -1,128 +0,0 @@ -#include "ELF.h" -#include "Log.h" -#include "arch/CPU.h" -#include "arch/MMU.h" -#include "memory/MemoryManager.h" -#include -#include -#include -#include - -static bool can_execute_segment(u32 flags) -{ - return flags & 1; -} - -static bool can_write_segment(u32 flags) -{ - return flags & 2; -} - -/*static bool can_write_and_execute_segment(u32 flags) -{ - return can_write_segment(flags) && can_execute_segment(flags); -}*/ - -namespace ELFLoader -{ - Result load(SharedPtr inode, AddressSpace* space) - { - Elf64_Ehdr elf_header; - usize nread = TRY(inode->read((u8*)&elf_header, 0, sizeof elf_header)); - - if (nread < sizeof elf_header) - { - kerrorln("Error while loading ELF: ELF header does not fit in file"); - return err(ENOEXEC); - } - - if (memcmp(elf_header.e_ident, ELFMAG, SELFMAG) != 0) - { - kerrorln("Error while loading ELF: ELF header has no valid magic"); - return err(ENOEXEC); - } - - if (elf_header.e_ident[EI_CLASS] != ELFCLASS64) - { - kerrorln("Error while loading ELF: ELF object is not 64-bit"); - return err(ENOEXEC); - } - - if (elf_header.e_ident[EI_DATA] != ELFDATA2LSB) - { - kerrorln("Error while loading ELF: ELF object is not 2's complement little-endian"); - return err(ENOEXEC); - } - - if (elf_header.e_type != ET_EXEC) - { - kerrorln("Error while loading ELF: ELF object is not an executable"); - return err(ENOEXEC); - } - - if (elf_header.e_machine != EM_MACH) - { - kerrorln("Error while loading ELF: ELF object's target architecture does not match the current one (%s)", - CPU::platform_string().chars()); - return err(ENOEXEC); - } - - if (elf_header.e_phnum == 0) - { - kerrorln("Error while loading ELF: ELF object has no program headers"); - return err(ENOEXEC); - } - -#ifdef ELF_DEBUG - kdbgln("ELF: Loading ELF with entry=%#.16lx", elf_header.e_entry); -#endif - - usize i; - Elf64_Phdr program_header; - - for (TRY(inode->read((u8*)&program_header, elf_header.e_phoff, sizeof program_header)), i = 0; - i < elf_header.e_phnum; - i++, TRY(inode->read((u8*)&program_header, elf_header.e_phoff + (i * elf_header.e_phentsize), - sizeof program_header))) - { - if (program_header.p_type == PT_LOAD) - { -#ifdef ELF_DEBUG - kdbgln("ELF: Loading segment (offset=%zu, base=%#.16lx, filesize=%zu, memsize=%zu)", - program_header.p_offset, program_header.p_vaddr, program_header.p_filesz, - program_header.p_memsz); -#endif - - u64 base_vaddr = align_down(program_header.p_vaddr); - u64 vaddr_diff = program_header.p_vaddr - base_vaddr; - /*expect(!can_write_and_execute_segment(program_header.p_flags), - "Segment is both writable and executable");*/ - - int flags = MMU::User | MMU::NoExecute; - if (can_write_segment(program_header.p_flags)) flags |= MMU::ReadWrite; - if (can_execute_segment(program_header.p_flags)) flags &= ~MMU::NoExecute; - - if (!TRY(space->test_and_alloc_region( - base_vaddr, get_blocks_from_size(program_header.p_memsz + vaddr_diff, ARCH_PAGE_SIZE), true))) - return err(ENOMEM); - - // Allocate physical memory for the segment - TRY(MemoryManager::alloc_at( - base_vaddr, get_blocks_from_size(program_header.p_memsz + vaddr_diff, ARCH_PAGE_SIZE), flags)); - - // Zero out unused memory (before the start of the segment) - memset((void*)base_vaddr, 0, vaddr_diff); - - // Load the file section of the segment - inode->read((u8*)program_header.p_vaddr, program_header.p_offset, program_header.p_filesz); - - // Fill out the rest of the segment with 0s - memset((void*)(program_header.p_vaddr + program_header.p_filesz), 0, - program_header.p_memsz - program_header.p_filesz); - } - else { kwarnln("ELF: Encountered non-loadable program header, skipping"); } - } - - return ELFData { elf_header.e_entry }; - } -} diff --git a/kernel/src/thread/Scheduler.cpp b/kernel/src/thread/Scheduler.cpp index c2270eb7..6e472228 100644 --- a/kernel/src/thread/Scheduler.cpp +++ b/kernel/src/thread/Scheduler.cpp @@ -1,8 +1,8 @@ #include "thread/Scheduler.h" -#include "ELF.h" #include "Log.h" #include "arch/CPU.h" #include "arch/MMU.h" +#include "binfmt/ELF.h" #include "memory/MemoryManager.h" #include "thread/ThreadImage.h" #include @@ -147,7 +147,10 @@ namespace Scheduler auto guard = make_scope_guard([&] { delete thread; }); - auto image = TRY(ThreadImage::try_load_from_elf(inode)); + // Contrary to other programs, which use BinaryFormat::create_loader(), init must be a native executable. + auto loader = TRY(ELFLoader::create(inode, nullptr)); + + auto image = TRY(ThreadImage::try_load_from_binary(loader)); u64 argv = TRY(image->push_string_vector_on_stack(args)); u64 envp = TRY(image->push_string_vector_on_stack(env)); diff --git a/kernel/src/thread/ThreadImage.cpp b/kernel/src/thread/ThreadImage.cpp index c96c66b4..43d1c9ac 100644 --- a/kernel/src/thread/ThreadImage.cpp +++ b/kernel/src/thread/ThreadImage.cpp @@ -24,7 +24,7 @@ static Result create_user_stack(Stack& user_stack, AddressSpace* space) return {}; } -Result> ThreadImage::try_load_from_elf(SharedPtr inode) +Result> ThreadImage::try_load_from_binary(SharedPtr loader) { auto image = TRY(make_owned()); @@ -36,7 +36,7 @@ Result> ThreadImage::try_load_from_elf(SharedPtrload(address_space.ptr())); Stack user_stack; TRY(create_user_stack(user_stack, address_space.ptr())); @@ -44,7 +44,7 @@ Result> ThreadImage::try_load_from_elf(SharedPtrm_user_stack = user_stack; - image->m_loaded_image_data = data; + image->m_program_entry = entry; image->m_address_space = move(address_space); image->m_sp = user_stack.top(); @@ -57,14 +57,12 @@ Result> ThreadImage::clone_from_thread(Thread* parent) auto address_space = TRY(parent->address_space->clone()); - const ELFData data = { .entry = parent->ip() }; - const u64 kernel_stack_base = TRY(MemoryManager::alloc_for_kernel(4, MMU::ReadWrite | MMU::NoExecute)); Stack kernel_stack { kernel_stack_base, 4 * ARCH_PAGE_SIZE }; image->m_kernel_stack = kernel_stack; image->m_user_stack = parent->stack; - image->m_loaded_image_data = data; + image->m_program_entry = parent->ip(); image->m_address_space = move(address_space); image->m_sp = parent->sp(); @@ -103,7 +101,7 @@ void ThreadImage::apply(Thread* thread) { thread->init_regs_user(); - thread->set_ip(m_loaded_image_data.entry); + thread->set_ip(m_program_entry); if (m_kernel_stack.bottom()) thread->kernel_stack = m_kernel_stack; thread->stack = m_user_stack; diff --git a/kernel/src/thread/ThreadImage.h b/kernel/src/thread/ThreadImage.h index f6f7fbad..33a8031c 100644 --- a/kernel/src/thread/ThreadImage.h +++ b/kernel/src/thread/ThreadImage.h @@ -1,8 +1,8 @@ #pragma once -#include "ELF.h" #include "arch/CPU.h" #include "arch/MMU.h" +#include "binfmt/BinaryFormat.h" #include "fs/VFS.h" #include "memory/AddressSpace.h" #include "thread/Thread.h" @@ -18,7 +18,7 @@ class Thread; class ThreadImage { public: - static Result> try_load_from_elf(SharedPtr inode); + static Result> try_load_from_binary(SharedPtr inode); static Result> clone_from_thread(Thread* parent); @@ -31,6 +31,6 @@ class ThreadImage OwnedPtr m_address_space; Stack m_user_stack; Stack m_kernel_stack; - ELFData m_loaded_image_data; + u64 m_program_entry; u64 m_sp; };