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.
This commit is contained in:
apio 2023-07-30 18:25:44 +02:00
parent 6e269c6bc4
commit 1c76675e40
Signed by: apio
GPG Key ID: B8A7D06E42258954
11 changed files with 260 additions and 149 deletions

View File

@ -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")

View File

@ -0,0 +1,37 @@
#include "binfmt/BinaryFormat.h"
#include "binfmt/ELF.h"
struct BinaryFormatDescriptor
{
binfmt_loader_creator_t creator;
void* arg;
};
Vector<BinaryFormatDescriptor> g_binary_formats;
Result<void> BinaryFormat::init()
{
TRY(register_binary_format(ELFLoader::create, nullptr));
return {};
}
Result<void> BinaryFormat::register_binary_format(binfmt_loader_creator_t creator, void* arg)
{
return g_binary_formats.try_append({ creator, arg });
}
Result<SharedPtr<BinaryFormatLoader>> BinaryFormat::create_loader(SharedPtr<VFS::Inode> 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<VFS::Inode> inode) : m_inode(inode)
{
}

View File

@ -0,0 +1,32 @@
#pragma once
#include "fs/VFS.h"
#include "memory/AddressSpace.h"
class BinaryFormatLoader : public Shareable
{
public:
virtual Result<bool> sniff() = 0;
virtual Result<u64> load(AddressSpace* space) = 0;
virtual StringView format() const = 0;
virtual Result<Vector<String>> cmdline(const String& path, Vector<String> args) = 0;
virtual ~BinaryFormatLoader() = default;
BinaryFormatLoader(SharedPtr<VFS::Inode>);
protected:
SharedPtr<VFS::Inode> m_inode;
};
typedef Result<SharedPtr<BinaryFormatLoader>> (*binfmt_loader_creator_t)(SharedPtr<VFS::Inode>, void*);
namespace BinaryFormat
{
Result<void> init();
Result<void> register_binary_format(binfmt_loader_creator_t creator, void* arg);
Result<SharedPtr<BinaryFormatLoader>> create_loader(SharedPtr<VFS::Inode> inode);
}

147
kernel/src/binfmt/ELF.cpp Normal file
View File

@ -0,0 +1,147 @@
#include "binfmt/ELF.h"
#include "Log.h"
#include "arch/CPU.h"
#include "arch/MMU.h"
#include "memory/MemoryManager.h"
#include <luna/Alignment.h>
#include <luna/Alloc.h>
#include <luna/CString.h>
#include <luna/ScopeGuard.h>
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<bool> 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<u64> 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<ARCH_PAGE_SIZE>(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<Vector<String>> ELFLoader::cmdline(const String&, Vector<String> args)
{
return args;
}
ELFLoader::ELFLoader(SharedPtr<VFS::Inode> inode) : BinaryFormatLoader(inode)
{
}
Result<SharedPtr<BinaryFormatLoader>> ELFLoader::create(SharedPtr<VFS::Inode> inode, void*)
{
return (SharedPtr<BinaryFormatLoader>)TRY(make_shared<ELFLoader>(inode));
}

View File

@ -1,6 +1,8 @@
#pragma once
#include "binfmt/BinaryFormat.h"
#include "fs/VFS.h"
#include "memory/AddressSpace.h"
#include <luna/SharedPtr.h>
#include <luna/Types.h>
#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<bool> sniff() override;
Result<u64> load(AddressSpace* space) override;
namespace ELFLoader
Result<Vector<String>> cmdline(const String& path, Vector<String> args) override;
StringView format() const override
{
Result<ELFData> load(SharedPtr<VFS::Inode> inode, AddressSpace* space);
return "elf";
}
ELFLoader(SharedPtr<VFS::Inode> inode);
static Result<SharedPtr<BinaryFormatLoader>> create(SharedPtr<VFS::Inode> inode, void*);
};

View File

@ -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 =

View File

@ -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 <bits/modes.h>
@ -70,9 +70,17 @@ Result<u64> 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();

View File

@ -1,128 +0,0 @@
#include "ELF.h"
#include "Log.h"
#include "arch/CPU.h"
#include "arch/MMU.h"
#include "memory/MemoryManager.h"
#include <luna/Alignment.h>
#include <luna/Alloc.h>
#include <luna/CString.h>
#include <luna/ScopeGuard.h>
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<ELFData> load(SharedPtr<VFS::Inode> 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<ARCH_PAGE_SIZE>(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 };
}
}

View File

@ -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 <luna/Alignment.h>
@ -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));

View File

@ -24,7 +24,7 @@ static Result<void> create_user_stack(Stack& user_stack, AddressSpace* space)
return {};
}
Result<OwnedPtr<ThreadImage>> ThreadImage::try_load_from_elf(SharedPtr<VFS::Inode> inode)
Result<OwnedPtr<ThreadImage>> ThreadImage::try_load_from_binary(SharedPtr<BinaryFormatLoader> loader)
{
auto image = TRY(make_owned<ThreadImage>());
@ -36,7 +36,7 @@ Result<OwnedPtr<ThreadImage>> ThreadImage::try_load_from_elf(SharedPtr<VFS::Inod
auto guard = make_scope_guard([=] { MMU::switch_page_directory(old_directory); });
const ELFData data = TRY(ELFLoader::load(inode, address_space.ptr()));
const u64 entry = TRY(loader->load(address_space.ptr()));
Stack user_stack;
TRY(create_user_stack(user_stack, address_space.ptr()));
@ -44,7 +44,7 @@ Result<OwnedPtr<ThreadImage>> ThreadImage::try_load_from_elf(SharedPtr<VFS::Inod
guard.deactivate();
image->m_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<OwnedPtr<ThreadImage>> 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;

View File

@ -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<OwnedPtr<ThreadImage>> try_load_from_elf(SharedPtr<VFS::Inode> inode);
static Result<OwnedPtr<ThreadImage>> try_load_from_binary(SharedPtr<BinaryFormatLoader> inode);
static Result<OwnedPtr<ThreadImage>> clone_from_thread(Thread* parent);
@ -31,6 +31,6 @@ class ThreadImage
OwnedPtr<AddressSpace> m_address_space;
Stack m_user_stack;
Stack m_kernel_stack;
ELFData m_loaded_image_data;
u64 m_program_entry;
u64 m_sp;
};