proc/dyn: proof-of-concept dynamic executable loader

This commit is contained in:
Mark Poliakov 2024-03-20 21:21:39 +02:00
parent 0fbd77ab20
commit 35a44a8ca1
29 changed files with 1154 additions and 50 deletions

View File

@ -5,6 +5,7 @@ exclude = [
"boot/yboot-proto",
"tool/abi-generator",
"toolchain",
"userspace/dynload-program"
]
members = [
"xtask",

View File

@ -9,7 +9,6 @@ SECTIONS {
PROVIDE(__kernel_start = . + KERNEL_VIRT_OFFSET);
.text.entry : {
*(.multiboot)
*(.text.entry)
}

View File

@ -10,7 +10,7 @@ use libk_mm::{
table::MapAttributes,
};
use libk_util::io::{Read, Seek};
use yggdrasil_abi::{error::Error, io::SeekFrom};
use yggdrasil_abi::{error::Error, io::SeekFrom, path::PathBuf};
use crate::{
random,
@ -29,7 +29,7 @@ cfg_if! {
}
#[derive(Clone)]
struct FileReader<F: Read + Seek> {
pub struct FileReader<F: Read + Seek> {
file: F,
}
@ -40,6 +40,11 @@ enum ElfSegmentType {
Tls,
}
pub enum ElfKind<F: Read + Seek> {
Dynamic(Option<PathBuf>),
Static(ElfStream<AnyEndian, FileReader<Arc<F>>>, FileReader<Arc<F>>),
}
struct ElfSegment {
pub ty: ElfSegmentType,
pub attrs: MapAttributes,
@ -115,11 +120,8 @@ impl ElfSegment {
Some((self.aligned_start(), self.aligned_end()))
}
}
/// Loads an ELF binary from `file` into the target address space
pub fn load_elf_from_file<F: Read + Seek>(
space: &ProcessAddressSpace,
file: Arc<F>,
) -> Result<ProcessImage, Error> {
pub fn open_elf_file<F: Read + Seek>(file: Arc<F>) -> Result<ElfKind<F>, Error> {
let file = FileReader { file };
let mut elf = ElfStream::<AnyEndian, _>::open_stream(file.clone()).map_err(from_parse_error)?;
@ -139,6 +141,20 @@ pub fn load_elf_from_file<F: Read + Seek>(
return Err(Error::UnrecognizedExecutable);
}
if is_dynamic(&mut elf)? {
// TODO populate interp value
Ok(ElfKind::Dynamic(None))
} else {
Ok(ElfKind::Static(elf, file))
}
}
/// Loads an ELF binary from `file` into the target address space
pub fn load_elf_from_file<F: Read + Seek>(
space: &ProcessAddressSpace,
mut elf: ElfStream<AnyEndian, FileReader<F>>,
file: FileReader<F>,
) -> Result<ProcessImage, Error> {
// TODO get information about interpreter: this could be a dynamic (not just relocatable)
// executable with required dependency libraries
@ -223,6 +239,22 @@ pub fn clone_tls(space: &ProcessAddressSpace, image: &ProcessImage) -> Result<us
Ok(address + tls.layout.ptr_offset)
}
fn is_dynamic<F: Read + Seek>(
elf: &mut ElfStream<AnyEndian, FileReader<F>>,
) -> Result<bool, Error> {
if elf.ehdr.e_type != elf::abi::ET_DYN {
return Ok(false);
}
let dynamic = elf.dynamic().map_err(from_parse_error)?;
let Some(dynamic) = dynamic else {
// No .dynamic section -> not dynamic
return Ok(false);
};
Ok(dynamic.into_iter().any(|e| e.d_tag == elf::abi::DT_NEEDED))
}
fn elf_virtual_range<F: Read + Seek>(elf: &ElfStream<AnyEndian, FileReader<F>>) -> (usize, usize) {
let mut vaddr_min = usize::MAX;
let mut vaddr_max = usize::MIN;

View File

@ -31,6 +31,8 @@ use crate::{
vfs::IoContext,
};
use self::elf::ElfKind;
pub mod elf;
pub type LoadedProcess = (Arc<Process>, Arc<Thread>);
@ -190,17 +192,17 @@ where
Ok((process, main))
}
fn load_binary<F: Read + Seek>(
head: &[u8],
file: Arc<F>,
space: &ProcessAddressSpace,
) -> Result<ProcessImage, Error> {
if head.starts_with(b"\x7FELF") {
elf::load_elf_from_file(space, file)
} else {
Err(Error::UnrecognizedExecutable)
}
}
// fn load_binary<F: Read + Seek>(
// head: &[u8],
// file: Arc<F>,
// space: &ProcessAddressSpace,
// ) -> Result<ProcessImage, Error> {
// if head.starts_with(b"\x7FELF") {
// elf::load_elf_from_file(space, file)
// } else {
// Err(Error::UnrecognizedExecutable)
// }
// }
fn xxx_load_program<P: AsRef<Path>>(
space: &ProcessAddressSpace,
@ -217,6 +219,7 @@ fn xxx_load_program<P: AsRef<Path>>(
let count = file.read(&mut head)?;
let head = &head[..count];
// Recognize #!
if let Some(shebang) = head.strip_prefix(b"#!")
&& let Some((shebang, _)) = shebang.split_once(|&ch| ch == b'\n')
{
@ -232,9 +235,28 @@ fn xxx_load_program<P: AsRef<Path>>(
file.seek(SeekFrom::Start(0))?;
let image = load_binary(head, file, space)?;
if head.starts_with(b"\x7FELF") {
match elf::open_elf_file(file)? {
ElfKind::Static(elf, file) => {
let image = elf::load_elf_from_file(space, elf, file)?;
Ok((image, args, envs))
Ok((image, args, envs))
}
ElfKind::Dynamic(interp) => {
let interp = interp.unwrap_or_else(|| "/libexec/dyn-loader".into());
let mut interp_args = Vec::new();
interp_args.push(interp.as_str().into());
interp_args.extend_from_slice(&args);
xxx_load_program(space, ioctx, interp, interp_args, envs)
}
}
} else {
Err(Error::UnrecognizedExecutable)
}
// let image = load_binary(head, file, space)?;
// Ok((image, args, envs))
}
/// Loads a program from given `path`

View File

@ -170,6 +170,8 @@ fn user_exception_inner(kind: ExceptionKind, frame: &mut ExceptionFrame) {
warnln!("CR3 = {:#x}", cr3);
}
log::warn!("{:#x?}", frame);
match kind {
ExceptionKind::Debug => {
// TODO check if the thread was really in single-step mode or has debugging related to

View File

@ -57,7 +57,7 @@ pub(crate) fn unmap_memory(address: usize, len: usize) -> Result<(), Error> {
let space = thread.address_space();
if len & 0xFFF != 0 {
todo!();
return Err(Error::InvalidArgument);
}
unsafe {

View File

@ -130,4 +130,4 @@ syscall execve(options: &ExecveOptions<'_>) -> Result<()>;
// Debugging
syscall debug_trace(message: &str);
syscall debug_control(pid: ProcessId, op: &DebugOperation) -> Result<()>;
syscall debug_control(pid: ProcessId, op: &DebugOperation<'_>) -> Result<()>;

View File

@ -1,2 +1,2 @@
/target
/extra.sh
/dynload-program/target

33
userspace/Cargo.lock generated
View File

@ -8,7 +8,7 @@ version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.53",
"thiserror",
]
@ -84,9 +84,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.2"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
[[package]]
name = "block-buffer"
@ -114,7 +114,7 @@ checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.53",
]
[[package]]
@ -175,7 +175,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.53",
]
[[package]]
@ -218,7 +218,7 @@ version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.5.0",
"crossterm_winapi",
"libc",
"mio",
@ -266,6 +266,15 @@ dependencies = [
"crypto-common",
]
[[package]]
name = "dyn-loader"
version = "0.1.0"
dependencies = [
"elf",
"thiserror",
"yggdrasil-rt",
]
[[package]]
name = "elf"
version = "0.7.4"
@ -633,7 +642,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5"
dependencies = [
"proc-macro2",
"syn 2.0.52",
"syn 2.0.53",
]
[[package]]
@ -786,7 +795,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.53",
]
[[package]]
@ -884,9 +893,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.52"
version = "2.0.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032"
dependencies = [
"proc-macro2",
"quote",
@ -948,7 +957,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.53",
]
[[package]]
@ -1286,5 +1295,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
"syn 2.0.53",
]

View File

@ -11,5 +11,8 @@ members = [
"lib/serde-ipc",
"lib/libterm",
"netutils",
"netutils",
"dyn-loader",
"rdb"
]
exclude = ["dynload-program"]

View File

@ -0,0 +1,9 @@
[package]
name = "dyn-loader"
version = "0.1.0"
edition = "2021"
[dependencies]
yggdrasil-rt = { path = "../../lib/runtime" }
elf = "0.7.4"
thiserror = "1.0.58"

View File

@ -0,0 +1,33 @@
use crate::STATE;
#[allow(non_camel_case_types)]
#[repr(C)]
pub struct tls_index {
dtpmod: usize,
dtpoff: usize,
}
pub unsafe extern "C" fn __dl_tls_get_addr(index: *const tls_index) -> usize {
let state = STATE.as_ref().unwrap();
let dtpmod = (*index).dtpmod;
let dtpoff = (*index).dtpoff;
assert_ne!(dtpmod, 0);
state.tls_address(dtpmod).expect("__tls_get_addr: failed") + dtpoff
}
#[cfg(any(target_arch = "aarch64", rust_analyzer))]
std::arch::global_asm!(
r#"
.global __dl_tlsdesc_static
__dl_tlsdesc_static:
// x0 -- ptr to two words
ldr x0, [x0, #8]
ret
"#
);
extern "C" {
pub fn __dl_tlsdesc_static(value: &[usize; 2]) -> usize;
}

View File

@ -0,0 +1,25 @@
use std::{io, path::PathBuf};
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("I/O error: {0}")]
IoError(#[from] io::Error),
#[error("ELF parse error: {0}")]
ElfParseError(#[from] elf::ParseError),
#[error("Could not map memory ({0} bytes): {1:?}")]
MapError(usize, yggdrasil_rt::Error),
#[error("Could not locate library: {0}")]
LibraryNotFound(String),
#[error("Cannot perform the operation: object is not loaded yet")]
ObjectNotLoaded,
#[error("Undefined reference(s)")]
UndefinedReference,
#[error("Unable to perform relocation")]
CannotRelocate,
#[error("Unsupported relocation type: {0}")]
UnsupportedRelocation(u32),
#[error("{0:?} is missing a dynamic symbol table")]
MissingDynamicSymbolTable(PathBuf),
#[error("No entry point in loaded object: not an executable?")]
NoEntryPoint,
}

View File

@ -0,0 +1,165 @@
#![feature(yggdrasil_os)]
use std::{
collections::HashMap,
env,
path::{Path, PathBuf},
process::ExitCode,
};
use error::Error;
use state::State;
use yggdrasil_rt::process::ProgramArgumentInner;
use crate::object::Object;
mod builtins;
mod error;
mod object;
mod relocation;
mod state;
struct Config {
library_path: Vec<PathBuf>,
trace_libraries: bool,
}
impl Config {
pub fn from_env() -> Result<Self, Error> {
let library_path = if let Ok(paths) = env::var("LD_LIBRARY_PATH") {
paths.split(':').map(PathBuf::from).collect()
} else {
vec!["/lib".into()]
};
let trace_libraries = env::var("LD_TRACE_LOADED_OBJECTS")
.map(|v| v == "1")
.unwrap_or(false);
Ok(Self {
library_path,
trace_libraries,
})
}
}
static mut STATE: Option<State> = None;
fn resolve_library(config: &Config, name: &str) -> Result<PathBuf, Error> {
// TODO temporary hack for hash-tagged libstd
if name.starts_with("libstd-") && name.ends_with(".so") {
return resolve_library(config, "libstd.so");
}
for lib_dir in config.library_path.iter() {
let path = lib_dir.join(name);
if path.exists() {
return Ok(path);
}
}
Err(Error::LibraryNotFound(name.into()))
}
fn run<P: AsRef<Path>>(path: P, args: &[String]) -> Result<(), Error> {
let mut state = State::default();
let mut libs = HashMap::new();
let config = Config::from_env()?;
state.insert_linker_builtins();
// Open and load the main object
let mut main_object = Object::open(path)?;
main_object.load(&mut state)?;
main_object.collect_dependencies(&mut libs, &|name| resolve_library(&config, name))?;
if config.trace_libraries {
println!("Main object: {}", main_object.path.display());
for (item, _) in libs.iter() {
println!("* {}", item.display());
}
return Ok(());
}
// Load the libraries first
for (_, lib) in libs.iter_mut() {
lib.load(&mut state)?;
}
// Relocate the libraries
for (_, lib) in libs.iter_mut() {
lib.relocate(&mut state)?;
}
// Then relocate the main object
main_object.relocate(&mut state)?;
if !state.undefined_references.is_empty() {
for item in state.undefined_references.iter() {
eprintln!("Undefined reference to {:?}", item);
}
return Err(Error::UndefinedReference);
}
let args = args.into_iter().map(|s| s.as_str()).collect::<Vec<_>>();
// TODO pass environment to the program
let envs = vec![];
let arg = Box::new(ProgramArgumentInner {
args: &args,
env: &envs,
});
let entry = main_object.entry()?.ok_or(Error::NoEntryPoint)?;
// Store object as linker's global state and enter the program
unsafe {
STATE = Some(state);
// FIXME TLS relocation and allocation is really broken for aarch64, so this hack ensures
// code doesn't try to add the TLS provided by the kernel to this linker to whatever
// is returned from __dl_tlsdesc_static.
//
// This breaks TLS for the loader itself, but at least allows proper relocations
// against local TLS data to be performed directly without having to go and collect
// all TLS locals into one single blob.
#[cfg(target_arch = "aarch64")]
std::arch::asm!("msr tpidr_el0, xzr");
entry(Box::leak(arg) as *mut _ as usize);
}
}
fn main() -> ExitCode {
let args: Vec<_> = env::args().collect();
assert_ne!(args.len(), 0);
if args.len() == 1 {
eprintln!(
"{}\n\n{} PROGRAM [ARGS...]",
r#"
This program is the Yggdrasil OS dynamic executable linker/loader.
It is not meant to be called directly without arguments, but to be
used as an interpreter for dynamically-linked programs instead.
If needed, the program can still be invoked like follows:
"#
.trim(),
args[0]
);
return ExitCode::SUCCESS;
};
let args = &args[1..];
let program = PathBuf::from(&args[0]);
match run(program, &args) {
// Normal execution doesn't return here, but if LD_TRACE_LOADED_OBJECTS is set,
// the loader will exit after printing everything
Ok(()) => ExitCode::SUCCESS,
Err(error) => {
eprintln!("Error: {}", error);
ExitCode::FAILURE
}
}
}

View File

@ -0,0 +1,308 @@
use std::{
collections::HashMap,
fs::File,
io::{BufReader, Read, Seek, SeekFrom},
path::{Path, PathBuf},
rc::Rc,
};
use elf::{endian::AnyEndian, symbol::Symbol, ElfStream};
use yggdrasil_rt::mem::MappingSource;
use crate::{relocation::RelocationExt, Error, State};
#[derive(Debug)]
pub struct DynamicSymbol {
pub value: Option<usize>,
pub tls_index: Option<usize>,
pub raw: Symbol,
pub name: Rc<str>,
}
pub struct ObjectTls {
pub data: ObjectMapping,
// Maps symbols to their offsets within the TLS data
pub symbol_table: HashMap<Rc<str>, usize>,
}
pub struct ObjectMapping {
pub base: usize,
pub size: usize,
}
pub struct Object {
pub path: PathBuf,
pub file: BufReader<File>,
pub elf: ElfStream<AnyEndian, BufReader<File>>,
pub vma_start: usize,
pub vma_end: usize,
mapping: Option<ObjectMapping>,
tls_module_id: Option<usize>,
needed: Vec<String>,
dynamic_symbol_array: Vec<DynamicSymbol>,
}
impl ObjectTls {
pub fn new(size: usize) -> Result<Self, Error> {
Ok(Self {
data: ObjectMapping::new(size)?,
symbol_table: HashMap::new(),
})
}
}
impl Object {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
let path = path.as_ref().to_owned();
let file = BufReader::new(File::open(&path)?);
let mut elf = ElfStream::open_stream(file)?;
let file = BufReader::new(File::open(&path)?);
let (vma_start, vma_end) = object_bounds(&elf);
// Extract info from .dynamic
let mut needed = vec![];
if let Some(dynamic) = elf.dynamic()? {
for entry in dynamic {
match entry.d_tag {
elf::abi::DT_NEEDED => needed.push(entry.d_val() as usize),
_ => (),
}
}
}
let needed = if !needed.is_empty() {
// TODO handle
let (_, dynstr) = elf
.dynamic_symbol_table()?
.ok_or_else(|| Error::MissingDynamicSymbolTable(path.clone()))?;
needed
.into_iter()
.map(|off| dynstr.get(off).map(ToOwned::to_owned))
.collect::<Result<_, _>>()?
} else {
vec![]
};
let mut dynamic_symbol_array = vec![];
if let Some((dynsym, dynstr)) = elf.dynamic_symbol_table()? {
for symbol in dynsym {
let name: Rc<str> = dynstr.get(symbol.st_name as usize)?.into();
dynamic_symbol_array.push(DynamicSymbol {
value: None,
tls_index: None,
raw: symbol.clone(),
name: name.clone(),
});
}
}
Ok(Self {
path,
file,
elf,
vma_start,
vma_end,
mapping: None,
tls_module_id: None,
needed,
dynamic_symbol_array,
})
}
pub fn entry(&self) -> Result<Option<extern "C" fn(usize) -> !>, Error> {
let mapping = self.mapping.as_ref().ok_or(Error::ObjectNotLoaded)?;
if self.elf.ehdr.e_entry == 0 {
return Ok(None);
}
let vma = self.elf.ehdr.e_entry as usize;
Ok(Some(unsafe {
std::mem::transmute(mapping.base + vma - self.vma_start)
}))
}
pub fn load(&mut self, state: &mut State) -> Result<(), Error> {
// Already loaded
if self.mapping.is_some() {
return Ok(());
}
let size = self.vma_end - self.vma_start;
let mut mapping = ObjectMapping::new(size)?;
let object_data = mapping.as_slice_mut();
for segment in self.elf.segments() {
if segment.p_type != elf::abi::PT_LOAD {
continue;
}
}
for segment in self.elf.segments() {
let mem_size = segment.p_memsz as usize;
let file_size = segment.p_filesz as usize;
match segment.p_type {
elf::abi::PT_LOAD => {
let rel_offset = segment.p_vaddr as usize - self.vma_start;
let segment_data = &mut object_data[rel_offset..rel_offset + mem_size];
if file_size > 0 {
self.file.seek(SeekFrom::Start(segment.p_offset))?;
self.file.read_exact(&mut segment_data[..file_size])?;
}
if mem_size > file_size {
segment_data[file_size..mem_size].fill(0);
}
}
elf::abi::PT_TLS => {
let (tls_index, tls) = state.new_tls((mem_size + 0xFFF) & !0xFFF)?;
let tls_data = tls.data.as_slice_mut();
if file_size > 0 {
self.file.seek(SeekFrom::Start(segment.p_offset))?;
self.file.read_exact(&mut tls_data[..file_size])?;
}
if mem_size > file_size {
tls_data[file_size..mem_size].fill(0);
}
self.tls_module_id = Some(tls_index);
}
_ => (),
}
}
for dynsym in self.dynamic_symbol_array.iter_mut() {
if dynsym.raw.st_shndx == elf::abi::SHN_UNDEF {
continue;
}
state.export_symbol(dynsym, mapping.base, &self.path, self.tls_module_id);
}
self.mapping = Some(mapping);
Ok(())
}
pub fn relocate(&mut self, state: &mut State) -> Result<(), Error> {
let mapping = self.mapping.as_mut().ok_or(Error::ObjectNotLoaded)?;
for dynsym in self.dynamic_symbol_array.iter_mut() {
if dynsym.value.is_none() {
state.resolve_symbol(dynsym);
} else {
if dynsym.raw.st_shndx == elf::abi::SHN_UNDEF {
state.undefined_references.push(dynsym.name.clone());
}
}
}
// TODO x86-64 doesn't seem to have REL sections, but having them handled would still be
// nice
// Perform relocations
let rela_sections = self
.elf
.section_headers()
.iter()
.filter(|s| s.sh_type == elf::abi::SHT_RELA)
.cloned()
.collect::<Vec<_>>();
for rela in rela_sections {
let rela = self.elf.section_data_as_relas(&rela)?;
for entry in rela {
if let Some(value) = entry.resolve(
state,
self.tls_module_id,
|idx| Ok(&self.dynamic_symbol_array[idx as usize]),
mapping.base,
)? {
value.write(mapping, entry.r_offset);
}
}
}
Ok(())
}
// TODO this only resolves direct dependencies, need recursion
pub fn collect_dependencies<R>(
&mut self,
libs: &mut HashMap<PathBuf, Object>,
resolver: &R,
) -> Result<(), Error>
where
R: Fn(&str) -> Result<PathBuf, Error>,
{
for dep in self.needed.drain(..) {
let path = resolver(&dep)?;
if libs.contains_key(&path) {
continue;
}
let object = Object::open(&path)?;
libs.insert(path, object);
}
Ok(())
}
}
impl ObjectMapping {
pub fn new(size: usize) -> Result<Self, Error> {
let base = unsafe { yggdrasil_rt::sys::map_memory(None, size, &MappingSource::Anonymous) }
.map_err(|e| Error::MapError(size, e))?;
Ok(Self { base, size })
}
pub fn as_slice_mut(&mut self) -> &mut [u8] {
unsafe { std::slice::from_raw_parts_mut(self.base as *mut u8, self.size) }
}
}
impl Drop for ObjectMapping {
fn drop(&mut self) {
unsafe {
yggdrasil_rt::sys::unmap_memory(self.base, self.size).ok();
}
}
}
fn object_bounds<F: Read + Seek>(elf: &ElfStream<AnyEndian, F>) -> (usize, usize) {
let mut vma_min = usize::MAX;
let mut vma_max = usize::MIN;
for segment in elf.segments() {
if segment.p_type != elf::abi::PT_LOAD {
continue;
}
let start_vma = (segment.p_vaddr & !(segment.p_align - 1)) as usize;
let end_vma = ((segment.p_vaddr + segment.p_memsz + segment.p_align - 1)
& !(segment.p_align - 1)) as usize;
if start_vma < vma_min {
vma_min = start_vma;
}
if end_vma > vma_max {
vma_max = end_vma;
}
}
(vma_min, vma_max)
}

View File

@ -0,0 +1,170 @@
use elf::relocation::Rela;
use crate::{
object::{DynamicSymbol, ObjectMapping},
Error, State,
};
pub enum RelValue {
QWord(i64),
#[allow(unused)] // unused on x86_64
QDWord(i64, i64),
}
impl RelValue {
pub fn write(&self, mapping: &mut ObjectMapping, offset: u64) {
let addr = mapping.base + offset as usize;
unsafe {
match self {
&Self::QWord(value) => {
(addr as *mut i64).write_volatile(value);
}
&Self::QDWord(word0, word1) => {
(addr as *mut i64).write_volatile(word0);
(addr as *mut i64).add(1).write_volatile(word1);
}
}
}
}
}
pub trait RelocationExt {
fn resolve<'a, F: Fn(u32) -> Result<&'a DynamicSymbol, Error>>(
&'a self,
state: &mut State,
tls_module_id: Option<usize>,
image_symbol: F,
image_base: usize,
) -> Result<Option<RelValue>, Error>;
}
#[cfg(any(target_arch = "x86_64", rust_analyzer))]
impl RelocationExt for Rela {
fn resolve<'a, F: Fn(u32) -> Result<&'a DynamicSymbol, Error>>(
&'a self,
state: &mut State,
tls_module_id: Option<usize>,
image_symbol: F,
image_base: usize,
) -> Result<Option<RelValue>, Error> {
let image_base = image_base as i64;
let symbol = image_symbol(self.r_sym)?;
let base_value = match self.r_type {
elf::abi::R_X86_64_JUMP_SLOT => {
// TODO lazy binding could be implemented here
state.lookup_resolved_symbol(&symbol.name)
}
elf::abi::R_X86_64_GLOB_DAT => state.lookup_resolved_glob_dat(&symbol.name),
elf::abi::R_X86_64_64 => state.lookup_resolved_symbol(&symbol.name),
// Retrieve raw symbol value, as this might be an offset into local TLS struct
elf::abi::R_X86_64_DTPMOD64 | elf::abi::R_X86_64_DTPOFF64 => {
symbol.raw.st_value as usize
}
_ => {
if let Some(value) = symbol.value {
value
} else if symbol.raw.st_symtype() == elf::abi::STT_NOTYPE {
0
} else {
return Err(Error::CannotRelocate);
}
}
} as i64;
match self.r_type {
// Direct 64 bit
elf::abi::R_X86_64_64 => Ok(Some(RelValue::QWord(base_value + self.r_addend))),
elf::abi::R_X86_64_JUMP_SLOT => Ok(Some(RelValue::QWord(base_value))),
// GLOB_DAT
elf::abi::R_X86_64_GLOB_DAT => Ok(Some(RelValue::QWord(base_value))),
// Adjust by image base
elf::abi::R_X86_64_RELATIVE => Ok(Some(RelValue::QWord(image_base + self.r_addend))),
// ID of module containing this symbol (if not present assume local symbol)
elf::abi::R_X86_64_DTPMOD64 => {
// TODO I'm not sure if per-object TLS is really needed:
// all TLS segments could just be collected into a single one, simplifying
// __tls_get_addr()
if let Some((module_id, _)) = state.lookup_resolved_tls_symbol(&symbol.name) {
Ok(Some(RelValue::QWord(module_id as _)))
} else {
Ok(Some(RelValue::QWord(tls_module_id.unwrap() as _)))
}
}
// Offset in module's TLS block
elf::abi::R_X86_64_DTPOFF64 => {
if let Some((_, value)) = state.lookup_resolved_tls_symbol(&symbol.name) {
Ok(Some(RelValue::QWord(self.r_addend + value as i64)))
} else {
Ok(Some(RelValue::QWord(self.r_addend + base_value)))
}
}
ty => Err(Error::UnsupportedRelocation(ty)),
}
}
}
#[cfg(any(target_arch = "aarch64", rust_analyzer))]
impl RelocationExt for Rela {
fn resolve<'a, F: Fn(u32) -> Result<&'a DynamicSymbol, Error>>(
&'a self,
state: &mut State,
tls_module_id: Option<usize>,
image_symbol: F,
image_base: usize,
) -> Result<Option<RelValue>, Error> {
let image_base = image_base as i64;
let symbol = image_symbol(self.r_sym)?;
let base_value = match self.r_type {
elf::abi::R_AARCH64_ABS64 => state.lookup_resolved_symbol(&symbol.name),
// TODO lazy binding?
elf::abi::R_AARCH64_JUMP_SLOT => state.lookup_resolved_symbol(&symbol.name),
elf::abi::R_AARCH64_GLOB_DAT => state.lookup_resolved_glob_dat(&symbol.name),
elf::abi::R_AARCH64_TLSDESC => symbol.value.unwrap_or(symbol.raw.st_value as usize),
_ => {
if let Some(value) = symbol.value {
value
} else if symbol.raw.st_symtype() == elf::abi::STT_NOTYPE {
0
} else {
return Err(Error::CannotRelocate);
}
}
} as i64;
match self.r_type {
// Adjust by image base
elf::abi::R_AARCH64_RELATIVE => Ok(Some(RelValue::QWord(image_base + self.r_addend))),
elf::abi::R_AARCH64_JUMP_SLOT => Ok(Some(RelValue::QWord(base_value))),
// GLOB_DAT
elf::abi::R_AARCH64_GLOB_DAT => Ok(Some(RelValue::QWord(base_value))),
// Direct 64 bit
elf::abi::R_AARCH64_ABS64 => Ok(Some(RelValue::QWord(base_value + self.r_addend))),
elf::abi::R_AARCH64_TLSDESC => {
let word0 = crate::builtins::__dl_tlsdesc_static as usize;
let reloc_offset =
unsafe { *((image_base as usize + self.r_offset as usize) as *const i64) };
let word1 = if let Some((module_id, offset)) =
state.lookup_resolved_tls_symbol(&symbol.name)
{
// FIXME not really sure about this code
let tls_base = state.tls_table[module_id].data.base;
base_value + reloc_offset + (tls_base + offset) as i64
} else {
let local_tls = state.tls_table[tls_module_id.unwrap() - 1].data.base;
local_tls as i64 + reloc_offset + self.r_addend
};
Ok(Some(RelValue::QDWord(word0 as _, word1)))
}
_ => Err(Error::UnsupportedRelocation(self.r_type)),
}
}
}

View File

@ -0,0 +1,130 @@
use std::{
collections::HashMap,
path::{Path, PathBuf},
rc::Rc,
};
use crate::{
builtins,
object::{DynamicSymbol, ObjectTls},
Error,
};
#[derive(Default)]
pub struct State {
pub glob_dat: HashMap<Rc<str>, usize>,
pub symbol_table: HashMap<Rc<str>, (usize, PathBuf)>,
pub tls_table: Vec<ObjectTls>,
pub tls_symbols: HashMap<Rc<str>, (usize, usize)>,
// This list should be empty if everything succeeds
pub undefined_references: Vec<Rc<str>>,
}
impl State {
pub fn new_tls(&mut self, size: usize) -> Result<(usize, &mut ObjectTls), Error> {
let index = self.tls_table.len();
let tls = ObjectTls::new(size)?;
self.tls_table.push(tls);
let value = &mut self.tls_table[index];
// Zero reserved or something
Ok((index + 1, value))
}
pub fn insert_linker_builtins(&mut self) {
let linker = PathBuf::from("/libexec/dyn-loader");
self.symbol_table.insert(
"__tls_get_addr".into(),
(builtins::__dl_tls_get_addr as usize, linker),
);
}
pub fn tls_address(&self, index: usize) -> Option<usize> {
self.tls_table
.get(index - 1)
.as_ref()
.map(|tls| tls.data.base)
}
pub fn export_symbol<P: AsRef<Path>>(
&mut self,
sym: &mut DynamicSymbol,
load_base: usize,
source: P,
tls_index: Option<usize>,
) {
if sym.raw.st_symtype() == elf::abi::STT_TLS {
// If it exports TLS symbols, it has TLS, I guess
let self_module_id = tls_index.expect("TLS symbol in an object without TLS segment");
// TODO sanity checks (that the symbol actually fits within the allocated TLS block)
let (module_id, value) =
if let Some(&(module_id, symbol_value)) = self.tls_symbols.get(&sym.name) {
(module_id, symbol_value)
} else {
// Does not exist, export it
self.tls_symbols.insert(
sym.name.clone(),
(self_module_id, sym.raw.st_value as usize),
);
(self_module_id, sym.raw.st_value as usize)
};
sym.value = Some(value);
sym.tls_index = Some(module_id);
} else {
let sym_value = sym.raw.st_value as usize + load_base;
let value = if let Some((value, _)) = self.symbol_table.get(&sym.name) {
*value
} else {
// Does not exist, export it
self.symbol_table
.insert(sym.name.clone(), (sym_value, source.as_ref().to_owned()));
sym_value
};
sym.value = Some(value);
}
}
pub fn resolve_symbol(&mut self, sym: &mut DynamicSymbol) {
if sym.raw.st_symtype() == elf::abi::STT_TLS {
if let Some(&(module_id, value)) = self.tls_symbols.get(&sym.name) {
sym.value = Some(value);
sym.tls_index = Some(module_id);
} else if !sym.name.is_empty() {
self.undefined_references.push(sym.name.clone());
}
} else {
if let Some((value, _)) = self.symbol_table.get(&sym.name) {
sym.value = Some(*value);
} else if !sym.name.is_empty() {
self.undefined_references.push(sym.name.clone());
}
}
}
pub fn lookup_resolved_glob_dat(&mut self, name: &Rc<str>) -> usize {
if let Some((value, _)) = self.symbol_table.get(name) {
*value
} else if let Some(value) = self.glob_dat.get(name) {
*value
} else {
self.undefined_references.push(name.clone());
0
}
}
pub fn lookup_resolved_symbol(&mut self, name: &Rc<str>) -> usize {
if let Some((value, _)) = self.symbol_table.get(name) {
*value
} else {
self.undefined_references.push(name.clone());
0
}
}
pub fn lookup_resolved_tls_symbol(&mut self, name: &Rc<str>) -> Option<(usize, usize)> {
// TODO differentiate between local TLS references and really undefined TLS references?
self.tls_symbols.get(name).copied()
}
}

View File

@ -0,0 +1,2 @@
[build]
rustflags = ["-C", "prefer-dynamic"]

115
userspace/dynload-program/Cargo.lock generated Normal file
View File

@ -0,0 +1,115 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "abi-generator"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
"thiserror",
]
[[package]]
name = "abi-lib"
version = "0.1.0"
[[package]]
name = "cc"
version = "1.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
[[package]]
name = "dynload-program"
version = "0.1.0"
dependencies = [
"yggdrasil-rt",
]
[[package]]
name = "prettyplease"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "yggdrasil-abi"
version = "0.1.0"
dependencies = [
"abi-generator",
"abi-lib",
"prettyplease",
]
[[package]]
name = "yggdrasil-rt"
version = "0.1.0"
dependencies = [
"abi-generator",
"abi-lib",
"cc",
"prettyplease",
"yggdrasil-abi",
]

View File

@ -0,0 +1,7 @@
[package]
name = "dynload-program"
version = "0.1.0"
edition = "2021"
[dependencies]
yggdrasil-rt = { path = "../../lib/runtime" }

View File

@ -0,0 +1,18 @@
#![feature(yggdrasil_os)]
// // TODO not sure why this pops up
// #[no_mangle]
// unsafe extern "C" fn __sync_val_compare_and_swap_16() -> ! {
// yggdrasil_rt::debug_trace!("__sync_val_compare_and_swap_16");
// loop {}
// }
//
// #[no_mangle]
// unsafe extern "C" fn __sync_lock_test_and_set_16() -> ! {
// yggdrasil_rt::debug_trace!("__sync_lock_test_and_set_16");
// loop {}
// }
fn main() {
println!("This program just printed stuff through dynamically linked std!");
}

View File

@ -6,7 +6,7 @@ use crate::{
};
pub enum CargoBuilder<'e> {
Host,
Host(bool),
Userspace(&'e BuildEnv),
Kernel(&'e BuildEnv),
}
@ -40,16 +40,28 @@ impl<'e> CargoBuilder<'e> {
.arg(arg)
.arg(&format!("--target={}", env.arch.user_triple()));
if env.verbose {
command.arg("-v");
}
if env.profile == Profile::Release {
command.arg("--release");
}
}
Self::Host => {
Self::Host(verbose) => {
command.arg("+nightly").arg(arg);
if verbose {
command.arg("-v");
}
}
Self::Kernel(env) => {
command.arg("+nightly").arg(arg);
if env.verbose {
command.arg("-v");
}
let target_spec_arg = format!(
"--target={}/etc/{}.json",
env.workspace_root.display(),

View File

@ -40,7 +40,7 @@ pub enum AllBuilt {
pub fn build_kernel_tools(env: &BuildEnv, _: AllOk) -> Result<ToolsBuilt, Error> {
log::info!("Building kernel tools");
CargoBuilder::Host.build("kernel/tools/gentables")?;
CargoBuilder::Host(env.verbose).build("kernel/tools/gentables")?;
Ok(ToolsBuilt(
env.workspace_root.join("target/debug/gentables"),
))
@ -91,8 +91,8 @@ pub fn check_all(env: BuildEnv, action: CheckAction) -> Result<(), Error> {
let action = action.command();
if env.arch == Arch::x86_64 {
CargoBuilder::Host.run(env.workspace_root.join("boot/yboot-proto"), action)?;
CargoBuilder::Host.run(env.workspace_root.join("boot/yboot"), action)?;
CargoBuilder::Host(env.verbose).run(env.workspace_root.join("boot/yboot-proto"), action)?;
CargoBuilder::Host(env.verbose).run(env.workspace_root.join("boot/yboot"), action)?;
}
CargoBuilder::Userspace(&env).run(env.workspace_root.join("userspace"), action)?;
@ -103,19 +103,24 @@ pub fn check_all(env: BuildEnv, action: CheckAction) -> Result<(), Error> {
pub fn test_all(env: BuildEnv) -> Result<(), Error> {
for path in ["kernel/driver/fs/memfs", "lib/abi", "kernel/libk"] {
CargoBuilder::Host.run(env.workspace_root.join(path), "test")?;
CargoBuilder::Host(env.verbose).run(env.workspace_root.join(path), "test")?;
}
Ok(())
}
pub fn clean_userspace(env: &BuildEnv) -> Result<(), Error> {
CargoBuilder::Host.run(env.workspace_root.join("userspace"), "clean")
CargoBuilder::Host(env.verbose).run(env.workspace_root.join("userspace"), "clean")?;
CargoBuilder::Host(env.verbose).run(
env.workspace_root.join("userspace/dynload-program"),
"clean",
)?;
Ok(())
}
pub fn clean_all(env: &BuildEnv, clean_toolchain: bool) -> Result<(), Error> {
log::info!("Cleaning");
CargoBuilder::Host.run(&env.workspace_root, "clean")?;
CargoBuilder::Host(env.verbose).run(&env.workspace_root, "clean")?;
clean_userspace(env)?;
if clean_toolchain {

View File

@ -47,11 +47,13 @@ const PROGRAMS: &[(&str, &str)] = &[
("red", "bin/red"),
// rdb
("rdb", "bin/rdb"),
("dyn-loader", "libexec/dyn-loader"),
];
fn build_userspace(env: &BuildEnv, _: AllOk) -> Result<(), Error> {
log::info!("Building userspace");
CargoBuilder::Userspace(env).build(env.workspace_root.join("userspace"))?;
CargoBuilder::Userspace(env).build(env.workspace_root.join("userspace/dynload-program"))?;
Ok(())
}
@ -86,9 +88,24 @@ fn build_rootfs<S: AsRef<Path>, D: AsRef<Path>>(
src_path.display(),
dst_path.display()
);
fs::copy(src_path, dst_path)?;
util::copy_file(src_path, dst_path)?;
}
// TODO this is a temporary hack
fs::create_dir_all(rootfs_dir.join("lib"))?;
util::copy_file(
env.workspace_root.join(format!(
"userspace/dynload-program/target/{}-unknown-yggdrasil/{}/dynload-program",
env.arch.name(),
env.profile.name()
)),
rootfs_dir.join("dynload-program"),
)?;
util::copy_file(format!(
"/home/alnyan/build/ygg/toolchain/build/host/stage1-std/{}-unknown-yggdrasil/release/libstd.so",
env.arch.name()
), rootfs_dir.join("lib/libstd.so"))?;
// Copy /etc
util::copy_dir_recursive(user_dir.join("etc"), rootfs_dir.join("etc"))?;
@ -101,7 +118,7 @@ fn build_rootfs<S: AsRef<Path>, D: AsRef<Path>>(
util::copy_dir_recursive(arch_rc_d, rootfs_dir.join("etc/rc.d"))?;
}
if arch_inittab.exists() {
fs::copy(arch_inittab, rootfs_dir.join("etc/inittab"))?;
util::copy_file(arch_inittab, rootfs_dir.join("etc/inittab"))?;
}
Ok(())

View File

@ -8,7 +8,7 @@ pub struct YbootBuilt(PathBuf);
fn build_yboot(env: &BuildEnv) -> Result<YbootBuilt, Error> {
log::info!("Building yboot");
CargoBuilder::Host.build("boot/yboot")?;
CargoBuilder::Host(env.verbose).build("boot/yboot")?;
let binary = env.workspace_root.join(format!(
"boot/yboot/target/x86_64-unknown-uefi/debug/yboot.efi"
));

View File

@ -33,6 +33,7 @@ pub enum Arch {
#[derive(Debug)]
pub struct BuildEnv {
pub config: XTaskConfig,
pub verbose: bool,
pub profile: Profile,
pub arch: Arch,
@ -53,7 +54,13 @@ impl Default for ToolchainConfig {
}
impl BuildEnv {
pub fn new(config: XTaskConfig, profile: Profile, arch: Arch, workspace_root: PathBuf) -> Self {
pub fn new(
config: XTaskConfig,
verbose: bool,
profile: Profile,
arch: Arch,
workspace_root: PathBuf,
) -> Self {
let kernel_output_dir =
workspace_root.join(format!("target/{}/{}", arch.kernel_triple(), profile.dir()));
let userspace_output_dir = workspace_root.join(format!(
@ -65,6 +72,7 @@ impl BuildEnv {
Self {
config,
verbose,
profile,
arch,

View File

@ -24,6 +24,8 @@ pub enum Error {
InvalidQemuConfig(#[from] toml::de::Error),
#[error("Could not copy directory")]
CopyDirectoryError(#[from] walkdir::Error),
#[error("Could not copy file {1:?} -> {2:?}: {0}")]
CopyFileError(io::Error, PathBuf, PathBuf),
#[error("Invalid path: {0:?}")]
InvalidPath(PathBuf),
}

View File

@ -35,6 +35,8 @@ struct Args {
help = "Do a clean userspace rebuild (e.g. toolchain has changed)"
)]
clean_userspace: bool,
#[clap(short, long, help = "Be verbose where possible")]
verbose: bool,
#[clap(
short = 'C',
@ -96,7 +98,7 @@ fn run(args: Args) -> Result<(), Error> {
Profile::Debug
};
let config = toml::from_str(&fs::read_to_string(args.config_path)?)?;
let env = env::BuildEnv::new(config, profile, args.arch, workspace_root);
let env = env::BuildEnv::new(config, args.verbose, profile, args.arch, workspace_root);
if args.clean_userspace && !matches!(&action, SubArgs::Clean { .. }) {
build::clean_userspace(&env)?;

View File

@ -28,6 +28,14 @@ pub fn run_external_command<S0: AsRef<OsStr>, S1: AsRef<OsStr>, I: IntoIterator<
}
}
pub fn copy_file(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<(), Error> {
let src = src.as_ref();
let dst = dst.as_ref();
fs::copy(src, dst)
.map_err(|e| Error::CopyFileError(e, src.to_owned(), dst.to_owned()))
.map(|_| ())
}
pub fn copy_dir_recursive<S: AsRef<Path>, D: AsRef<Path>>(src: S, dst: D) -> Result<(), Error> {
let src = src.as_ref();
let dst = dst.as_ref();
@ -44,7 +52,7 @@ pub fn copy_dir_recursive<S: AsRef<Path>, D: AsRef<Path>>(src: S, dst: D) -> Res
fs::create_dir_all(dst_path)?;
} else {
log::trace!("Copy {} -> {}", src_path.display(), dst_path.display());
fs::copy(src_path, dst_path)?;
copy_file(src_path, dst_path)?;
}
}