Compare commits
1 Commits
master
...
feature/un
Author | SHA1 | Date | |
---|---|---|---|
7e5979c1a2 |
8
Cargo.lock
generated
8
Cargo.lock
generated
@ -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"
|
||||
|
@ -15,7 +15,8 @@ members = [
|
||||
"lib/abi",
|
||||
"lib/libyalloc",
|
||||
"lib/runtime",
|
||||
"lib/qemu"
|
||||
"lib/qemu",
|
||||
"lib/unwind"
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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)?;
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
11
lib/unwind/Cargo.toml
Normal 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
|
4
lib/unwind/src/arch/mod.rs
Normal file
4
lib/unwind/src/arch/mod.rs
Normal 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};
|
93
lib/unwind/src/arch/x86_64.rs
Normal file
93
lib/unwind/src/arch/x86_64.rs
Normal 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
178
lib/unwind/src/lib.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
userspace/lib/ygglibc/Cargo.lock
generated
15
userspace/lib/ygglibc/Cargo.lock
generated
@ -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",
|
||||
]
|
||||
|
@ -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 }
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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 => {
|
||||
|
40
userspace/lib/ygglibc/src/unwind.rs
Normal file
40
userspace/lib/ygglibc/src/unwind.rs
Normal 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);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user