diff --git a/src/arch.rs b/src/arch.rs index 886befb..32ca5c0 100644 --- a/src/arch.rs +++ b/src/arch.rs @@ -1,5 +1,5 @@ +use core::arch::asm; use core::ffi; -use std::arch::asm; pub fn get_rbp() -> *const usize { let mut out; diff --git a/src/dwarf/divination.rs b/src/dwarf/divination.rs new file mode 100644 index 0000000..aab7a91 --- /dev/null +++ b/src/dwarf/divination.rs @@ -0,0 +1,69 @@ +//! # divination +//! +//! the practice of seeking knowledge of the future or the unknown by supernatural means. +//! +//! we ask supernatural means (the dynamic linker) for knowledge of the future +//! (where we will find the dwarves) +//! +//! first, we ask the dynamic linker to give us the `.eh_frame` for the current binary using +//! the GNU extension (`_dl_find_object`)[https://www.gnu.org/software/libc/manual/html_node/Dynamic-Linker-Introspection.html]. +//! then, we parse that as beautiful DWARF call frame information, as god (or rather, the x86-64 psABI) intended. + +use core::ffi; + +use crate::stdext::with_last_os_error_str; + +#[allow(non_camel_case_types)] +#[repr(C)] +struct dl_find_object { + dlfo_flags: ffi::c_ulonglong, + dlfo_map_start: *const ffi::c_void, + dlfo_map_end: *const ffi::c_void, + dlf_link_map: *const ffi::c_void, + dlfo_eh_frame: *const ffi::c_void, +} + +extern "C" { + fn _dl_find_object(address: *const ffi::c_void, result: *mut dl_find_object) -> ffi::c_int; +} + +#[derive(Debug, Clone, Copy)] +pub struct DwarfInfo { + /// The text segment + map: *const [u8], + /// PT_GNU_EH_FRAME + dwarf: *const u8, +} + +pub fn dwarf_info(addr: *const ffi::c_void) -> Option { + unsafe { + let mut out = core::mem::zeroed(); + let ret = _dl_find_object(addr, &mut out); + trace!("dl_find_object returned {ret}"); + if ret != 0 { + with_last_os_error_str(|err| trace!("dl_find_object error: {err}")); + return None; + } + if out.dlfo_eh_frame.is_null() { + return None; + } + + let text_len = out.dlfo_map_end as usize - out.dlfo_map_start as usize; + trace!( + "dwarf info; map: ({:p}, {:x}), dwarf: {:p}", + out.dlfo_map_start, + text_len, + out.dlfo_eh_frame + ); + + if !(out.dlfo_map_start..out.dlfo_map_end).contains(&addr) { + trace!("dl_find_object returned object out of range for addr: {addr:p}"); + return None; + } + + Some(DwarfInfo { + map: core::ptr::slice_from_raw_parts(out.dlfo_map_start as _, text_len), + dwarf: out.dlfo_eh_frame as _, + }) + } +} diff --git a/src/dwarf/mod.rs b/src/dwarf/mod.rs index 7fd9445..89d9ce2 100644 --- a/src/dwarf/mod.rs +++ b/src/dwarf/mod.rs @@ -1,72 +1,11 @@ //! this implements the stuff necessary to get the uwutables for actual unwinding //! -//! # how it works -//! first, we ask the dynamic linker to give us the `.eh_frame` for the current binary using -//! the GNU extension (`_dl_find_object`)[https://www.gnu.org/software/libc/manual/html_node/Dynamic-Linker-Introspection.html]. -//! then, we parse that as beautiful DWARF call frame information, as god (or rather, the x86-64 psABI) intended. -//! //! for this we need a DWARF parser and a DWARF call frame information interpreter (yes, that shit is basically a programming //! language). See https://dwarfstd.org/doc/DWARF5.pdf for more information if more information is desired. +mod divination; mod parse; -use core::ffi; -use std::fmt::Debug; - -#[allow(non_camel_case_types)] -#[repr(C)] -struct dl_find_object { - dlfo_flags: ffi::c_ulonglong, - dlfo_map_start: *const ffi::c_void, - dlfo_map_end: *const ffi::c_void, - dlf_link_map: *const ffi::c_void, - dlfo_eh_frame: *const ffi::c_void, -} - -// -extern "C" { - fn _dl_find_object(address: *const ffi::c_void, result: *mut dl_find_object) -> ffi::c_int; -} - -#[derive(Debug, Clone, Copy)] -pub struct DwarfInfo { - /// The text segment - map: *const [u8], - /// PT_GNU_EH_FRAME - dwarf: *const u8, -} - -pub fn dwarf_info(addr: *const ffi::c_void) -> Option { - unsafe { - let mut out = core::mem::zeroed(); - let ret = _dl_find_object(addr, &mut out); - trace!("dl_find_object returned {ret}"); - if ret != 0 { - trace!("dl_find_object error: {}", std::io::Error::last_os_error()); - return None; - } - if out.dlfo_eh_frame.is_null() { - return None; - } - - let text_len = out.dlfo_map_end as usize - out.dlfo_map_start as usize; - trace!( - "dwarf info; map: ({:p}, {:x}), dwarf: {:p}", - out.dlfo_map_start, - text_len, - out.dlfo_eh_frame - ); - - if !(out.dlfo_map_start..out.dlfo_map_end).contains(&addr) { - trace!("dl_find_object returned object out of range for addr: {addr:p}"); - return None; - } - - Some(DwarfInfo { - map: core::ptr::slice_from_raw_parts(out.dlfo_map_start as _, text_len), - dwarf: out.dlfo_eh_frame as _, - }) - } -} +pub use divination::{dwarf_info, DwarfInfo}; pub fn uwutables(_dwarf_info: DwarfInfo) {} diff --git a/src/identify.rs b/src/identify.rs index 17936ad..3544bd1 100644 --- a/src/identify.rs +++ b/src/identify.rs @@ -1,8 +1,8 @@ -use std::ffi::CStr; +use core::ffi::CStr; pub fn identify(addr: usize) -> Option<&'static CStr> { unsafe { - let mut info: libc::Dl_info = std::mem::zeroed(); + let mut info: libc::Dl_info = core::mem::zeroed(); libc::dladdr(addr as _, &mut info); diff --git a/src/lib.rs b/src/lib.rs index 9fcd1ab..8b5ec4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,31 +1,30 @@ +#![no_std] #![allow(dead_code)] -use std::sync::atomic::AtomicPtr; +use core::sync::atomic::AtomicPtr; -macro_rules! trace { - ($($tt:tt)*) => { - eprintln!("UWUWIND TRACE | uwuwind/{}:{}: {}", file!(), line!(), format_args!($($tt)*)); - }; -} +// Get the macros into our local prelude. +#[macro_use] +mod stdext; pub mod uw; mod arch; mod dwarf; mod identify; + mod walk; #[allow(nonstandard_style)] pub unsafe extern "C" fn _UnwindRaiseException( exception_object: *mut uw::_Unwind_Exception, ) -> uw::_Unwind_Reason_Code { - println!("someone raised an exception with addr {exception_object:p}"); - + trace!("someone raised an exception with addr {exception_object:p}"); crate::dwarf::dwarf_info(arch::get_rip() as _); // walk::fp::walk(); - std::process::abort(); + stdext::abort(); } // This is normally provided by the language runtime through the unwind info block. @@ -33,6 +32,6 @@ pub unsafe extern "C" fn _UnwindRaiseException( static PERSONALITY_ROUTINE: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut()); pub unsafe fn set_personality_routine(routine: uw::PersonalityRoutine) { - let ptr: *mut () = std::mem::transmute(routine); - PERSONALITY_ROUTINE.store(ptr, std::sync::atomic::Ordering::Relaxed); + let ptr: *mut () = core::mem::transmute(routine); + PERSONALITY_ROUTINE.store(ptr, core::sync::atomic::Ordering::Relaxed); } diff --git a/src/stdext.rs b/src/stdext.rs new file mode 100644 index 0000000..72977dd --- /dev/null +++ b/src/stdext.rs @@ -0,0 +1,68 @@ +use core::fmt::Write; +use core::{ffi, fmt}; + +pub struct LibCStdoutWriter; + +impl Write for LibCStdoutWriter { + fn write_str(&mut self, mut s: &str) -> fmt::Result { + loop { + let r = unsafe { libc::write(libc::STDOUT_FILENO, s.as_ptr().cast(), s.len()) }; + if r < 0 { + return Err(fmt::Error); + } + if r == 0 { + return Ok(()); + } + s = &s[(r as usize)..]; + } + } +} + +pub fn print(args: fmt::Arguments<'_>) -> fmt::Result { + write!(LibCStdoutWriter, "{}", args) +} + +macro_rules! print { + ($($tt:tt)*) => {{ + $crate::stdext::print(format_args!($($tt)*)).unwrap(); + }}; +} + +macro_rules! trace { + ($($tt:tt)*) => { + print!("UWUWIND TRACE | uwuwind/{}:{}: {}\n", file!(), line!(), format_args!($($tt)*)) + }; +} + +pub(crate) use trace; + +pub(crate) fn abort() -> ! { + // SAFETY: We abort. + unsafe { libc::abort() }; +} + +fn errno() -> i32 { + // SAFETY: Surely errno_location would be valid, right? + unsafe { *libc::__errno_location() } +} + +pub(crate) fn with_last_os_error_str(f: impl FnOnce(&str) -> R) -> R { + let mut buf: [u8; 512] = [0; 512]; + + extern "C" { + // the libc crate only has the definition for the POSIX version, but a GNU system has the GNU version. + fn strerror_r(errnum: i32, buf: *mut ffi::c_char, buflen: usize) -> i32; + } + + // SAFETY: Our buffer length is passed correctly + let error = unsafe { libc::strerror_r(errno(), buf.as_mut_ptr().cast(), buf.len()) }; + // SAFETY: strerror_r writes the string to buf, even if it didnt write anything, we did zero init it. + let cstr = if error != 0 { + ffi::CStr::from_bytes_with_nul(b"\n").unwrap() + } else { + unsafe { ffi::CStr::from_ptr(buf.as_ptr().cast()) } + }; + f(cstr + .to_str() + .unwrap_or("")) +} diff --git a/src/walk/fp.rs b/src/walk/fp.rs index 9952431..fd39fec 100644 --- a/src/walk/fp.rs +++ b/src/walk/fp.rs @@ -1,21 +1,22 @@ //! Test frame pointer walker. Not very good. use crate::arch::get_rbp; +use crate::stdext::trace; pub(crate) unsafe fn walk() { let mut current_rbp = get_rbp(); loop { - println!("walk... rbp={current_rbp:p}"); + trace!("walk... rbp={current_rbp:p}"); let return_addr = current_rbp.add(1).read() as *const usize; - println!("walk... return_addr={return_addr:p}"); + trace!("walk... return_addr={return_addr:p}"); - println!( + trace!( "walk... frame={:?}", crate::identify::identify(return_addr as usize) ); - println!("no read yet"); + trace!("no read yet"); current_rbp = current_rbp.read() as *const usize; }