commit 66fedc6178fac78006d0a2aa7f33d57283d987d4 Author: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Wed May 17 20:09:40 2023 +0200 uwuwind diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..e83e3e7 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,5 @@ +[target.x86_64-unknown-linux-gnu] +rustflags = [ + "-Cforce-frame-pointers=yes", # for the uwuwind fp stack walker + "-Clink-arg=-Wl,-E" # for dladdr +] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c41cc9e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3723bc5 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,72 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "gimli" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "libc" +version = "0.2.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "uwuwind" +version = "0.1.0" +dependencies = [ + "gimli", + "libc", + "memmap2", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c053f7c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[workspace] + +[package] +name = "uwuwind" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +gimli = "0.27.2" +libc = { version = "0.2.140", default-features = false, features = ["extra_traits"] } +memmap2 = "0.5.10" + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" diff --git a/a.cpp b/a.cpp new file mode 100644 index 0000000..1d1b89d --- /dev/null +++ b/a.cpp @@ -0,0 +1,61 @@ + +bool findUnwindSections(int_t targetAddr, UnwindInfoSections &info) +{ + // Use DLFO_STRUCT_HAS_EH_DBASE to determine the existence of + // `_dl_find_object`. Use _LIBUNWIND_SUPPORT_DWARF_INDEX, because libunwind + // support for _dl_find_object on other unwind formats is not implemented, + // yet. +#if defined(DLFO_STRUCT_HAS_EH_DBASE) & defined(_LIBUNWIND_SUPPORT_DWARF_INDEX) + // We expect `_dl_find_object` to return PT_GNU_EH_FRAME. +#if DLFO_EH_SEGMENT_TYPE != PT_GNU_EH_FRAME +#error _dl_find_object retrieves an unexpected section type +#endif + // We look-up `dl_find_object` dynamically at runtime to ensure backwards + // compatibility with earlier version of glibc not yet providing it. On older + // systems, we gracefully fallback to `dl_iterate_phdr`. Cache the pointer + // so we only look it up once. Do manual lock to avoid _cxa_guard_acquire. + static decltype(_dl_find_object) *dlFindObject; + static bool dlFindObjectChecked = false; + if (!dlFindObjectChecked) + { + dlFindObject = reinterpret_cast( + dlsym(RTLD_DEFAULT, "_dl_find_object")); + dlFindObjectChecked = true; + } + // Try to find the unwind info using `dl_find_object` + dl_find_object findResult; + if (dlFindObject && dlFindObject((void *)targetAddr, &findResult) == 0) + { + if (findResult.dlfo_eh_frame == nullptr) + { + // Found an entry for `targetAddr`, but there is no unwind info. + return false; + } + info.dso_base = reinterpret_cast(findResult.dlfo_map_start); + info.text_segment_length = static_cast( + (char *)findResult.dlfo_map_end - (char *)findResult.dlfo_map_start); + + // Record the start of PT_GNU_EH_FRAME. + info.dwarf_index_section = + reinterpret_cast(findResult.dlfo_eh_frame); + // `_dl_find_object` does not give us the size of PT_GNU_EH_FRAME. + // Setting length to `SIZE_MAX` effectively disables all range checks. + info.dwarf_index_section_length = SIZE_MAX; + EHHeaderParser::EHHeaderInfo hdrInfo; + if (!EHHeaderParser::decodeEHHdr( + *this, info.dwarf_index_section, info.dwarf_index_section_length, + hdrInfo)) + { + return false; + } + // Record the start of the FDE and use SIZE_MAX to indicate that we do + // not know the end address. + info.dwarf_section = hdrInfo.eh_frame_ptr; + info.dwarf_section_length = SIZE_MAX; + return true; + } +#endif + dl_iterate_cb_data cb_data = {this, &info, targetAddr}; + int found = dl_iterate_phdr(findUnwindSectionsByPhdr, &cb_data); + return static_cast(found); +} diff --git a/src/arch.rs b/src/arch.rs new file mode 100644 index 0000000..886befb --- /dev/null +++ b/src/arch.rs @@ -0,0 +1,26 @@ +use core::ffi; +use std::arch::asm; + +pub fn get_rbp() -> *const usize { + let mut out; + unsafe { + asm!( + "mov {out}, rbp", + out = out(reg) out, + options(nostack, readonly) + ); + } + out +} + +pub fn get_rip() -> *const ffi::c_void { + let mut out; + unsafe { + asm!( + "lea {out}, [rip]", + out = out(reg) out, + options(nostack, readonly), + ); + } + out +} diff --git a/src/dwarf.rs b/src/dwarf.rs new file mode 100644 index 0000000..555292b --- /dev/null +++ b/src/dwarf.rs @@ -0,0 +1,57 @@ +use core::ffi; +use std::{ffi::CStr, fmt::Debug}; + +use gimli::UnwindTable; + +#[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)] +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 { + 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 + ); + Some(DwarfInfo { + map: core::ptr::slice_from_raw_parts(out.dlfo_map_start as _, text_len), + dwarf: out.dlfo_eh_frame as _, + }) + } +} + +pub fn uwutables() { + //let UnwindTable; +} \ No newline at end of file diff --git a/src/identify.rs b/src/identify.rs new file mode 100644 index 0000000..17936ad --- /dev/null +++ b/src/identify.rs @@ -0,0 +1,36 @@ +use std::ffi::CStr; + +pub fn identify(addr: usize) -> Option<&'static CStr> { + unsafe { + let mut info: libc::Dl_info = std::mem::zeroed(); + + libc::dladdr(addr as _, &mut info); + + if !info.dli_sname.is_null() { + let sym_name = CStr::from_ptr(info.dli_sname); + return Some(sym_name); + } + + /* + let parse = |str| usize::from_str_radix(str, 16).ok(); + let maps = std::fs::read_to_string("/proc/self/maps").unwrap(); + maps.lines() + .filter_map(|line| { + let mut ws = line.split_ascii_whitespace(); + + let addr_range = ws.next()?; + let mut addr_range = addr_range.split("-"); + let addr_range = (parse(addr_range.next()?)?)..(parse(addr_range.next()?)?); + + if addr_range.contains(&addr) { + ws.nth(4).map(ToOwned::to_owned) + } else { + None + } + }) + .next() + */ + + None + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9fcd1ab --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,38 @@ +#![allow(dead_code)] + +use std::sync::atomic::AtomicPtr; + +macro_rules! trace { + ($($tt:tt)*) => { + eprintln!("UWUWIND TRACE | uwuwind/{}:{}: {}", file!(), line!(), format_args!($($tt)*)); + }; +} + +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}"); + + crate::dwarf::dwarf_info(arch::get_rip() as _); + + // walk::fp::walk(); + + std::process::abort(); +} + +// This is normally provided by the language runtime through the unwind info block. +// We don't want to access that usually because Rust messes with it :(. +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); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..dcdde56 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,35 @@ +use uwuwind::uw; + +#[repr(C)] +struct Exception { + _uwe: uw::_Unwind_Exception, + uwu: &'static str, +} + +fn main() { + unsafe { + uwuwind::set_personality_routine(personality_routine); + + let exception = Box::into_raw(Box::new(Exception { + _uwe: uw::_Unwind_Exception { + exception_class: 123456, + exception_cleanup0: |_, _| {}, + private_1: 0, + private_2: 0, + }, + uwu: "meow :3", + })); + + uwuwind::_UnwindRaiseException(exception.cast::()); + } +} + +fn personality_routine( + _version: i32, + _actions: uw::_UnwindAction, + _exception_class: u64, + _exception_object: *mut uw::_Unwind_Exception, + _context: *mut uw::_Unwind_Context, +) -> uw::_Unwind_Reason_Code { + uw::_Unwind_Reason_Code::_URC_NO_REASON +} diff --git a/src/uw.rs b/src/uw.rs new file mode 100644 index 0000000..b697e03 --- /dev/null +++ b/src/uw.rs @@ -0,0 +1,52 @@ +#![allow(nonstandard_style)] // Closely follow the spec here + +#[repr(C)] +pub enum _Unwind_Reason_Code { + _URC_NO_REASON = 0, + /// This indicates that a different runtime caught this exception. + /// Nested foreign exceptions, or re-throwing a foreign exception, result in + /// undefined behavior. + _URC_FOREIGN_EXCEPTION_CAUGHT = 1, + /// The personality routine encountered an error during phase 1, other than the specific error codes defined. + _URC_FATAL_PHASE1_ERROR = 3, + /// The personality routine encountered an error during phase 2, for instance a stack corruption. + _URC_FATAL_PHASE2_ERROR = 2, + _URC_NORMAL_STOP = 4, + /// The unwinder encountered the end of the stack during phase 1, without finding a handler. + /// The unwind runtime will not have modified the stack. + /// The C++ runtime will normally call uncaught_exception() in this case + _URC_END_OF_STACK = 5, + _URC_HANDLER_FOUND = 6, + _URC_INSTALL_CONTEXT = 7, + _URC_CONTINUE_UNWIND = 8, +} + +#[repr(C, align(8))] +pub struct _Unwind_Exception { + pub exception_class: u64, + pub exception_cleanup0: _Unwind_Exception_Cleanup_Fn, + pub private_1: u64, + pub private_2: u64, +} + +pub type _Unwind_Exception_Cleanup_Fn = + fn(reason: _Unwind_Reason_Code, exc: *const _Unwind_Exception); + +/// The _Unwind_Context type is an opaque type used to refer to a system-specific data structure used by the system unwinder. +/// This context is created and destroyed by the system, and passed to the personality routine during unwinding +pub struct _Unwind_Context {} + +pub type PersonalityRoutine = fn( + version: i32, + actions: _UnwindAction, + exceptionClass: u64, + exception_object: *mut _Unwind_Exception, + context: *mut _Unwind_Context, +) -> _Unwind_Reason_Code; + +pub type _UnwindAction = i32; + +const _UA_SEARCH_PHASE: _UnwindAction = 1; +const _UA_CLEANUP_PHASE: _UnwindAction = 2; +const _UA_HANDLER_FRAME: _UnwindAction = 4; +const _UA_FORCE_UNWIND: _UnwindAction = 8; diff --git a/src/walk/fp.rs b/src/walk/fp.rs new file mode 100644 index 0000000..9952431 --- /dev/null +++ b/src/walk/fp.rs @@ -0,0 +1,22 @@ +//! Test frame pointer walker. Not very good. + +use crate::arch::get_rbp; + +pub(crate) unsafe fn walk() { + let mut current_rbp = get_rbp(); + loop { + println!("walk... rbp={current_rbp:p}"); + + let return_addr = current_rbp.add(1).read() as *const usize; + println!("walk... return_addr={return_addr:p}"); + + println!( + "walk... frame={:?}", + crate::identify::identify(return_addr as usize) + ); + + println!("no read yet"); + + current_rbp = current_rbp.read() as *const usize; + } +} diff --git a/src/walk/mod.rs b/src/walk/mod.rs new file mode 100644 index 0000000..3cdd361 --- /dev/null +++ b/src/walk/mod.rs @@ -0,0 +1 @@ +pub mod fp; \ No newline at end of file diff --git a/test b/test new file mode 100755 index 0000000..a478771 Binary files /dev/null and b/test differ diff --git a/test.c b/test.c new file mode 100644 index 0000000..93cb891 --- /dev/null +++ b/test.c @@ -0,0 +1,8 @@ +#include +#define __USE_GNU +#include +#include + +int main() { + printf("%d", PT_GNU_EH_FRAME); +} \ No newline at end of file diff --git a/test.rs b/test.rs new file mode 100644 index 0000000..5d26406 --- /dev/null +++ b/test.rs @@ -0,0 +1,3 @@ +fn main() { + panic!(); +} \ No newline at end of file