dyn-loader: add config, better option parsing

This commit is contained in:
Mark Poliakov 2025-03-07 12:10:11 +02:00
parent 9f2ad4f2c9
commit 60bd925122
14 changed files with 222 additions and 78 deletions

3
userspace/Cargo.lock generated
View File

@ -746,11 +746,14 @@ version = "0.1.0"
dependencies = [
"bytemuck",
"cfg-if",
"clap",
"elf",
"log",
"logsink",
"rustc-demangle",
"serde",
"thiserror",
"toml",
"yggdrasil-rt",
]

View File

@ -10,6 +10,9 @@ logsink.workspace = true
thiserror.workspace = true
bytemuck.workspace = true
log.workspace = true
serde.workspace = true
toml.workspace = true
clap.workspace = true
elf = "0.7.4"
cfg-if = "1.0.0"

View File

@ -0,0 +1,46 @@
use std::{
env, fs,
path::{Path, PathBuf},
};
use serde::Deserialize;
use crate::error::Error;
#[derive(Debug, Deserialize)]
pub struct Config {
pub search_paths: Vec<PathBuf>,
}
impl Default for Config {
fn default() -> Self {
Self {
search_paths: vec!["/lib".into(), "/usr/lib".into()],
}
}
}
impl Config {
const DEFAULT_PATH: &str = "/etc/dyn-loader.toml";
pub fn load_or_default() -> Result<Config, Error> {
let default_path: &Path = Self::DEFAULT_PATH.as_ref();
if !default_path.exists() {
return Ok(Config::default());
}
let config = fs::read_to_string(default_path)?;
toml::from_str(&config).map_err(Error::ConfigError)
}
pub fn extend_from_env(&mut self) {
if let Ok(library_path) = env::var("LD_LIBRARY_PATH") {
for path in library_path
.split([':', ';'])
.map(str::trim)
.filter(|s| !s.is_empty())
{
self.search_paths.insert(0, path.into());
}
}
}
}

View File

@ -20,4 +20,6 @@ pub enum Error {
LibraryNotFound(String),
#[error("Object does not have an entry (trying to run a shared library?)")]
NoEntrypoint,
#[error("Configuration error")]
ConfigError(toml::de::Error),
}

View File

@ -4,18 +4,21 @@
map_try_insert,
slice_ptr_get,
iter_chain,
naked_functions
naked_functions,
let_chains
)]
#![allow(clippy::new_without_default, clippy::missing_transmute_annotations)]
use std::{fs, mem::MaybeUninit, os::yggdrasil::real_binary_path, path::Path, process::ExitCode};
use config::Config;
use error::Error;
use object::Object;
use state::ObjectSet;
use yggdrasil_rt::process::{auxv, AuxValue};
pub mod builtins;
pub mod config;
pub mod env;
pub mod error;
pub mod mapping;
@ -25,14 +28,30 @@ pub mod search;
pub mod state;
pub mod thread_local;
#[derive(Debug)]
pub struct Args {
pub this: String,
pub verbose: bool,
pub args: Vec<String>,
}
static mut OBJECTS: MaybeUninit<ObjectSet> = MaybeUninit::uninit();
fn run(binary: &Path, args: &[String]) -> Result<!, Error> {
log::info!("dyn-loader {binary:?}");
fn run(binary: &Path, args: &Args) -> Result<!, Error> {
let mut config = Config::load_or_default()?;
config.extend_from_env();
if args.verbose {
log::info!("Search paths:");
for path in config.search_paths.iter() {
log::info!("* {path:?}");
}
}
// Open root and its dependencies
let root = Object::open(binary, true)?;
let mut objects = ObjectSet::new(root);
objects.open_root_dependencies()?;
objects.open_root_dependencies(&config)?;
let tls_image = objects.load_all()?;
objects.state.add_magic_symbols();
objects.resolve_symbols()?;
@ -105,9 +124,11 @@ fn run(binary: &Path, args: &[String]) -> Result<!, Error> {
log::info!("entry = {:p}", entry);
#[allow(static_mut_refs)]
unsafe { OBJECTS.write(objects) };
unsafe {
OBJECTS.write(objects)
};
let argument = env::build_argument(args, &auxv)?;
let argument = env::build_argument(&args.args, &auxv)?;
unsafe { enter(entry, argument) };
}
@ -146,16 +167,62 @@ fn real_binary(arg0: &str) -> &Path {
real_binary_path()
}
fn main() -> ExitCode {
logsink::setup_logging(false);
let args: Vec<String> = std::env::args().skip(1).collect();
fn usage_no_args(this: &str) {
eprintln!("{this}: no arguments provided");
eprintln!(" {this} is an utility to load dynamically-linked programs");
eprintln!(" and is not intended to be invoked as a standalone program.");
eprintln!(" If you need to run a program explicitly, the command can be");
eprintln!(" used liked follows:");
eprintln!();
usage(this);
}
if args.is_empty() {
// Dump help and exit
todo!()
fn usage(this: &str) {
eprintln!("{this} [-v] PROGRAM [ARGS...]");
}
fn get_args() -> Result<Args, (String, String)> {
let mut args = std::env::args();
let this = args.next().unwrap();
let mut verbose = false;
let mut pargs = vec![];
let mut collect_flags = true;
for arg in args {
if collect_flags && let Some(flag) = arg.strip_prefix("-") {
match flag {
"v" => verbose = true,
_ => return Err((this, format!("unknown flag '-{flag}'"))),
}
} else {
collect_flags = false;
pargs.push(arg);
}
}
let real_binary = real_binary(&args[0]);
Ok(Args {
this,
verbose,
args: pargs,
})
}
fn main() -> ExitCode {
let args = match get_args() {
Ok(args) => args,
Err((this, message)) => {
eprintln!("{this}: {message}");
eprintln!("Usage:");
usage(&this);
return ExitCode::FAILURE;
}
};
if args.args.is_empty() {
usage_no_args(&args.this);
return ExitCode::FAILURE;
}
logsink::setup_logging(args.verbose);
let real_binary = real_binary(&args.args[0]);
let Err(error) = run(real_binary, &args);
eprintln!("Error: {error}");

View File

@ -12,7 +12,11 @@ use std::{
use elf::{
abi::{
DF_1_PIE, DT_FINI, DT_FINI_ARRAY, DT_FINI_ARRAYSZ, DT_FLAGS_1, DT_INIT, DT_INIT_ARRAY, DT_INIT_ARRAYSZ, DT_NEEDED, DT_PREINIT_ARRAY, DT_PREINIT_ARRAYSZ, ET_DYN, ET_EXEC, PT_DYNAMIC, PT_GNU_EH_FRAME, PT_GNU_RELRO, PT_GNU_STACK, PT_INTERP, PT_LOAD, PT_NOTE, PT_NULL, PT_PHDR, PT_RISCV_ATTRIBUTES, PT_TLS, SHN_UNDEF, SHT_REL, SHT_RELA, STB_GLOBAL, STB_LOCAL, STB_WEAK
DF_1_PIE, DT_FINI, DT_FINI_ARRAY, DT_FINI_ARRAYSZ, DT_FLAGS_1, DT_INIT, DT_INIT_ARRAY,
DT_INIT_ARRAYSZ, DT_NEEDED, DT_PREINIT_ARRAY, DT_PREINIT_ARRAYSZ, ET_DYN, ET_EXEC,
PT_DYNAMIC, PT_GNU_EH_FRAME, PT_GNU_RELRO, PT_GNU_STACK, PT_INTERP, PT_LOAD, PT_NOTE,
PT_NULL, PT_PHDR, PT_RISCV_ATTRIBUTES, PT_TLS, SHN_UNDEF, SHT_REL, SHT_RELA, STB_GLOBAL,
STB_LOCAL, STB_WEAK,
},
endian::AnyEndian,
parse::ParsingIterator,
@ -294,7 +298,7 @@ impl Object {
_ => {
log::error!("Unhandled segment type: {:#x}", segment.p_type);
return Err(Error::UnhandledObject);
},
}
}
}

View File

@ -79,7 +79,12 @@ impl Relocation for Rela {
},
ResolvedSymbol::Null(object_id) => match self.r_type {
// See make_tlsdesc_relocation()
R_AARCH64_TLSDESC => Ok(Some(make_tlsdesc_relocation(false, state, object_id + 1, 0))),
R_AARCH64_TLSDESC => Ok(Some(make_tlsdesc_relocation(
false,
state,
object_id + 1,
0,
))),
// B + A
R_AARCH64_RELATIVE => Ok(Some(RelaValue::QWord(load_base as i64 + self.r_addend))),
_ => todo!(
@ -94,13 +99,9 @@ impl Relocation for Rela {
}
match self.r_type {
// Direct 64 bit: S + A
R_AARCH64_ABS64 => {
Ok(Some(RelaValue::QWord(s + self.r_addend)))
}
R_AARCH64_ABS64 => Ok(Some(RelaValue::QWord(s + self.r_addend))),
// Direct 64 bit: S
R_AARCH64_JUMP_SLOT | R_AARCH64_GLOB_DAT => {
Ok(Some(RelaValue::QWord(s)))
}
R_AARCH64_JUMP_SLOT | R_AARCH64_GLOB_DAT => Ok(Some(RelaValue::QWord(s))),
_ => todo!("Unsupported relocation: {}", self.r_type),
}
}

View File

@ -46,8 +46,7 @@ impl Relocation for Rel {
Ok(Some(RelValue::DWordNoAddend(offset_from_tp)))
}
// 14 => Ok(Some(RelValue::DWordNoAddend()))
_ => todo!("Unsupported relocation against TLS symbol: {}", self.r_type)
_ => todo!("Unsupported relocation against TLS symbol: {}", self.r_type),
},
&ResolvedSymbol::Null(object_id) => match self.r_type {
// R_386_RELATIVE: B + A
@ -56,11 +55,14 @@ impl Relocation for Rel {
// R_386_DTPMOD32: TLS module ID
35 => Ok(Some(RelValue::DWordNoAddend(object_id as _))),
_ => todo!("Unsupported relocation against NULL symbol: {}", self.r_type),
}
_ => todo!(
"Unsupported relocation against NULL symbol: {}",
self.r_type
),
},
_ => {
let s: i32 = symbol.value().try_into().unwrap();
if s == 0 {
if s == 0 {
todo!("Relocation: r_type={}, name={name} is NULL", self.r_type)
}
match self.r_type {
@ -70,7 +72,7 @@ impl Relocation for Rel {
// R_386_JMP_SLOT: S
6 | 7 => Ok(Some(RelValue::DWordNoAddend(s))),
_ => todo!("Unsupported relocation type: {}", self.r_type)
_ => todo!("Unsupported relocation type: {}", self.r_type),
}
}
}

View File

@ -1,15 +1,18 @@
use elf::{parse::ParseAt, relocation::{Rel, Rela}};
use elf::{
parse::ParseAt,
relocation::{Rel, Rela},
};
use crate::{error::Error, mapping::Mapping, object::ResolvedSymbol, state::State};
#[cfg(any(target_arch = "aarch64", rust_analyzer))]
mod aarch64;
#[cfg(any(target_arch = "x86_64", rust_analyzer))]
mod x86_64;
#[cfg(any(target_arch = "x86", rust_analyzer))]
mod i686;
#[cfg(any(target_arch = "riscv64", rust_analyzer))]
mod riscv64;
#[cfg(any(target_arch = "x86_64", rust_analyzer))]
mod x86_64;
pub enum RelaValue {
DQWord(i64, i64),
@ -45,12 +48,10 @@ pub trait Relocation {
impl RelocationValue for RelaValue {
fn write(&self, mapping: &mut Mapping, offset: u64) {
match *self {
Self::DQWord(v0, v1) => {
unsafe {
mapping.qword(offset).write_unaligned(v0);
mapping.qword(offset + 8).write_unaligned(v1);
}
}
Self::DQWord(v0, v1) => unsafe {
mapping.qword(offset).write_unaligned(v0);
mapping.qword(offset + 8).write_unaligned(v1);
},
Self::QWord(value) => unsafe {
let ptr = mapping.qword(offset);
// yggdrasil_rt::debug_trace!("write({:p}, {:#x})", ptr, value);
@ -66,7 +67,7 @@ impl RelocationValue for RelValue {
Self::DWord(value) => {
let a = unsafe { mapping.dword(offset).read() };
unsafe { mapping.dword(offset).write(value + a) };
},
}
Self::DWordNoAddend(value) => {
unsafe { mapping.dword(offset).write(value) };
}

View File

@ -1,5 +1,7 @@
use elf::{
abi::{R_RISCV_64, R_RISCV_JUMP_SLOT, R_RISCV_RELATIVE, R_RISCV_TLS_DTPMOD64, R_RISCV_TLS_DTPREL64},
abi::{
R_RISCV_64, R_RISCV_JUMP_SLOT, R_RISCV_RELATIVE, R_RISCV_TLS_DTPMOD64, R_RISCV_TLS_DTPREL64,
},
relocation::{Rel, Rela},
};

View File

@ -1,20 +1,13 @@
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use crate::error::Error;
use crate::{config::Config, error::Error};
pub fn find_library(name: &str) -> Result<PathBuf, Error> {
// TODO this is a hack, because Rust puts a hash at the end of its libstd.so
if name.starts_with("libstd-") {
return find_library("libstd.so");
}
let path = Path::new("/lib").join(name);
if path.exists() {
return Ok(path);
}
let path = Path::new("/mnt").join(name);
if path.exists() {
return Ok(path);
pub fn find_library(config: &Config, name: &str) -> Result<PathBuf, Error> {
for search_path in config.search_paths.iter() {
let path = search_path.join(name);
if path.exists() {
return Ok(path);
}
}
Err(Error::LibraryNotFound(name.to_owned()))
}

View File

@ -1,10 +1,15 @@
use std::{
collections::{HashMap, HashSet}, ffi::{CStr, CString}, iter, path::{Path, PathBuf}, rc::Rc
collections::{HashMap, HashSet},
ffi::{CStr, CString},
iter,
path::{Path, PathBuf},
rc::Rc,
};
use elf::abi::{STT_FUNC, STT_NOTYPE, STT_OBJECT, STT_TLS};
use crate::{
config::Config,
error::Error,
object::{DynamicSymbol, Object},
search,
@ -24,7 +29,7 @@ pub struct ExportedNormalSymbol {
pub size: usize,
pub weak: bool,
pub object_id: u32,
pub c_name: CString
pub c_name: CString,
}
pub struct ExportedTlsSymbol {
@ -129,7 +134,13 @@ impl State {
}
Some(_) => (),
None => {
log::debug!("{:?}: TLS {:?} -> {}:{:#x}", source, sym.name, object_id + 1, offset);
log::debug!(
"{:?}: TLS {:?} -> {}:{:#x}",
source,
sym.name,
object_id + 1,
offset
);
self.tls_symbol_table.insert(
sym.name.clone(),
ExportedTlsSymbol {
@ -167,9 +178,10 @@ impl State {
STT_FUNC | STT_OBJECT | STT_NOTYPE => {
self.symbol_table.get(&sym.name).map(ExportedSymbol::Normal)
}
STT_TLS => {
self.tls_symbol_table.get(&sym.name).map(ExportedSymbol::Tls)
},
STT_TLS => self
.tls_symbol_table
.get(&sym.name)
.map(ExportedSymbol::Tls),
_ => todo!(),
}
}
@ -179,7 +191,9 @@ impl State {
let list = if let Some(list) = self.undefined_references.get_mut(source) {
list
} else {
self.undefined_references.try_insert(source.to_owned(), HashSet::new()).unwrap()
self.undefined_references
.try_insert(source.to_owned(), HashSet::new())
.unwrap()
};
list.insert(sym.clone());
}
@ -194,14 +208,17 @@ impl State {
fn export_magic_symbol(&mut self, name: &str, value: usize) {
let c_name = CString::new(name).unwrap();
self.symbol_table.insert(name.into(), ExportedNormalSymbol {
source: "".into(),
weak: false,
value,
size: 0,
c_name,
object_id: u32::MAX
});
self.symbol_table.insert(
name.into(),
ExportedNormalSymbol {
source: "".into(),
weak: false,
value,
size: 0,
c_name,
object_id: u32::MAX,
},
);
}
pub fn add_magic_symbols(&mut self) {
@ -230,14 +247,14 @@ impl ObjectSet {
iter::chain(iter::once(&self.root), self.libraries.values())
}
fn open_dependencies_of(&mut self, object_id: u32) -> Result<(), Error> {
fn open_dependencies_of(&mut self, config: &Config, object_id: u32) -> Result<(), Error> {
let object = match object_id {
0 => &self.root,
_ => self.libraries.get(&object_id).ok_or(Error::NotLoaded)?,
};
let needed = object.needed().cloned().collect::<Vec<_>>();
for needed in needed {
let path = search::find_library(needed.as_str())?;
let path = search::find_library(config, needed.as_str())?;
if self.library_path_map.contains_key(&path) {
continue;
}
@ -248,13 +265,13 @@ impl ObjectSet {
self.libraries.insert(id, object);
self.library_path_map.insert(path, id);
self.open_dependencies_of(id)?;
self.open_dependencies_of(config, id)?;
}
Ok(())
}
pub fn open_root_dependencies(&mut self) -> Result<(), Error> {
self.open_dependencies_of(0)
pub fn open_root_dependencies(&mut self, config: &Config) -> Result<(), Error> {
self.open_dependencies_of(config, 0)
}
pub fn load_all(&mut self) -> Result<Option<TlsImage>, Error> {
@ -340,7 +357,7 @@ impl ObjectSet {
object_name: object.c_name.as_c_str(),
object_base,
symbol_name: symbol.c_name.as_c_str(),
symbol_base: symbol.value
symbol_base: symbol.value,
});
}
}

View File

@ -1,7 +1,7 @@
use std::collections::HashMap;
use yggdrasil_rt::mem::MappingFlags;
use cfg_if::cfg_if;
use yggdrasil_rt::mem::MappingFlags;
use crate::{error::Error, mapping::Mapping, object::Object};
@ -48,7 +48,10 @@ pub struct TlsImage {
impl TlsLayout {
pub fn offset(&self, module_id: u32, offset: usize) -> Option<usize> {
let segment = self.segments.iter().find(|seg| seg.module_id == module_id)?;
let segment = self
.segments
.iter()
.find(|seg| seg.module_id == module_id)?;
Some(segment.offset + offset)
}
}

View File

@ -74,7 +74,7 @@ impl TlsLayoutBuilder for TlsLayoutImpl {
segments,
total_size,
tp_offset,
prefix_len: 0
prefix_len: 0,
},
align,
))