Compare commits

...

1 Commits

Author SHA1 Message Date
7e5979c1a2 unwind: basic unwinding for x86-64 2024-12-05 22:21:46 +02:00
17 changed files with 430 additions and 2 deletions

8
Cargo.lock generated
View File

@ -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"

View File

@ -15,7 +15,8 @@ members = [
"lib/abi",
"lib/libyalloc",
"lib/runtime",
"lib/qemu"
"lib/qemu",
"lib/unwind"
]
[workspace.dependencies]

View File

@ -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<F: Read + Seek>(
// 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<F: Read + Seek>(
ip_offset,
load_base: image_load_base,
tls_image,
unwind_info,
})
}
fn extract_unwind_info<F: Read + Seek>(
elf: &mut ElfStream<AnyEndian, FileReader<F>>,
image_load_base: usize,
vaddr_min: usize,
) -> Option<UnwindInfo> {
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,
})
}

View File

@ -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)?;

View File

@ -48,6 +48,13 @@ pub struct ProcessManager {
processes: IrqSafeRwLock<BTreeMap<ProcessId, Arc<Process>>>,
}
#[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<TlsImage>,
/// Unwind info table
pub unwind_info: Option<UnwindInfo>,
}
pub struct ProcessInner {

View File

@ -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;
}

11
lib/unwind/Cargo.toml Normal file
View File

@ -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

View File

@ -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};

View File

@ -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<u64>,
pub rsp: Option<u64>,
pub rbp: Option<u64>,
pub ret: Option<u64>,
}
#[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<Item = gimli::Register> {
[X86_64::RA, X86_64::RBP, X86_64::RSP].into_iter()
}
}
impl Index<gimli::Register> for RegisterSet {
type Output = Option<u64>;
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<gimli::Register> 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>) -> &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)
)
}

178
lib/unwind/src/lib.rs Normal file
View File

@ -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<EndianSlice<'static, arch::HostEndian>>>,
hdr_table: Option<EhHdrTable<'static, EndianSlice<'static, arch::HostEndian>>>,
eh_frame: EhFrame<EndianSlice<'static, arch::HostEndian>>,
}
struct Unwinder {
eh_info: EhInfo,
unwind_ctx: UnwindContext<usize, StoreOnHeap>,
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<Option<CallFrame>, 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::<usize>() 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<F: FnMut(CallFrame) -> 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;
}
}
}
}

View File

@ -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",
]

View File

@ -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 }

View File

@ -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,

View File

@ -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<CString> {
// Setup TLS from argument
thread::init_main_thread(arg);
unwind::init_from_auxv(arg);
args
}

View File

@ -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(),

View File

@ -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 => {

View File

@ -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<usize> = 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<F: FnMut(unwind::CallFrame) -> 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);
}
}