proc/dyn: proof-of-concept dynamic executable loader
This commit is contained in:
parent
0fbd77ab20
commit
35a44a8ca1
@ -5,6 +5,7 @@ exclude = [
|
||||
"boot/yboot-proto",
|
||||
"tool/abi-generator",
|
||||
"toolchain",
|
||||
"userspace/dynload-program"
|
||||
]
|
||||
members = [
|
||||
"xtask",
|
||||
|
@ -9,7 +9,6 @@ SECTIONS {
|
||||
PROVIDE(__kernel_start = . + KERNEL_VIRT_OFFSET);
|
||||
|
||||
.text.entry : {
|
||||
*(.multiboot)
|
||||
*(.text.entry)
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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`
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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<()>;
|
||||
|
2
userspace/.gitignore
vendored
2
userspace/.gitignore
vendored
@ -1,2 +1,2 @@
|
||||
/target
|
||||
/extra.sh
|
||||
/dynload-program/target
|
||||
|
33
userspace/Cargo.lock
generated
33
userspace/Cargo.lock
generated
@ -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",
|
||||
]
|
||||
|
@ -11,5 +11,8 @@ members = [
|
||||
"lib/serde-ipc",
|
||||
"lib/libterm",
|
||||
"netutils",
|
||||
"netutils",
|
||||
"dyn-loader",
|
||||
"rdb"
|
||||
]
|
||||
exclude = ["dynload-program"]
|
||||
|
9
userspace/dyn-loader/Cargo.toml
Normal file
9
userspace/dyn-loader/Cargo.toml
Normal 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"
|
33
userspace/dyn-loader/src/builtins.rs
Normal file
33
userspace/dyn-loader/src/builtins.rs
Normal 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;
|
||||
}
|
25
userspace/dyn-loader/src/error.rs
Normal file
25
userspace/dyn-loader/src/error.rs
Normal 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,
|
||||
}
|
165
userspace/dyn-loader/src/main.rs
Normal file
165
userspace/dyn-loader/src/main.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
308
userspace/dyn-loader/src/object.rs
Normal file
308
userspace/dyn-loader/src/object.rs
Normal 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)
|
||||
}
|
170
userspace/dyn-loader/src/relocation.rs
Normal file
170
userspace/dyn-loader/src/relocation.rs
Normal 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)),
|
||||
}
|
||||
}
|
||||
}
|
130
userspace/dyn-loader/src/state.rs
Normal file
130
userspace/dyn-loader/src/state.rs
Normal 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()
|
||||
}
|
||||
}
|
2
userspace/dynload-program/.cargo/config.toml
Normal file
2
userspace/dynload-program/.cargo/config.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[build]
|
||||
rustflags = ["-C", "prefer-dynamic"]
|
115
userspace/dynload-program/Cargo.lock
generated
Normal file
115
userspace/dynload-program/Cargo.lock
generated
Normal 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",
|
||||
]
|
7
userspace/dynload-program/Cargo.toml
Normal file
7
userspace/dynload-program/Cargo.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "dynload-program"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
yggdrasil-rt = { path = "../../lib/runtime" }
|
18
userspace/dynload-program/src/main.rs
Normal file
18
userspace/dynload-program/src/main.rs
Normal 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!");
|
||||
}
|
@ -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(),
|
||||
|
@ -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 {
|
||||
|
@ -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(())
|
||||
|
@ -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"
|
||||
));
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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)?;
|
||||
|
@ -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)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user