[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

@@ -6,7 +6,6 @@
#include <stdint.h>
#include <util/counted.h>
#include <util/enum_bitfields.h>
namespace bootproto {

View File

@@ -2,17 +2,74 @@
/// \file init.h
/// Process initialization utility functions
#include <stdint.h>
#include <j6/types.h>
#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

View File

@@ -0,0 +1,11 @@
#pragma once
/// \file j6/protocols/vfs.h
/// Definitions for the virtual file system protocol
#include <j6/protocols.h>
enum j6_proto_vfs_tag
{
j6_proto_vfs_load = j6_proto_base_first_proto_id,
j6_proto_vfs_file,
};

View File

@@ -0,0 +1,22 @@
#include <j6/protocols/vfs.h>
#include <j6/types.h>
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

View File

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

View File

@@ -0,0 +1,57 @@
#include <j6/errors.h>
#include <j6/protocols/vfs.hh>
#include <j6/syscalls.h>
#include <j6/syslog.hh>
#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<char*>(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<j6_status_t*>(data); // contains a status
return j6_err_unexpected;
}
} // namespace j6::proto::vfs
#endif // __j6kernel

View File

@@ -179,7 +179,126 @@ private:
uint32_t m_bits;
};
/// A statically-sized templated bitset
template <>
class bitset<16>
{
template <typename T>
static constexpr uint16_t bit_or(T b) { return 1u << uint16_t(b); }
template <typename T, typename ...Args>
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 <typename ...Args>
constexpr bitset(Args... args) : m_bits(bit_or(args...)) {}
template <typename T>
inline bitset & operator=(T v) { m_bits = static_cast<uint16_t>(v); return *this; }
inline constexpr operator uint16_t () const { return m_bits; }
template <typename T>
__attribute__ ((force_inline))
inline constexpr bool get(T i) const {
return m_bits & bit(i);
}
template <typename T>
__attribute__ ((force_inline))
inline bitset & set(T i) {
m_bits |= bit(i);
return *this;
}
template <typename T>
__attribute__ ((force_inline))
inline bitset & clear(T i) {
m_bits &= ~bit(i);
return *this;
}
template <typename T>
__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 <typename T>
inline uint16_t bit(T i) const { return (1u << static_cast<uint16_t>(i)); }
uint16_t m_bits;
};
/// A statically-sized templated bitset
template <>
class bitset<8>
{
template <typename T>
static constexpr uint8_t bit_or(T b) { return 1u << uint8_t(b); }
template <typename T, typename ...Args>
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 <typename ...Args>
constexpr bitset(Args... args) : m_bits(bit_or(args...)) {}
template <typename T>
inline bitset & operator=(T v) { m_bits = static_cast<uint8_t>(v); return *this; }
inline constexpr operator uint8_t () const { return m_bits; }
template <typename T>
__attribute__ ((force_inline))
inline constexpr bool get(T i) const {
return m_bits & bit(i);
}
template <typename T>
__attribute__ ((force_inline))
inline bitset & set(T i) {
m_bits |= bit(i);
return *this;
}
template <typename T>
__attribute__ ((force_inline))
inline bitset & clear(T i) {
m_bits &= ~bit(i);
return *this;
}
template <typename T>
__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 <typename T>
inline uint8_t bit(T i) const { return (1u << static_cast<uint8_t>(i)); }
uint8_t m_bits;
};
using bitset64 = bitset<64>;
using bitset32 = bitset<32>;
using bitset16 = bitset<16>;
using bitset8 = bitset<8>;
} // namespace util