diff --git a/Cargo.toml b/Cargo.toml index 47f75c5f..e3469e03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ exclude = [ "boot/yboot-proto", "tool/abi-generator", "toolchain", + "userspace/dynload-program" ] members = [ "xtask", diff --git a/etc/x86_64-unknown-none.ld b/etc/x86_64-unknown-none.ld index 1be78b30..c901c4fb 100644 --- a/etc/x86_64-unknown-none.ld +++ b/etc/x86_64-unknown-none.ld @@ -9,7 +9,6 @@ SECTIONS { PROVIDE(__kernel_start = . + KERNEL_VIRT_OFFSET); .text.entry : { - *(.multiboot) *(.text.entry) } diff --git a/kernel/libk/src/task/binary/elf.rs b/kernel/libk/src/task/binary/elf.rs index 4a4f82fc..f77b91ec 100644 --- a/kernel/libk/src/task/binary/elf.rs +++ b/kernel/libk/src/task/binary/elf.rs @@ -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 { +pub struct FileReader { file: F, } @@ -40,6 +40,11 @@ enum ElfSegmentType { Tls, } +pub enum ElfKind { + Dynamic(Option), + Static(ElfStream>>, FileReader>), +} + 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( - space: &ProcessAddressSpace, - file: Arc, -) -> Result { + +pub fn open_elf_file(file: Arc) -> Result, Error> { let file = FileReader { file }; let mut elf = ElfStream::::open_stream(file.clone()).map_err(from_parse_error)?; @@ -139,6 +141,20 @@ pub fn load_elf_from_file( 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( + space: &ProcessAddressSpace, + mut elf: ElfStream>, + file: FileReader, +) -> Result { // 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( + elf: &mut ElfStream>, +) -> Result { + 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(elf: &ElfStream>) -> (usize, usize) { let mut vaddr_min = usize::MAX; let mut vaddr_max = usize::MIN; diff --git a/kernel/libk/src/task/binary/mod.rs b/kernel/libk/src/task/binary/mod.rs index a2cce0a2..e0735337 100644 --- a/kernel/libk/src/task/binary/mod.rs +++ b/kernel/libk/src/task/binary/mod.rs @@ -31,6 +31,8 @@ use crate::{ vfs::IoContext, }; +use self::elf::ElfKind; + pub mod elf; pub type LoadedProcess = (Arc, Arc); @@ -190,17 +192,17 @@ where Ok((process, main)) } -fn load_binary( - head: &[u8], - file: Arc, - space: &ProcessAddressSpace, -) -> Result { - if head.starts_with(b"\x7FELF") { - elf::load_elf_from_file(space, file) - } else { - Err(Error::UnrecognizedExecutable) - } -} +// fn load_binary( +// head: &[u8], +// file: Arc, +// space: &ProcessAddressSpace, +// ) -> Result { +// if head.starts_with(b"\x7FELF") { +// elf::load_elf_from_file(space, file) +// } else { +// Err(Error::UnrecognizedExecutable) +// } +// } fn xxx_load_program>( space: &ProcessAddressSpace, @@ -217,6 +219,7 @@ fn xxx_load_program>( 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>( 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` diff --git a/kernel/src/arch/x86_64/exception.rs b/kernel/src/arch/x86_64/exception.rs index 9a1b3172..370a4963 100644 --- a/kernel/src/arch/x86_64/exception.rs +++ b/kernel/src/arch/x86_64/exception.rs @@ -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 diff --git a/kernel/src/syscall/imp/sys_process.rs b/kernel/src/syscall/imp/sys_process.rs index 79cd0026..3f0c8b2a 100644 --- a/kernel/src/syscall/imp/sys_process.rs +++ b/kernel/src/syscall/imp/sys_process.rs @@ -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 { diff --git a/lib/abi/def/yggdrasil.abi b/lib/abi/def/yggdrasil.abi index 643c83c5..467e4e50 100644 --- a/lib/abi/def/yggdrasil.abi +++ b/lib/abi/def/yggdrasil.abi @@ -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<()>; diff --git a/userspace/.gitignore b/userspace/.gitignore index ea1ddae8..ac9b50a3 100644 --- a/userspace/.gitignore +++ b/userspace/.gitignore @@ -1,2 +1,2 @@ /target -/extra.sh +/dynload-program/target diff --git a/userspace/Cargo.lock b/userspace/Cargo.lock index afd77465..760a4bd5 100644 --- a/userspace/Cargo.lock +++ b/userspace/Cargo.lock @@ -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", ] diff --git a/userspace/Cargo.toml b/userspace/Cargo.toml index edd2eb5d..3b203369 100644 --- a/userspace/Cargo.toml +++ b/userspace/Cargo.toml @@ -11,5 +11,8 @@ members = [ "lib/serde-ipc", "lib/libterm", "netutils", + "netutils", + "dyn-loader", "rdb" ] +exclude = ["dynload-program"] diff --git a/userspace/dyn-loader/Cargo.toml b/userspace/dyn-loader/Cargo.toml new file mode 100644 index 00000000..5e1682cb --- /dev/null +++ b/userspace/dyn-loader/Cargo.toml @@ -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" diff --git a/userspace/dyn-loader/src/builtins.rs b/userspace/dyn-loader/src/builtins.rs new file mode 100644 index 00000000..50e733df --- /dev/null +++ b/userspace/dyn-loader/src/builtins.rs @@ -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; +} diff --git a/userspace/dyn-loader/src/error.rs b/userspace/dyn-loader/src/error.rs new file mode 100644 index 00000000..5eb0bb71 --- /dev/null +++ b/userspace/dyn-loader/src/error.rs @@ -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, +} diff --git a/userspace/dyn-loader/src/main.rs b/userspace/dyn-loader/src/main.rs new file mode 100644 index 00000000..a1a01eaa --- /dev/null +++ b/userspace/dyn-loader/src/main.rs @@ -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, + trace_libraries: bool, +} + +impl Config { + pub fn from_env() -> Result { + 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 = None; + +fn resolve_library(config: &Config, name: &str) -> Result { + // 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>(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::>(); + // 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 + } + } +} diff --git a/userspace/dyn-loader/src/object.rs b/userspace/dyn-loader/src/object.rs new file mode 100644 index 00000000..473f4b1b --- /dev/null +++ b/userspace/dyn-loader/src/object.rs @@ -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, + pub tls_index: Option, + pub raw: Symbol, + pub name: Rc, +} + +pub struct ObjectTls { + pub data: ObjectMapping, + // Maps symbols to their offsets within the TLS data + pub symbol_table: HashMap, usize>, +} + +pub struct ObjectMapping { + pub base: usize, + pub size: usize, +} + +pub struct Object { + pub path: PathBuf, + pub file: BufReader, + pub elf: ElfStream>, + + pub vma_start: usize, + pub vma_end: usize, + mapping: Option, + tls_module_id: Option, + + needed: Vec, + dynamic_symbol_array: Vec, +} + +impl ObjectTls { + pub fn new(size: usize) -> Result { + Ok(Self { + data: ObjectMapping::new(size)?, + symbol_table: HashMap::new(), + }) + } +} + +impl Object { + pub fn open>(path: P) -> Result { + 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::>()? + } else { + vec![] + }; + + let mut dynamic_symbol_array = vec![]; + + if let Some((dynsym, dynstr)) = elf.dynamic_symbol_table()? { + for symbol in dynsym { + let name: Rc = 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 !>, 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::>(); + + 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( + &mut self, + libs: &mut HashMap, + resolver: &R, + ) -> Result<(), Error> + where + R: Fn(&str) -> Result, + { + 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 { + 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(elf: &ElfStream) -> (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) +} diff --git a/userspace/dyn-loader/src/relocation.rs b/userspace/dyn-loader/src/relocation.rs new file mode 100644 index 00000000..69e6df1f --- /dev/null +++ b/userspace/dyn-loader/src/relocation.rs @@ -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, + image_symbol: F, + image_base: usize, + ) -> Result, 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, + image_symbol: F, + image_base: usize, + ) -> Result, 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, + image_symbol: F, + image_base: usize, + ) -> Result, 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)), + } + } +} diff --git a/userspace/dyn-loader/src/state.rs b/userspace/dyn-loader/src/state.rs new file mode 100644 index 00000000..da66de4d --- /dev/null +++ b/userspace/dyn-loader/src/state.rs @@ -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, usize>, + pub symbol_table: HashMap, (usize, PathBuf)>, + pub tls_table: Vec, + pub tls_symbols: HashMap, (usize, usize)>, + // This list should be empty if everything succeeds + pub undefined_references: Vec>, +} + +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 { + self.tls_table + .get(index - 1) + .as_ref() + .map(|tls| tls.data.base) + } + + pub fn export_symbol>( + &mut self, + sym: &mut DynamicSymbol, + load_base: usize, + source: P, + tls_index: Option, + ) { + 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) -> 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) -> 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) -> Option<(usize, usize)> { + // TODO differentiate between local TLS references and really undefined TLS references? + self.tls_symbols.get(name).copied() + } +} diff --git a/userspace/dynload-program/.cargo/config.toml b/userspace/dynload-program/.cargo/config.toml new file mode 100644 index 00000000..d046680d --- /dev/null +++ b/userspace/dynload-program/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +rustflags = ["-C", "prefer-dynamic"] diff --git a/userspace/dynload-program/Cargo.lock b/userspace/dynload-program/Cargo.lock new file mode 100644 index 00000000..0c979353 --- /dev/null +++ b/userspace/dynload-program/Cargo.lock @@ -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", +] diff --git a/userspace/dynload-program/Cargo.toml b/userspace/dynload-program/Cargo.toml new file mode 100644 index 00000000..730e0095 --- /dev/null +++ b/userspace/dynload-program/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "dynload-program" +version = "0.1.0" +edition = "2021" + +[dependencies] +yggdrasil-rt = { path = "../../lib/runtime" } diff --git a/userspace/dynload-program/src/main.rs b/userspace/dynload-program/src/main.rs new file mode 100644 index 00000000..5bc2cdcf --- /dev/null +++ b/userspace/dynload-program/src/main.rs @@ -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!"); +} diff --git a/xtask/src/build/cargo.rs b/xtask/src/build/cargo.rs index b565c587..5556431d 100644 --- a/xtask/src/build/cargo.rs +++ b/xtask/src/build/cargo.rs @@ -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(), diff --git a/xtask/src/build/mod.rs b/xtask/src/build/mod.rs index 45d9229d..881ac61c 100644 --- a/xtask/src/build/mod.rs +++ b/xtask/src/build/mod.rs @@ -40,7 +40,7 @@ pub enum AllBuilt { pub fn build_kernel_tools(env: &BuildEnv, _: AllOk) -> Result { 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 { diff --git a/xtask/src/build/userspace.rs b/xtask/src/build/userspace.rs index 909ee834..d9fc987a 100644 --- a/xtask/src/build/userspace.rs +++ b/xtask/src/build/userspace.rs @@ -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, D: AsRef>( 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, D: AsRef>( 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(()) diff --git a/xtask/src/build/x86_64.rs b/xtask/src/build/x86_64.rs index f5e85786..00e56783 100644 --- a/xtask/src/build/x86_64.rs +++ b/xtask/src/build/x86_64.rs @@ -8,7 +8,7 @@ pub struct YbootBuilt(PathBuf); fn build_yboot(env: &BuildEnv) -> Result { 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" )); diff --git a/xtask/src/env.rs b/xtask/src/env.rs index 79761ac1..e1ff6600 100644 --- a/xtask/src/env.rs +++ b/xtask/src/env.rs @@ -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, diff --git a/xtask/src/error.rs b/xtask/src/error.rs index 2ebc0ccf..887a5adf 100644 --- a/xtask/src/error.rs +++ b/xtask/src/error.rs @@ -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), } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index b985501e..56fa694a 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -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)?; diff --git a/xtask/src/util.rs b/xtask/src/util.rs index de5b267d..b382dcef 100644 --- a/xtask/src/util.rs +++ b/xtask/src/util.rs @@ -28,6 +28,14 @@ pub fn run_external_command, S1: AsRef, I: IntoIterator< } } +pub fn copy_file(src: impl AsRef, dst: impl AsRef) -> 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, D: AsRef>(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, D: AsRef>(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)?; } }