diff --git a/Cargo.lock b/Cargo.lock index 70481734..e14cada8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1825,6 +1825,14 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1230ec65f13e0f9b28d789da20d2d419511893ea9dac2c1f4ef67b8b14e5da80" +[[package]] +name = "unwind" +version = "0.1.0" +dependencies = [ + "gimli", + "yggdrasil-rt", +] + [[package]] name = "url" version = "2.5.2" diff --git a/Cargo.toml b/Cargo.toml index 94d9a518..552ebc45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,8 @@ members = [ "lib/abi", "lib/libyalloc", "lib/runtime", - "lib/qemu" + "lib/qemu", + "lib/unwind" ] [workspace.dependencies] diff --git a/kernel/libk/src/task/binary/elf.rs b/kernel/libk/src/task/binary/elf.rs index 763ad1a9..ab679bf6 100644 --- a/kernel/libk/src/task/binary/elf.rs +++ b/kernel/libk/src/task/binary/elf.rs @@ -20,7 +20,10 @@ use yggdrasil_abi::{error::Error, io::SeekFrom, path::PathBuf}; use crate::{ random, - task::{process::ProcessImage, types::TlsImage}, + task::{ + process::{ProcessImage, UnwindInfo}, + types::TlsImage, + }, }; cfg_if! { @@ -207,6 +210,8 @@ pub fn load_elf_from_file( // Load TLS master copy somewhere into process memory let tls_image = handle_tls(space, &elf, &file)?; + let unwind_info = extract_unwind_info(&mut elf, image_load_base, vaddr_min); + // Fixup relocations handle_relocations(&mut elf, space, image_load_base)?; @@ -219,6 +224,38 @@ pub fn load_elf_from_file( ip_offset, load_base: image_load_base, tls_image, + unwind_info, + }) +} + +fn extract_unwind_info( + elf: &mut ElfStream>, + image_load_base: usize, + vaddr_min: usize, +) -> Option { + let eh_frame_hdr = elf + .segments() + .iter() + .find(|e| e.p_type == elf::abi::PT_GNU_EH_FRAME)?; + let eh_frame_hdr_size = eh_frame_hdr.p_memsz as usize; + let eh_frame_hdr = eh_frame_hdr.p_vaddr as usize + image_load_base - vaddr_min; + let (sections, strtab) = elf.section_headers_with_strtab().ok()?; + let strtab = strtab?; + + let eh_frame_size = sections + .iter() + .find(|e| { + strtab + .get(e.sh_name as usize) + .map(|name| name == ".eh_frame") + .unwrap_or(false) + }) + .map(|e| e.sh_size as usize)?; + + Some(UnwindInfo { + eh_frame_hdr, + eh_frame_size, + eh_frame_hdr_size, }) } diff --git a/kernel/libk/src/task/binary/mod.rs b/kernel/libk/src/task/binary/mod.rs index 4caaf390..b5ffa62b 100644 --- a/kernel/libk/src/task/binary/mod.rs +++ b/kernel/libk/src/task/binary/mod.rs @@ -197,6 +197,20 @@ where val: tls_image.full_size as _, }); } + if let Some(info) = image.unwind_info.as_ref() { + auxv.push(AuxValue { + tag: auxv::EH_FRAME_HDR, + val: info.eh_frame_hdr as _, + }); + auxv.push(AuxValue { + tag: auxv::EH_FRAME_SIZE, + val: info.eh_frame_size as _, + }); + auxv.push(AuxValue { + tag: auxv::EH_FRAME_HDR_SIZE, + val: info.eh_frame_hdr_size as _, + }); + } let argument = setup_program_env(space, virt_args_base, args, envs, &auxv)?; diff --git a/kernel/libk/src/task/process.rs b/kernel/libk/src/task/process.rs index ec181ff4..ce230153 100644 --- a/kernel/libk/src/task/process.rs +++ b/kernel/libk/src/task/process.rs @@ -48,6 +48,13 @@ pub struct ProcessManager { processes: IrqSafeRwLock>>, } +#[derive(Clone)] +pub struct UnwindInfo { + pub eh_frame_hdr: usize, + pub eh_frame_hdr_size: usize, + pub eh_frame_size: usize, +} + /// Describes information about a program's image in memory #[derive(Clone)] pub struct ProcessImage { @@ -57,6 +64,8 @@ pub struct ProcessImage { pub entry: usize, /// TLS master copy, if any pub tls_image: Option, + /// Unwind info table + pub unwind_info: Option, } pub struct ProcessInner { diff --git a/lib/abi/src/process/mod.rs b/lib/abi/src/process/mod.rs index 34f40401..014ef395 100644 --- a/lib/abi/src/process/mod.rs +++ b/lib/abi/src/process/mod.rs @@ -26,6 +26,10 @@ pub mod auxv { pub const TLS_ALREADY_INITIALIZED: u64 = 0x86; + pub const EH_FRAME_HDR: u64 = 0x60; + pub const EH_FRAME_HDR_SIZE: u64 = 0x61; + pub const EH_FRAME_SIZE: u64 = 0x62; + pub const NULL: u64 = 0x00; } diff --git a/lib/unwind/Cargo.toml b/lib/unwind/Cargo.toml new file mode 100644 index 00000000..e64de359 --- /dev/null +++ b/lib/unwind/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "unwind" +version = "0.1.0" +edition = "2021" + +[dependencies] +yggdrasil-rt = { path = "../runtime" } +gimli = { version = "0.31.1", default-features = false, features = ["read"] } + +[lints] +workspace = true diff --git a/lib/unwind/src/arch/mod.rs b/lib/unwind/src/arch/mod.rs new file mode 100644 index 00000000..ab567067 --- /dev/null +++ b/lib/unwind/src/arch/mod.rs @@ -0,0 +1,4 @@ +#[cfg(target_arch = "x86_64")] +mod x86_64; +#[cfg(target_arch = "x86_64")] +pub use x86_64::{host_endian, HostEndian, RegisterSet}; diff --git a/lib/unwind/src/arch/x86_64.rs b/lib/unwind/src/arch/x86_64.rs new file mode 100644 index 00000000..ee2e4e17 --- /dev/null +++ b/lib/unwind/src/arch/x86_64.rs @@ -0,0 +1,93 @@ +use core::{ + mem::MaybeUninit, + ops::{Index, IndexMut}, +}; + +use gimli::{LittleEndian, X86_64}; + +pub type HostEndian = LittleEndian; + +pub fn host_endian() -> HostEndian { + LittleEndian +} + +#[derive(Default)] +pub struct RegisterSet { + pub rip: Option, + pub rsp: Option, + pub rbp: Option, + pub ret: Option, +} + +#[derive(Debug)] +#[repr(C)] +struct UnwindEntryInfo { + rip: usize, + rbp: usize, + rsp: usize, +} + +impl RegisterSet { + pub fn current() -> Self { + let mut cx = MaybeUninit::uninit(); + let cx = unsafe { get_context(&mut cx) }; + + Self { + rip: Some(cx.rip as _), + rsp: Some(cx.rsp as _), + rbp: Some(cx.rbp as _), + ret: None, + } + } + + pub fn iter() -> impl Iterator { + [X86_64::RA, X86_64::RBP, X86_64::RSP].into_iter() + } +} + +impl Index for RegisterSet { + type Output = Option; + + fn index(&self, index: gimli::Register) -> &Self::Output { + match index { + X86_64::RA => &self.ret, + X86_64::RSP => &self.rsp, + X86_64::RBP => &self.rbp, + _ => panic!("Unhandled register"), + } + } +} + +impl IndexMut for RegisterSet { + fn index_mut(&mut self, index: gimli::Register) -> &mut Self::Output { + match index { + X86_64::RA => &mut self.ret, + X86_64::RSP => &mut self.rsp, + X86_64::RBP => &mut self.rbp, + _ => panic!("Unhandled register"), + } + } +} + +#[naked] +unsafe extern "C" fn get_context(unw: &mut MaybeUninit) -> &UnwindEntryInfo { + // %rsp: return address (%rip) + // %rdi: *mut UnwindInfo unw + // %rax clobbered + core::arch::naked_asm!( + r#" + // unw.rip = [%rsp] + mov (%rsp), %rax + mov %rax, 0(%rdi) + // unw.rsp = %rsp + 8 + mov %rsp, %rax + add $8, %rax + mov %rax, 16(%rdi) + // unw.rbp = %rbp + mov %rbp, 8(%rdi) + mov %rdi, %rax + ret + "#, + options(att_syntax) + ) +} diff --git a/lib/unwind/src/lib.rs b/lib/unwind/src/lib.rs new file mode 100644 index 00000000..01e0c721 --- /dev/null +++ b/lib/unwind/src/lib.rs @@ -0,0 +1,178 @@ +#![no_std] +#![feature(naked_functions)] + +extern crate alloc; + +use core::{ptr, slice}; + +use alloc::boxed::Box; +use gimli::{ + BaseAddresses, CfaRule, EhFrame, EhFrameHdr, EhHdrTable, EndianSlice, ParsedEhFrameHdr, + Pointer, RegisterRule, StoreOnHeap, UnwindContext, UnwindSection, +}; + +mod arch; + +#[derive(Debug)] +pub enum Error { + NoPc, + NoReturnAddress, + NoUnwindInfo(gimli::Error, u64), + UnknownCfaRuleRegister(gimli::Register), +} + +pub struct UnwindBeginInfo { + pub eh_frame_hdr: *const (), + pub eh_frame_hdr_size: usize, + pub eh_frame_size: usize, +} + +struct EhInfo { + base_addrs: BaseAddresses, + #[allow(unused)] + hdr: Option<&'static ParsedEhFrameHdr>>, + hdr_table: Option>>, + eh_frame: EhFrame>, +} + +struct Unwinder { + eh_info: EhInfo, + unwind_ctx: UnwindContext, + regs: arch::RegisterSet, + cfa: u64, + is_first: bool, +} + +#[derive(Debug)] +pub struct CallFrame { + pub pc: u64, + pub start_address: u64, + pub end_address: u64, +} + +impl Unwinder { + fn new(eh_info: EhInfo, regs: arch::RegisterSet) -> Self { + Self { + eh_info, + regs, + unwind_ctx: UnwindContext::new(), + cfa: 0, + is_first: true, + } + } + + fn next(&mut self) -> Result, Error> { + let pc = self.regs.rip.ok_or(Error::NoPc)?; + if self.is_first { + self.is_first = false; + return Ok(Some(CallFrame { + pc, + start_address: 0, + end_address: 0, + })); + } + let hdr_table = self.eh_info.hdr_table.as_ref().unwrap(); + let row = hdr_table + .unwind_info_for_address( + &self.eh_info.eh_frame, + &self.eh_info.base_addrs, + &mut self.unwind_ctx, + pc, + |section, bases, offset| section.cie_from_offset(bases, offset), + ) + .map_err(|err| Error::NoUnwindInfo(err, pc))?; + + match row.cfa() { + CfaRule::RegisterAndOffset { register, offset } => { + let reg_val = + self.regs[*register].ok_or(Error::UnknownCfaRuleRegister(*register))?; + self.cfa = (reg_val as i64 + *offset) as u64; + } + CfaRule::Expression(_) => todo!(), + } + + for reg in arch::RegisterSet::iter() { + match row.register(reg) { + RegisterRule::Undefined => self.regs[reg] = None, + RegisterRule::SameValue => (), + RegisterRule::Offset(offset) => { + let ptr = (self.cfa as i64 + offset) as u64 as *const usize; + self.regs[reg] = Some(unsafe { ptr.read() } as u64); + } + _ => todo!(), + } + } + + let pc = self.regs.ret.ok_or(Error::NoReturnAddress)? - 1; + self.regs.rip = Some(pc); + self.regs.rsp = Some(self.cfa); + + Ok(Some(CallFrame { + pc, + start_address: row.start_address(), + end_address: row.end_address(), + })) + } +} + +impl EhInfo { + unsafe fn from_eh_frame_hdr(begin_info: UnwindBeginInfo) -> Self { + let mut base_addrs = BaseAddresses::default(); + base_addrs = base_addrs.set_eh_frame_hdr(begin_info.eh_frame_hdr.addr() as _); + + let hdr = Box::leak(Box::new( + EhFrameHdr::new( + unsafe { + slice::from_raw_parts( + begin_info.eh_frame_hdr.cast(), + begin_info.eh_frame_hdr_size, + ) + }, + arch::host_endian(), + ) + .parse(&base_addrs, size_of::() as _) + .unwrap(), + )); + + let eh_frame: *mut u8 = match hdr.eh_frame_ptr() { + Pointer::Direct(ptr) => ptr::with_exposed_provenance_mut(ptr as usize), + _ => unimplemented!(), + }; + + base_addrs = base_addrs.set_eh_frame(eh_frame.addr() as u64); + + let eh_frame = EhFrame::new( + unsafe { slice::from_raw_parts(eh_frame, begin_info.eh_frame_size) }, + arch::host_endian(), + ); + + Self { + base_addrs, + hdr: Some(hdr), + hdr_table: Some(hdr.table().unwrap()), + eh_frame, + } + } +} + +pub fn backtrace bool>(begin_info: UnwindBeginInfo, mut handler: F) { + let regs = arch::RegisterSet::current(); + let eh_info = unsafe { EhInfo::from_eh_frame_hdr(begin_info) }; + let mut unwinder = Unwinder::new(eh_info, regs); + loop { + match unwinder.next() { + Ok(Some(frame)) => { + if !handler(frame) { + break; + } + } + Ok(None) => { + break; + } + Err(error) => { + yggdrasil_rt::debug_trace!("Unwind error: {error:?}"); + break; + } + } + } +} diff --git a/userspace/lib/ygglibc/Cargo.lock b/userspace/lib/ygglibc/Cargo.lock index b6e10f72..0eb18f2d 100644 --- a/userspace/lib/ygglibc/Cargo.lock +++ b/userspace/lib/ygglibc/Cargo.lock @@ -194,6 +194,12 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + [[package]] name = "hashbrown" version = "0.15.1" @@ -459,6 +465,14 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "unwind" +version = "0.1.0" +dependencies = [ + "gimli", + "yggdrasil-rt", +] + [[package]] name = "utf8parse" version = "0.2.2" @@ -585,6 +599,7 @@ dependencies = [ "cbindgen", "chrono", "libyalloc", + "unwind", "yggdrasil-abi", "yggdrasil-rt", ] diff --git a/userspace/lib/ygglibc/Cargo.toml b/userspace/lib/ygglibc/Cargo.toml index dfeab444..7818b761 100644 --- a/userspace/lib/ygglibc/Cargo.toml +++ b/userspace/lib/ygglibc/Cargo.toml @@ -10,6 +10,7 @@ crate-type = ["cdylib", "staticlib"] yggdrasil-rt = { path = "../../../lib/runtime", features = ["__tls_get_addr"] } yggdrasil-abi = { path = "../../../lib/abi", features = ["alloc", "bytemuck"] } libyalloc = { path = "../../../lib/libyalloc" } +unwind = { path = "../../../lib/unwind" } bitflags = "2.6.0" chrono = { version = "0.4.31", default-features = false } diff --git a/userspace/lib/ygglibc/etc/x86_64-unknown-none.json b/userspace/lib/ygglibc/etc/x86_64-unknown-none.json index ee5b4fa7..f76b4454 100644 --- a/userspace/lib/ygglibc/etc/x86_64-unknown-none.json +++ b/userspace/lib/ygglibc/etc/x86_64-unknown-none.json @@ -13,6 +13,8 @@ "executables": true, "panic-strategy": "abort", "dynamic-linking": true, + "eh-frame-header": true, + "default-uwtable": true, "relocation-model": "pic", "position-independent-executables": true, "crt-static-allows-dylibs": true, diff --git a/userspace/lib/ygglibc/src/env.rs b/userspace/lib/ygglibc/src/env.rs index f3776712..90ca2f9e 100644 --- a/userspace/lib/ygglibc/src/env.rs +++ b/userspace/lib/ygglibc/src/env.rs @@ -16,6 +16,7 @@ use crate::{ errno::Errno, string::{mem::memcpy, str::strlen} }, thread, + unwind, util::PointerExt, }; @@ -227,6 +228,7 @@ pub fn handle_kernel_argument(arg: usize) -> Vec { // Setup TLS from argument thread::init_main_thread(arg); + unwind::init_from_auxv(arg); args } diff --git a/userspace/lib/ygglibc/src/lib.rs b/userspace/lib/ygglibc/src/lib.rs index 8205b3c4..9debd420 100644 --- a/userspace/lib/ygglibc/src/lib.rs +++ b/userspace/lib/ygglibc/src/lib.rs @@ -55,6 +55,7 @@ mod ssp; mod thread; mod types; mod util; +mod unwind; pub mod headers; @@ -76,6 +77,8 @@ unsafe extern "C" fn __ygglibc_entry( } c_args.push(null()); + panic!("Test panic"); + let status = main( args.len().try_into().unwrap(), c_args.as_ptr(), diff --git a/userspace/lib/ygglibc/src/panic.rs b/userspace/lib/ygglibc/src/panic.rs index eb9f887a..26cca04f 100644 --- a/userspace/lib/ygglibc/src/panic.rs +++ b/userspace/lib/ygglibc/src/panic.rs @@ -91,6 +91,12 @@ fn panic_handler(pi: &core::panic::PanicInfo) -> ! { } } + writeln!(printer, "Stack trace:"); + crate::unwind::unwind(|frame| { + writeln!(printer, "* {:#x}", frame.pc); + true + }); + printer.flush(); } 1 => { diff --git a/userspace/lib/ygglibc/src/unwind.rs b/userspace/lib/ygglibc/src/unwind.rs new file mode 100644 index 00000000..4b367736 --- /dev/null +++ b/userspace/lib/ygglibc/src/unwind.rs @@ -0,0 +1,40 @@ +use core::{ptr, sync::atomic::{AtomicUsize, Ordering}}; + +use unwind::UnwindBeginInfo; +use yggdrasil_rt::{ + process::{auxv, AuxValue, ProgramArgumentInner}, + sync::once::Once, +}; + +static EH_FRAME_HDR: Once = Once::uninit(); +static EH_FRAME_HDR_SIZE: AtomicUsize = AtomicUsize::new(0); +static EH_FRAME_SIZE: AtomicUsize = AtomicUsize::new(0); + +pub fn init_from_auxv(arg: &ProgramArgumentInner) { + let mut auxv = arg.auxv(); + for entry in auxv { + match entry.tag { + auxv::EH_FRAME_HDR => { + EH_FRAME_HDR.init_with(|| entry.val as usize); + } + auxv::EH_FRAME_SIZE => { + EH_FRAME_SIZE.store(entry.val as usize, Ordering::Release); + } + auxv::EH_FRAME_HDR_SIZE => { + EH_FRAME_HDR_SIZE.store(entry.val as usize, Ordering::Release); + } + _ => (), + } + } +} + +pub fn unwind bool>(handler: F) { + if let Some(&eh_frame_hdr) = EH_FRAME_HDR.try_get() { + let info = UnwindBeginInfo { + eh_frame_hdr: ptr::with_exposed_provenance(eh_frame_hdr), + eh_frame_size: EH_FRAME_SIZE.load(Ordering::Acquire), + eh_frame_hdr_size: EH_FRAME_HDR_SIZE.load(Ordering::Acquire) + }; + unwind::backtrace(info, handler); + } +}