From c4bb60299e7afa5923cd50d2d3c325dc62119f41 Mon Sep 17 00:00:00 2001 From: "Justin C. Miller" Date: Sat, 12 Aug 2023 22:55:37 -0700 Subject: [PATCH] [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. --- assets/build/target.user.exe.yaml | 2 +- assets/build/target.user.yaml | 5 +- assets/manifests/default.yaml | 2 + .../bootproto/include/bootproto/init.h | 1 - src/libraries/j6/include/j6/init.h | 59 ++++ src/libraries/j6/include/j6/protocols/vfs.h | 11 + src/libraries/j6/include/j6/protocols/vfs.hh | 22 ++ src/libraries/j6/j6.module | 1 + src/libraries/j6/protocols/vfs.cpp | 57 ++++ src/libraries/util/include/util/bitset.h | 119 +++++++ src/user/ld.so/ld.so.module | 15 + src/user/ld.so/main.cpp | 70 ++++ src/user/ld.so/relocate.cpp | 155 +++++++++ src/user/ld.so/relocate.h | 7 + src/user/ld.so/start.s | 33 ++ src/user/srv.init/init.module | 1 + src/user/srv.init/initfs.cpp | 94 ++++++ src/user/srv.init/initfs.h | 10 + src/user/srv.init/loader.cpp | 300 +++++++++++++----- src/user/srv.init/loader.h | 9 +- src/user/srv.init/main.cpp | 66 ++-- 21 files changed, 903 insertions(+), 136 deletions(-) create mode 100644 src/libraries/j6/include/j6/protocols/vfs.h create mode 100644 src/libraries/j6/include/j6/protocols/vfs.hh create mode 100644 src/libraries/j6/protocols/vfs.cpp create mode 100644 src/user/ld.so/ld.so.module create mode 100644 src/user/ld.so/main.cpp create mode 100644 src/user/ld.so/relocate.cpp create mode 100644 src/user/ld.so/relocate.h create mode 100644 src/user/ld.so/start.s create mode 100644 src/user/srv.init/initfs.cpp create mode 100644 src/user/srv.init/initfs.h diff --git a/assets/build/target.user.exe.yaml b/assets/build/target.user.exe.yaml index 151b531..00fe79d 100644 --- a/assets/build/target.user.exe.yaml +++ b/assets/build/target.user.exe.yaml @@ -5,6 +5,6 @@ ccflags: [ ldflags: [ "-pie", - "--dynamic-linker", "/tools/ld.so", + "--dynamic-linker", "/jsix/tools/ld.so", "-lc++", "-lc++abi", "-lunwind", ] diff --git a/assets/build/target.user.yaml b/assets/build/target.user.yaml index b385a76..0fdb219 100644 --- a/assets/build/target.user.yaml +++ b/assets/build/target.user.yaml @@ -15,11 +15,8 @@ ccflags: [ "-U__linux__", "--sysroot='${source_root}/sysroot'", - - "-fpic" ] - cxxflags: [ "-fno-exceptions", "-fno-rtti", @@ -32,5 +29,5 @@ ldflags: [ "-L", "${source_root}/sysroot/lib", "-z", "separate-code", "--no-dependent-libraries", + "-Bstatic", ] - diff --git a/assets/manifests/default.yaml b/assets/manifests/default.yaml index 1205c25..8038aaf 100644 --- a/assets/manifests/default.yaml +++ b/assets/manifests/default.yaml @@ -12,3 +12,5 @@ services: drivers: - drv.uart - drv.uefi_fb +tools: + - ld.so \ No newline at end of file diff --git a/src/libraries/bootproto/include/bootproto/init.h b/src/libraries/bootproto/include/bootproto/init.h index bbe8004..6061e68 100644 --- a/src/libraries/bootproto/include/bootproto/init.h +++ b/src/libraries/bootproto/include/bootproto/init.h @@ -6,7 +6,6 @@ #include #include -#include namespace bootproto { diff --git a/src/libraries/j6/include/j6/init.h b/src/libraries/j6/include/j6/init.h index 3f75d29..57b6c43 100644 --- a/src/libraries/j6/include/j6/init.h +++ b/src/libraries/j6/include/j6/init.h @@ -2,17 +2,74 @@ /// \file init.h /// Process initialization utility functions +#include #include #ifdef __cplusplus extern "C" { #endif +#ifdef __cplusplus +#define add_header(name) \ + static constexpr j6_arg_type type_id = j6_arg_type_ ## name; \ + j6_arg_header header; +#else +#define add_header(name) \ + j6_arg_header header; +#endif + +enum j6_arg_type { + j6_arg_type_none, + j6_arg_type_sysv_init, + j6_arg_type_loader, + j6_arg_type_driver, + j6_arg_type_handles, +}; + +struct j6_arg_header +{ + uint32_t size; + uint16_t type; +}; + +struct j6_arg_loader +{ + add_header(loader); + uintptr_t loader_base; + uintptr_t image_base; + uintptr_t phdr; + size_t phdr_size; + size_t phdr_count; + uintptr_t entrypoint; +}; + +struct j6_arg_driver +{ + add_header(driver); + uint64_t device; + uint8_t data [0]; +}; + +struct j6_arg_handle_entry +{ + uint64_t proto; + j6_handle_t handle; +}; + +struct j6_arg_handles +{ + add_header(handles); + size_t nhandles; + j6_arg_handle_entry handles[0]; +}; + struct j6_init_args { uint64_t args[2]; }; + + /// Find the first handle of the given type held by this process j6_handle_t j6_find_first_handle(j6_object_type obj_type); @@ -22,3 +79,5 @@ int driver_main(unsigned, const char **, const char **, const j6_init_args *); #ifdef __cplusplus } // extern "C" #endif + +#undef add_header \ No newline at end of file diff --git a/src/libraries/j6/include/j6/protocols/vfs.h b/src/libraries/j6/include/j6/protocols/vfs.h new file mode 100644 index 0000000..baa4dd8 --- /dev/null +++ b/src/libraries/j6/include/j6/protocols/vfs.h @@ -0,0 +1,11 @@ +#pragma once +/// \file j6/protocols/vfs.h +/// Definitions for the virtual file system protocol + +#include + +enum j6_proto_vfs_tag +{ + j6_proto_vfs_load = j6_proto_base_first_proto_id, + j6_proto_vfs_file, +}; \ No newline at end of file diff --git a/src/libraries/j6/include/j6/protocols/vfs.hh b/src/libraries/j6/include/j6/protocols/vfs.hh new file mode 100644 index 0000000..8cf73bb --- /dev/null +++ b/src/libraries/j6/include/j6/protocols/vfs.hh @@ -0,0 +1,22 @@ +#include +#include + +namespace j6::proto::vfs { + +class client +{ +public: + /// Constructor. + /// \arg vfs_mb Handle to the VFS service's mailbox + client(j6_handle_t vfs_mb); + + /// Load a file into a VMA + /// \arg path Path of the file to load + /// \arg vma [out] Handle to the loaded VMA, or invalid if not found + j6_status_t load_file(char *path, j6_handle_t &vma); + +private: + j6_handle_t m_service; +}; + +} // namespace j6::proto::vfs \ No newline at end of file diff --git a/src/libraries/j6/j6.module b/src/libraries/j6/j6.module index 7be0e93..db84f98 100644 --- a/src/libraries/j6/j6.module +++ b/src/libraries/j6/j6.module @@ -12,6 +12,7 @@ j6 = module("j6", "mutex.cpp", "protocol_ids.cpp", "protocols/service_locator.cpp", + "protocols/vfs.cpp", "syscalls.s.cog", "sysconf.cpp.cog", "syslog.cpp", diff --git a/src/libraries/j6/protocols/vfs.cpp b/src/libraries/j6/protocols/vfs.cpp new file mode 100644 index 0000000..b34bd0f --- /dev/null +++ b/src/libraries/j6/protocols/vfs.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include + +#ifndef __j6kernel + +namespace j6::proto::vfs { + +client::client(j6_handle_t vfs_mb) : + m_service {vfs_mb} +{ +} + +inline size_t simple_strlen(const char *s) { size_t n = 0; while (s && *s) n++; return n; } + +j6_status_t +client::load_file(char *path, j6_handle_t &vma) +{ + if (!path) + return j6_err_invalid_arg; + + uint64_t tag = j6_proto_vfs_load; + size_t handle_count = 1; + vma = j6_handle_invalid; + + // Always need to send a big enough buffer for a status code + size_t path_len = simple_strlen(path); + size_t data_len = path_len; + char *data = path; + j6_status_t alternate = 0; + if (path_len < sizeof(alternate)) { + data = reinterpret_cast(alternate); + for (unsigned i = 0; i < path_len; ++i) + data[i] = path[i]; + data_len = sizeof(alternate); + } + + j6_status_t s = j6_mailbox_call(m_service, &tag, + data, &data_len, path_len, + &vma, &handle_count); + + if (s != j6_status_ok) + return s; + + if (tag == j6_proto_vfs_file) + return j6_status_ok; // handle is already in `vma` + + else if (tag == j6_proto_base_status) + return *reinterpret_cast(data); // contains a status + + return j6_err_unexpected; +} + + +} // namespace j6::proto::vfs +#endif // __j6kernel diff --git a/src/libraries/util/include/util/bitset.h b/src/libraries/util/include/util/bitset.h index 27a2d34..2788777 100644 --- a/src/libraries/util/include/util/bitset.h +++ b/src/libraries/util/include/util/bitset.h @@ -179,7 +179,126 @@ private: uint32_t m_bits; }; +/// A statically-sized templated bitset +template <> +class bitset<16> +{ + template + static constexpr uint16_t bit_or(T b) { return 1u << uint16_t(b); } + + template + static constexpr uint16_t bit_or(T b, Args... bs) { return (1u << uint16_t(b)) | bit_or(bs...); } + +public: + bitset(uint16_t v = 0) : m_bits {v} {} + + bitset(const bitset<16> &o) : m_bits {o.m_bits} {} + + template + constexpr bitset(Args... args) : m_bits(bit_or(args...)) {} + + template + inline bitset & operator=(T v) { m_bits = static_cast(v); return *this; } + + inline constexpr operator uint16_t () const { return m_bits; } + + template + __attribute__ ((force_inline)) + inline constexpr bool get(T i) const { + return m_bits & bit(i); + } + + template + __attribute__ ((force_inline)) + inline bitset & set(T i) { + m_bits |= bit(i); + return *this; + } + + template + __attribute__ ((force_inline)) + inline bitset & clear(T i) { + m_bits &= ~bit(i); + return *this; + } + + template + __attribute__ ((force_inline)) + inline bool operator[](T i) const { return get(i); } + + inline bool empty() const { return m_bits == 0; } + + inline constexpr uint16_t value() const { return m_bits; } + +private: + template + inline uint16_t bit(T i) const { return (1u << static_cast(i)); } + + uint16_t m_bits; +}; + + +/// A statically-sized templated bitset +template <> +class bitset<8> +{ + template + static constexpr uint8_t bit_or(T b) { return 1u << uint8_t(b); } + + template + static constexpr uint8_t bit_or(T b, Args... bs) { return (1u << uint8_t(b)) | bit_or(bs...); } + +public: + bitset(uint8_t v = 0) : m_bits {v} {} + + bitset(const bitset<8> &o) : m_bits {o.m_bits} {} + + template + constexpr bitset(Args... args) : m_bits(bit_or(args...)) {} + + template + inline bitset & operator=(T v) { m_bits = static_cast(v); return *this; } + + inline constexpr operator uint8_t () const { return m_bits; } + + template + __attribute__ ((force_inline)) + inline constexpr bool get(T i) const { + return m_bits & bit(i); + } + + template + __attribute__ ((force_inline)) + inline bitset & set(T i) { + m_bits |= bit(i); + return *this; + } + + template + __attribute__ ((force_inline)) + inline bitset & clear(T i) { + m_bits &= ~bit(i); + return *this; + } + + template + __attribute__ ((force_inline)) + inline bool operator[](T i) const { return get(i); } + + inline bool empty() const { return m_bits == 0; } + + inline constexpr uint8_t value() const { return m_bits; } + +private: + template + inline uint8_t bit(T i) const { return (1u << static_cast(i)); } + + uint8_t m_bits; +}; + using bitset64 = bitset<64>; using bitset32 = bitset<32>; +using bitset16 = bitset<16>; +using bitset8 = bitset<8>; } // namespace util diff --git a/src/user/ld.so/ld.so.module b/src/user/ld.so/ld.so.module new file mode 100644 index 0000000..a0ab69d --- /dev/null +++ b/src/user/ld.so/ld.so.module @@ -0,0 +1,15 @@ +# vim: ft=python + +ldso = module("ld.so", + kind = "shared", + output = "ld.so", + targets = [ "user" ], + deps = [ "libc", "util", "elf" ], + description = "Dynamic Linker", + sources = [ + "main.cpp", + "relocate.cpp", + "start.s", + ]) + +ldso.variables["ldflags"] = ["${ldflags}", "--entry=_ldso_start"] \ No newline at end of file diff --git a/src/user/ld.so/main.cpp b/src/user/ld.so/main.cpp new file mode 100644 index 0000000..01c3b8e --- /dev/null +++ b/src/user/ld.so/main.cpp @@ -0,0 +1,70 @@ +#include +#include + +#include +#include +#include +#include +#include "relocate.h" + +uintptr_t +locate_dyn_section(uintptr_t base, uintptr_t phdr, size_t phdr_size, size_t phdr_count) +{ + if (!phdr || !phdr_count) + return 0; + + const elf::segment_header *ph_base = + reinterpret_cast(phdr + base); + + for (size_t i = 0; i < phdr_count; ++i) { + const elf::segment_header *ph = + util::offset_pointer(ph_base, i*phdr_size); + + if (ph->type == elf::segment_type::dynamic) + return ph->vaddr; + } + + return 0; +} + +extern "C" uintptr_t +ldso_init(j6_arg_header *stack_args, uintptr_t const *got) +{ + j6_arg_loader *arg_loader = nullptr; + j6_arg_handles *arg_handles = nullptr; + + j6_arg_header *arg = stack_args; + while (arg && arg->type != j6_arg_type_none) { + switch (arg->type) + { + case j6_arg_type_loader: + arg_loader = reinterpret_cast(arg); + break; + + case j6_arg_type_handles: + arg_handles = reinterpret_cast(arg); + break; + + default: + break; + } + + arg = util::offset_pointer(arg, arg->size); + } + + if (!arg_loader) { + j6::syslog("ld.so: error: did not get j6_arg_loader"); + exit(127); + } + + relocate_image(arg_loader->loader_base, got[0]); + + uintptr_t dyn_section = locate_dyn_section( + arg_loader->image_base, + arg_loader->phdr, + arg_loader->phdr_size, + arg_loader->phdr_count); + relocate_image(arg_loader->image_base, dyn_section); + + return arg_loader->entrypoint + arg_loader->image_base; +} \ No newline at end of file diff --git a/src/user/ld.so/relocate.cpp b/src/user/ld.so/relocate.cpp new file mode 100644 index 0000000..8bf1c03 --- /dev/null +++ b/src/user/ld.so/relocate.cpp @@ -0,0 +1,155 @@ +#include +#include +#include +#include "relocate.h" + +enum class dyn_type : uint64_t { + null, needed, pltrelsz, pltgot, hash, strtab, symtab, rela, relasz, relaent, + strsz, syment, init, fini, soname, rpath, symbolic, rel, relsz, relent, pltrel, + debug, textrel, jmprel, bind_now, init_array, fini_array, init_arraysz, fini_arraysz, + gnu_hash = 0x6ffffef5, relacount = 0x6ffffff9, +}; + +struct dyn_entry { + dyn_type tag; + uintptr_t value; +}; + +struct dyn_values { + uintptr_t pltrelsz; + uintptr_t pltgot; + uintptr_t hash; + uintptr_t pltrel; + uintptr_t strtab; + uintptr_t symtab; + uintptr_t rela; + uintptr_t relasz; + uintptr_t relaent; + uintptr_t strsz; + uintptr_t syment; + uintptr_t jmprel; + uintptr_t relacount; +}; + +#define read_dyn_value(name) \ + case dyn_type::name: \ + values.name = dyn.value; \ + break; + +void +read_dynamic_values(uintptr_t base, uintptr_t dyn_section, dyn_values &values) +{ + const dyn_entry *dyns = reinterpret_cast(dyn_section + base); + + unsigned i = 0; + while (true) { + const dyn_entry &dyn = dyns[i++]; + switch (dyn.tag) { + case dyn_type::null: + return; + + read_dyn_value(pltrelsz); + read_dyn_value(pltgot); + read_dyn_value(pltrel); + read_dyn_value(strtab); + read_dyn_value(symtab); + read_dyn_value(rela); + read_dyn_value(relasz); + read_dyn_value(relaent); + read_dyn_value(strsz); + read_dyn_value(syment); + read_dyn_value(jmprel); + read_dyn_value(relacount); + + case dyn_type::gnu_hash: + values.hash = dyn.value; + break; + + default: + break; + } + } +} + +#undef read_dyn_value(name) + +class string_table : + private util::counted +{ +public: + string_table(const char *start, size_t size) : + util::counted {start, size} {} + + const char *lookup(size_t offset) { + if (offset > count) return nullptr; + return pointer + offset; + } +}; + +struct symbol +{ + uint32_t name; + uint8_t type : 4; + uint8_t binding : 4; + uint8_t _reserved0; + uint16_t section; + uintptr_t address; + size_t size; +}; + +struct gnu_hash_table +{ + uint32_t bucket_count; + uint32_t start_symbol; + uint32_t bloom_count; + uint32_t bloom_shift; + uint64_t bloom [0]; +}; + +enum class reloc : uint32_t { + relative = 8, +}; + +struct rela +{ + uintptr_t address; + reloc type; + uint32_t symbol; + ptrdiff_t offset; +}; + +template T off(uintptr_t p, uintptr_t base) { return reinterpret_cast(p ? p + base : 0); } + +void +relocate_image(uintptr_t base, uintptr_t dyn_section) +{ + dyn_values values = {0}; + read_dynamic_values(base, dyn_section, values); + + string_table strtab { + reinterpret_cast(values.strtab + base), + values.strsz, + }; + + symbol *symtab = off(values.symtab, base); + gnu_hash_table *hashtab = off(values.hash, base); + + uintptr_t *jmprel = off(values.jmprel, base); + uintptr_t *plt = off(values.pltgot, base); + + size_t nrela = values.relacount; + for (size_t i = 0; i < values.relacount; ++i) { + rela *rel = off(values.rela + i * values.relaent, base); + switch (rel->type) + { + case reloc::relative: + *reinterpret_cast(rel->address + base) = base + rel->offset; + break; + + default: + j6::syslog("Unknown relocation type %d", rel->type); + exit(126); + break; + } + } +} \ No newline at end of file diff --git a/src/user/ld.so/relocate.h b/src/user/ld.so/relocate.h new file mode 100644 index 0000000..30d9003 --- /dev/null +++ b/src/user/ld.so/relocate.h @@ -0,0 +1,7 @@ +#pragma once +/// \file relocate.h +/// Image relocation services + +#include + +void relocate_image(uintptr_t base, uintptr_t dyn_section); \ No newline at end of file diff --git a/src/user/ld.so/start.s b/src/user/ld.so/start.s new file mode 100644 index 0000000..bbc2c77 --- /dev/null +++ b/src/user/ld.so/start.s @@ -0,0 +1,33 @@ +extern ldso_init +extern _GLOBAL_OFFSET_TABLE_ + +global _ldso_start:function hidden (_ldso_start.end - _ldso_start) +_ldso_start: + mov rbp, rsp + + ; Save off anything that might be a function arg + push rdi + push rsi + push rdx + push rcx + push r8 + push r9 + + ; Call ldso_init with the loader-provided stack data and + ; also the address of the GOT, since clang refuses to take + ; the address of it, only dereference it. + mov rdi, rbp + lea rsi, [rel _GLOBAL_OFFSET_TABLE_] + call ldso_init + ; The real program's entrypoint is now in rax + + ; Put the function call params back + pop r9 + pop r8 + pop rcx + pop rdx + pop rsi + pop rdi + + jmp rax +.end: \ No newline at end of file diff --git a/src/user/srv.init/init.module b/src/user/srv.init/init.module index 84847f3..9626f0e 100644 --- a/src/user/srv.init/init.module +++ b/src/user/srv.init/init.module @@ -7,6 +7,7 @@ init = module("srv.init", ld_script = "init.ld", sources = [ "acpi.cpp", + "initfs.cpp", "j6romfs.cpp", "loader.cpp", "main.cpp", diff --git a/src/user/srv.init/initfs.cpp b/src/user/srv.init/initfs.cpp new file mode 100644 index 0000000..104f0be --- /dev/null +++ b/src/user/srv.init/initfs.cpp @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include +#include + +#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(buffer) = s; + data_out = sizeof(j6_status_t); + break; + } + tag = j6_proto_vfs_file; + break; + + default: + tag = j6_proto_base_status; + *reinterpret_cast(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); + } +} diff --git a/src/user/srv.init/initfs.h b/src/user/srv.init/initfs.h new file mode 100644 index 0000000..12a772d --- /dev/null +++ b/src/user/srv.init/initfs.h @@ -0,0 +1,10 @@ +#pragma once +/// \file initfs.h +/// Temporary VFS service until we can load the real VFS service + +#include + +namespace j6romfs { class fs; } + +void initfs_start(j6romfs::fs &fs, j6_handle_t mb); +void initfs_stop(); \ No newline at end of file diff --git a/src/user/srv.init/loader.cpp b/src/user/srv.init/loader.cpp index 0e9c91f..5f00080 100644 --- a/src/user/srv.init/loader.cpp +++ b/src/user/srv.init/loader.cpp @@ -6,17 +6,22 @@ #include #include #include +#include +#include #include #include -#include +#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(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(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 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(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(start); - uint8_t *dest = reinterpret_cast(load_addr); + uint8_t *dest = reinterpret_cast(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(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 *arg_dest = reinterpret_cast(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(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(stack, data_size); + driver_arg->device = arg->type_id; + + const uint8_t *arg_data = arg->data(); + 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(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(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(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(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(program_data.pointer); + return true; +} + diff --git a/src/user/srv.init/loader.h b/src/user/srv.init/loader.h index e254192..ef52e27 100644 --- a/src/user/srv.init/loader.h +++ b/src/user/srv.init/loader.h @@ -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); diff --git a/src/user/srv.init/main.cpp b/src/user/srv.init/main.cpp index c6b5c70..67a3eb6 100644 --- a/src/user/srv.init/main.cpp +++ b/src/user/srv.init/main.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -14,6 +15,7 @@ #include #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 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;