[ld.so] Add a minimal dynamic linker

This commit includes a number of changes to enable loading of PIE
executables:

- The loader in srv.init checks for a `PT_INTERP` segment in the program
  its loading, and if it exists, loads the specified interpreter and
  passes control to it instead of the program itself.
- Added ld.so the dynamic linker executable and set it as the
  interpreter for all user-target programs.
- Program initial stack changed again to now contain a number of
  possible tagged structures, including a new one for ld.so's arguments,
  and for passing handles tagged with protocol ids.
- Added a stub for a new VFS protocol. Unused so far, but srv.init will
  need to serve VFS requests from ld.so once I transition libraries to
  shared libs for user-target programs. (Right now all executables are
  PIE but statically linked, so they only need internal relocations.)
- Added 16 and 8 bit variants of `util::bitset`. This ended up not being
  used, but could be useful.
This commit is contained in:
Justin C. Miller
2023-08-12 22:55:37 -07:00
parent 3cfd0cf86b
commit c4bb60299e
21 changed files with 903 additions and 136 deletions

View File

@@ -7,6 +7,7 @@ init = module("srv.init",
ld_script = "init.ld",
sources = [
"acpi.cpp",
"initfs.cpp",
"j6romfs.cpp",
"loader.cpp",
"main.cpp",

View File

@@ -0,0 +1,94 @@
#include <stdint.h>
#include <j6/flags.h>
#include <j6/errors.h>
#include <j6/protocols/vfs.h>
#include <j6/syscalls.h>
#include <j6/syslog.hh>
#include "j6romfs.h"
#include "initfs.h"
static uint64_t initfs_running = 1;
static constexpr size_t buffer_size = 2048;
static constexpr uintptr_t load_addr = 0xf00000000;
j6_status_t
handle_load_request(j6romfs::fs &fs, const char *path, j6_handle_t &vma)
{
const j6romfs::inode *in = fs.lookup_inode(path);
if (!in) {
vma = j6_handle_invalid;
return j6_status_ok;
}
j6_vma_create_map(&vma, in->size, load_addr, j6_vm_flag_write);
util::buffer dest = util::buffer::from(load_addr, in->size);
fs.load_inode_data(in, dest);
j6_vma_unmap(vma, 0);
return j6_err_nyi;
}
void
sanitize(char *s, size_t len)
{
if (len >= buffer_size) len--;
for (size_t i = 0; i < len; ++i)
if (!s || !*s) return;
s[len] = 0;
}
void
initfs_start(j6romfs::fs &fs, j6_handle_t mb)
{
uint64_t tag = 0;
char *buffer = new char [buffer_size];
size_t out_len = buffer_size;
uint64_t reply_tag = 0;
size_t handles_count = 1;
j6_handle_t give_handle = j6_handle_invalid;
uint64_t proto_id;
j6_status_t s = j6_mailbox_respond(mb, &tag,
buffer, &out_len, 0,
&give_handle, &handles_count,
&reply_tag, j6_flag_block);
while (initfs_running) {
if (s != j6_status_ok) {
j6::syslog("initfs: error in j6_mailbox_respond: %x", s);
return;
}
size_t data_out = 0;
switch (tag) {
case j6_proto_vfs_load:
sanitize(buffer, out_len);
s = handle_load_request(fs, buffer, give_handle);
if (s != j6_status_ok) {
tag = j6_proto_base_status;
*reinterpret_cast<j6_status_t*>(buffer) = s;
data_out = sizeof(j6_status_t);
break;
}
tag = j6_proto_vfs_file;
break;
default:
tag = j6_proto_base_status;
*reinterpret_cast<j6_status_t*>(buffer) = j6_err_invalid_arg;
data_out = sizeof(j6_status_t);
give_handle = j6_handle_invalid;
}
out_len = buffer_size;
s = j6_mailbox_respond(mb, &tag,
buffer, &out_len, data_out,
&give_handle, &handles_count,
&reply_tag, j6_flag_block);
}
}

View File

@@ -0,0 +1,10 @@
#pragma once
/// \file initfs.h
/// Temporary VFS service until we can load the real VFS service
#include <j6/types.h>
namespace j6romfs { class fs; }
void initfs_start(j6romfs::fs &fs, j6_handle_t mb);
void initfs_stop();

View File

@@ -6,17 +6,22 @@
#include <elf/headers.h>
#include <j6/errors.h>
#include <j6/flags.h>
#include <j6/init.h>
#include <j6/protocols.h>
#include <j6/syscalls.h>
#include <j6/syslog.hh>
#include <util/enum_bitfields.h>
#include "j6romfs.h"
#include "loader.h"
using bootproto::module;
constexpr uintptr_t load_addr = 0xf8000000;
constexpr size_t stack_size = 0x10000;
constexpr uintptr_t stack_top = 0x80000000000;
static uintptr_t load_addr = 0xf'000'0000;
static constexpr size_t stack_size = 0x10000;
static constexpr uintptr_t stack_top = 0x80000000000;
static constexpr size_t MiB = 0x10'0000ull;
inline uintptr_t align_up(uintptr_t a) { return ((a-1) & ~(MiB-1)) + MiB; }
j6_handle_t
map_phys(j6_handle_t sys, uintptr_t phys, size_t len, j6_vm_flags flags)
@@ -33,125 +38,248 @@ map_phys(j6_handle_t sys, uintptr_t phys, size_t len, j6_vm_flags flags)
return vma;
}
bool
load_program(
const char *name,
util::const_buffer data,
j6_handle_t sys, j6_handle_t slp,
const module *arg)
void
stack_push_sentinel(uint8_t *&stack)
{
uintptr_t base_address = reinterpret_cast<uintptr_t>(data.pointer);
static constexpr size_t size = sizeof(j6_arg_header);
elf::file progelf {data};
bool dyn = progelf.type() == elf::filetype::shared;
uintptr_t image_base = dyn ? 0xa'0000'0000 : 0;
stack -= size;
memset(stack, 0, size);
j6_arg_header *header = reinterpret_cast<j6_arg_header*>(stack);
header->type = j6_arg_type_none;
header->size = 0;
}
if (!progelf.valid(dyn ? elf::filetype::shared : elf::filetype::executable)) {
j6::syslog(" ** error loading program '%s': ELF is invalid", name);
return false;
}
template <typename T> T *
stack_push(uint8_t *&stack, size_t extra)
{
size_t len = sizeof(T) + extra;
size_t size = (len + 7) & ~7ull;
stack -= size;
memset(stack, 0, sizeof(T));
T * arg = reinterpret_cast<T*>(stack);
arg->header.type = T::type_id;
arg->header.size = len;
return arg;
}
j6_handle_t proc = j6_handle_invalid;
j6_status_t res = j6_process_create(&proc);
if (res != j6_status_ok) {
j6::syslog(" ** error loading program '%s': creating process: %lx", name, res);
return false;
}
uintptr_t
load_program_into(j6_handle_t proc, elf::file &file, uintptr_t image_base, const char *path)
{
uintptr_t eop = 0;
res = j6_process_give_handle(proc, sys);
if (res != j6_status_ok) {
j6::syslog(" ** error loading program '%s': giving system handle: %lx", name, res);
return false;
}
res = j6_process_give_handle(proc, slp);
if (res != j6_status_ok) {
j6::syslog(" ** error loading program '%s': giving SLP handle: %lx", name, res);
return false;
}
for (auto &seg : progelf.segments()) {
for (auto &seg : file.segments()) {
if (seg.type != elf::segment_type::load)
continue;
uintptr_t addr = load_addr;
load_addr = align_up(load_addr + seg.mem_size);
// TODO: way to remap VMA as read-only if there's no write flag on
// the segment
unsigned long flags = j6_vm_flag_write;
if (seg.flags && elf::segment_flags::exec)
flags |= j6_vm_flag_exec;
uintptr_t start = base_address + seg.offset;
uintptr_t start = file.base() + seg.offset;
size_t prologue = seg.vaddr & 0xfff;
size_t epilogue = seg.mem_size - seg.file_size;
j6_handle_t sub_vma = j6_handle_invalid;
res = j6_vma_create_map(&sub_vma, seg.mem_size+prologue, load_addr, flags);
j6_status_t res = j6_vma_create_map(&sub_vma, seg.mem_size+prologue, addr, flags);
if (res != j6_status_ok) {
j6::syslog(" ** error loading program '%s': creating sub vma: %lx", name, res);
return false;
j6::syslog(" ** error loading ELF '%s': creating sub vma: %lx", path, res);
return 0;
}
uint8_t *src = reinterpret_cast<uint8_t *>(start);
uint8_t *dest = reinterpret_cast<uint8_t *>(load_addr);
uint8_t *dest = reinterpret_cast<uint8_t *>(addr);
memset(dest, 0, prologue);
memcpy(dest+prologue, src, seg.file_size);
memset(dest+prologue+seg.file_size, 0, epilogue);
res = j6_vma_map(sub_vma, proc, (image_base + seg.vaddr) & ~0xfffull);
uintptr_t eos = image_base + seg.vaddr + seg.mem_size + prologue;
if (eos > eop)
eop = eos;
uintptr_t start_addr = (image_base + seg.vaddr);
j6::syslog("Mapping segment from %s at %012lx - %012lx", path, start_addr, start_addr+seg.mem_size);
res = j6_vma_map(sub_vma, proc, start_addr & ~0xfffull);
if (res != j6_status_ok) {
j6::syslog(" ** error loading program '%s': mapping sub vma to child: %lx", name, res);
return false;
j6::syslog(" ** error loading ELF '%s': mapping sub vma to child: %lx", path, res);
return 0;
}
res = j6_vma_unmap(sub_vma, 0);
if (res != j6_status_ok) {
j6::syslog(" ** error loading program '%s': unmapping sub vma: %lx", name, res);
return false;
j6::syslog(" ** error loading ELF '%s': unmapping sub vma: %lx", path, res);
return 0;
}
}
j6_handle_t stack_vma = j6_handle_invalid;
res = j6_vma_create_map(&stack_vma, stack_size, load_addr, j6_vm_flag_write);
if (res != j6_status_ok) {
j6::syslog(" ** error loading program '%s': creating stack vma: %lx", name, res);
return false;
}
return eop;
}
uint64_t arg0 = 0;
uint64_t *stack = reinterpret_cast<uint64_t*>(load_addr + stack_size);
memset(stack - 512, 0, 512 * sizeof(uint64_t)); // Zero top page
size_t stack_consumed = 0;
if (arg) {
size_t arg_size = arg->bytes - sizeof(module);
const uint8_t *arg_data = arg->data<uint8_t>();
uint8_t *arg_dest = reinterpret_cast<uint8_t*>(stack) - arg_size;
memcpy(arg_dest, arg_data, arg_size);
stack_consumed += arg_size;
arg0 = stack_top - stack_consumed;
}
res = j6_vma_map(stack_vma, proc, stack_top-stack_size);
if (res != j6_status_ok) {
j6::syslog(" ** error loading program '%s': mapping stack vma: %lx", name, res);
return false;
}
res = j6_vma_unmap(stack_vma, 0);
if (res != j6_status_ok) {
j6::syslog(" ** error loading program '%s': unmapping stack vma: %lx", name, res);
return false;
}
j6_handle_t thread = j6_handle_invalid;
res = j6_thread_create(&thread, proc, stack_top - stack_consumed, image_base + progelf.entrypoint(), image_base, arg1);
if (res != j6_status_ok) {
j6::syslog(" ** error loading program '%s': creating thread: %lx", name, res);
return false;
static bool
give_handle(j6_handle_t proc, j6_handle_t h, const char *name)
{
if (h != j6_handle_invalid) {
j6_status_t res = j6_process_give_handle(proc, h);
if (res != j6_status_ok) {
j6::syslog(" ** error loading program: giving %s handle: %lx", name, res);
return false;
}
}
return true;
}
static j6_handle_t
create_process(j6_handle_t sys, j6_handle_t slp, j6_handle_t vfs)
{
j6_handle_t proc = j6_handle_invalid;
j6_status_t res = j6_process_create(&proc);
if (res != j6_status_ok) {
j6::syslog(" ** error loading program: creating process: %lx", res);
return j6_handle_invalid;
}
if (!give_handle(proc, sys, "system")) return j6_handle_invalid;
if (!give_handle(proc, slp, "SLP")) return j6_handle_invalid;
if (!give_handle(proc, vfs, "VFS")) return j6_handle_invalid;
return proc;
}
static util::buffer
load_file(const j6romfs::fs &fs, const char *path)
{
const j6romfs::inode *in = fs.lookup_inode(path);
if (!in || in->type != j6romfs::inode_type::file)
return {};
j6::syslog(" Loading file: %s", path);
uint8_t *data = new uint8_t [in->size];
util::buffer program {data, in->size};
fs.load_inode_data(in, program);
return program;
}
bool
load_program(
const char *path, const j6romfs::fs &fs,
j6_handle_t sys, j6_handle_t slp, j6_handle_t vfs,
const module *arg)
{
j6::syslog("Loading program '%s' into new process", path);
util::buffer program_data = load_file(fs, path);
if (!program_data.pointer)
return false;
elf::file program_elf {program_data};
bool dyn = program_elf.type() == elf::filetype::shared;
uintptr_t program_image_base = dyn ? 0xa00'0000 : 0;
if (!program_elf.valid(dyn ? elf::filetype::shared : elf::filetype::executable)) {
j6::syslog(" ** error loading program '%s': ELF is invalid", path);
return false;
}
j6_handle_t proc = create_process(sys, slp, vfs);
uintptr_t eop = load_program_into(proc, program_elf, program_image_base, path);
if (!eop)
return false;
uintptr_t stack_addr = load_addr;
load_addr = align_up(load_addr + stack_size);
j6_handle_t stack_vma = j6_handle_invalid;
j6_status_t res = j6_vma_create_map(&stack_vma, stack_size, stack_addr, j6_vm_flag_write);
if (res != j6_status_ok) {
j6::syslog(" ** error loading program '%s': creating stack vma: %lx", path, res);
return false;
}
uint8_t *stack_orig = reinterpret_cast<uint8_t*>(stack_addr + stack_size);
uint8_t *stack = stack_orig;
memset(stack - 4096, 0, 4096); // Zero top page
stack_push_sentinel(stack);
if (arg) {
size_t data_size = arg->bytes - sizeof(module);
j6_arg_driver *driver_arg = stack_push<j6_arg_driver>(stack, data_size);
driver_arg->device = arg->type_id;
const uint8_t *arg_data = arg->data<uint8_t>();
memcpy(driver_arg->data, arg_data, data_size);
}
uintptr_t entrypoint = program_elf.entrypoint() + program_image_base;
if (dyn) {
j6_arg_loader *loader_arg = stack_push<j6_arg_loader>(stack, 0);
const elf::file_header *h = program_elf.header();
loader_arg->image_base = program_image_base;
loader_arg->phdr = h->ph_offset;
loader_arg->phdr_size = h->ph_entsize;
loader_arg->phdr_count = h->ph_num;
loader_arg->entrypoint = program_elf.entrypoint();
j6_arg_handles *handles_arg = stack_push<j6_arg_handles>(stack, 2 * sizeof(j6_arg_handle_entry));
handles_arg->nhandles = 1;
handles_arg->handles[0].handle = slp;
handles_arg->handles[0].proto = j6::proto::sl::id;
handles_arg->handles[1].handle = vfs;
handles_arg->handles[1].proto = j6::proto::vfs::id;
uintptr_t ldso_image_base = (eop & ~(MiB-1)) + MiB;
for (auto seg : program_elf.segments()) {
if (seg.type == elf::segment_type::interpreter) {
const char *ldso_path = reinterpret_cast<const char*>(program_elf.base() + seg.offset);
util::buffer ldso_data = load_file(fs, ldso_path);
if (!ldso_data.pointer)
return false;
elf::file ldso_elf {ldso_data};
if (!ldso_elf.valid(elf::filetype::shared)) {
j6::syslog(" ** error loading dynamic linker for program '%s': ELF is invalid", path);
return false;
}
load_program_into(proc, ldso_elf, ldso_image_base, ldso_path);
loader_arg->loader_base = ldso_image_base;
entrypoint = ldso_elf.entrypoint() + ldso_image_base;
delete [] reinterpret_cast<uint8_t*>(ldso_data.pointer);
break;
}
}
}
res = j6_vma_map(stack_vma, proc, stack_top-stack_size);
if (res != j6_status_ok) {
j6::syslog(" ** error loading program '%s': mapping stack vma: %lx", path, res);
return false;
}
res = j6_vma_unmap(stack_vma, 0);
if (res != j6_status_ok) {
j6::syslog(" ** error loading program '%s': unmapping stack vma: %lx", path, res);
return false;
}
j6_handle_t thread = j6_handle_invalid;
ptrdiff_t stack_consumed = stack_orig - stack;
res = j6_thread_create(&thread, proc, stack_top - stack_consumed, entrypoint, program_image_base, 0);
if (res != j6_status_ok) {
j6::syslog(" ** error loading program '%s': creating thread: %lx", path, res);
return false;
}
// TODO: smart pointer this, it's a memory leak if we return early
delete [] reinterpret_cast<uint8_t*>(program_data.pointer);
return true;
}

View File

@@ -10,10 +10,13 @@ namespace bootproto {
struct module;
}
namespace j6romfs {
class fs;
}
bool load_program(
const char *name,
util::const_buffer data,
j6_handle_t sys, j6_handle_t slp,
const char *path, const j6romfs::fs &fs,
j6_handle_t sys, j6_handle_t slp, j6_handle_t vfs,
const bootproto::module *arg = nullptr);
j6_handle_t map_phys(j6_handle_t sys, uintptr_t phys, size_t len, j6_vm_flags flags = j6_vm_flag_none);

View File

@@ -7,6 +7,7 @@
#include <j6/init.h>
#include <j6/syscalls.h>
#include <j6/syslog.hh>
#include <j6/thread.hh>
#include <j6/types.h>
#include <bootproto/acpi.h>
@@ -14,6 +15,7 @@
#include <bootproto/devices/framebuffer.h>
#include "acpi.h"
#include "initfs.h"
#include "j6romfs.h"
#include "loader.h"
#include "modules.h"
@@ -22,30 +24,7 @@
using bootproto::module;
using bootproto::module_type;
void
load_driver(
j6romfs::fs &initrd,
const j6romfs::inode *dir,
const char *name,
j6_handle_t sys,
j6_handle_t slp,
const module *arg = nullptr)
{
const j6romfs::inode *in = initrd.lookup_inode_in_dir(dir, name);
if (in->type != j6romfs::inode_type::file)
return;
j6::syslog("Loading driver: %s", name);
uint8_t *data = new uint8_t [in->size];
util::buffer program {data, in->size};
initrd.load_inode_data(in, program);
load_program(name, program, sys, slp, arg);
delete [] data;
}
constexpr uintptr_t stack_top = 0xf80000000;
int
driver_main(unsigned argc, const char **argv, const char **env, const j6_init_args *initp)
@@ -58,6 +37,9 @@ driver_main(unsigned argc, const char **argv, const char **env, const j6_init_ar
j6_handle_t sys = j6_handle_invalid;
j6_handle_t sys_child = j6_handle_invalid;
j6_handle_t vfs_mb = j6_handle_invalid;
j6_handle_t vfs_mb_child = j6_handle_invalid;
j6_log("srv.init starting");
sys = j6_find_first_handle(j6_object_type_system);
@@ -82,6 +64,16 @@ driver_main(unsigned argc, const char **argv, const char **env, const j6_init_ar
if (s != j6_status_ok)
return s;
s = j6_mailbox_create(&vfs_mb);
if (s != j6_status_ok)
return s;
s = j6_handle_clone(vfs_mb, &vfs_mb_child,
j6_cap_mailbox_send |
j6_cap_object_clone);
if (s != j6_status_ok)
return s;
uintptr_t modules_addr = initp->args[0];
std::vector<const module*> mods;
@@ -130,19 +122,17 @@ driver_main(unsigned argc, const char **argv, const char **env, const j6_init_ar
// have driver_source objects..
j6romfs::fs initrd {initrd_buf};
j6::thread vfs_thread {[=, &initrd](){ initfs_start(initrd, vfs_mb); }, stack_top};
j6_status_t result = vfs_thread.start();
load_acpi(sys, acpi_module);
const j6romfs::inode *driver_dir = initrd.lookup_inode("/jsix/drivers");
if (!driver_dir) {
j6_log("Could not load drivers directory");
return 1;
}
load_program("/jsix/drivers/drv.uart.elf", initrd, sys_child, slp_mb_child, vfs_mb_child);
load_driver(initrd, driver_dir, "drv.uart.elf", sys_child, slp_mb_child);
for (const module *m : devices) {
switch (m->type_id) {
case bootproto::devices::type_id_uefi_fb:
load_driver(initrd, driver_dir, "drv.uefi_fb.elf", sys_child, slp_mb_child, m);
load_program("/jsix/drivers/drv.uefi_fb.elf", initrd, sys_child, slp_mb_child, vfs_mb_child, m);
break;
default:
@@ -155,16 +145,10 @@ driver_main(unsigned argc, const char **argv, const char **env, const j6_init_ar
if (in->type != j6romfs::inode_type::file)
return;
j6::syslog("Loading service: %s", name);
uint8_t *data = new uint8_t [in->size];
util::buffer program {data, in->size};
initrd.load_inode_data(in, program);
load_program(name, program, sys_child, slp_mb_child);
delete [] data;
});
char path [128];
sprintf(path, "/jsix/services/%s", name);
load_program(path, initrd, sys_child, slp_mb_child, vfs_mb_child);
});
service_locator_start(slp_mb);
return 0;