diff --git a/eh_frame_hdr.bin b/eh_frame_hdr.bin new file mode 100644 index 0000000..28c33db Binary files /dev/null and b/eh_frame_hdr.bin differ diff --git a/src/dwarf/divination.rs b/src/dwarf/divination.rs index a27e65d..b23039b 100644 --- a/src/dwarf/divination.rs +++ b/src/dwarf/divination.rs @@ -9,11 +9,12 @@ //! 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; +#![allow(non_camel_case_types)] + +use core::{ffi, fmt, ptr::addr_of}; use crate::stdext::with_last_os_error_str; -#[allow(non_camel_case_types)] #[repr(C)] struct dl_find_object { dlfo_flags: ffi::c_ulonglong, @@ -29,26 +30,115 @@ extern "C" { #[derive(Debug, Clone, Copy)] pub struct DwarfInfo { - /// PT_GNU_EH_FRAME - pub(crate) eh_frame: *const u8, + /// A pointer to the `PT_GNU_EH_FRAME` segment (the `.eh_frame_hdr` section). + pub(crate) eh_frame_hdr: *const u8, +} + +/// The `.eh_frame_hdr` section. +/// See +/// and . +#[repr(C)] +struct EhFrameHeader { + version: u8, + eh_frame_ptr_enc: EhHeaderEncoded, + fde_count_enc: EhHeaderEncoded, + table_enc: EhHeaderEncoded, + encoded_fields: (), +} + +impl EhFrameHeader { + unsafe fn encoded_fields(&self) -> *const u8 { + addr_of!((*self).encoded_fields).cast::() + } + + unsafe fn eh_frame(&self, pc: *const ffi::c_void) -> Option<*const u8> { + let ValueFormat::DW_EH_PE_sdata4 = self.eh_frame_ptr_enc.format() else { + return None; + }; + let ValueApplication::DW_EH_PE_pcrel = self.eh_frame_ptr_enc.application() else { + return None; + }; + + let eh_frame_ptr = unsafe { self.encoded_fields().cast::().read_unaligned() }; + + Some(pc.cast::().offset(eh_frame_ptr as isize)) + } + + fn fde_count(&self) -> Option { + let ValueFormat::DW_EH_PE_udata4 = self.fde_count_enc.format() else { + return None; + }; + let ValueApplication::DW_EH_PE_absptr = self.fde_count_enc.application() else { + return None; + }; + let fde_count = unsafe { self.encoded_fields().add(4).cast::().read() }; + Some(fde_count as _) + } +} + +impl fmt::Debug for EhFrameHeader { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("EhFrameHeader") + .field("version", &self.version) + .field("eh_frame_ptr_enc", &self.eh_frame_ptr_enc) + .field("fde_count_enc", &self.fde_count_enc) + .field("table_enc", &self.table_enc) + .field("fde_count", &self.fde_count()) + .finish() + } +} + +struct EhHeaderEncoded(u8); +impl EhHeaderEncoded { + fn format(&self) -> ValueFormat { + match self.0 & 0b1111 { + 0x01 => ValueFormat::DW_EH_PE_uleb128, + 0x02 => ValueFormat::DW_EH_PE_udata2, + 0x03 => ValueFormat::DW_EH_PE_udata4, + 0x04 => ValueFormat::DW_EH_PE_udata8, + 0x09 => ValueFormat::DW_EH_PE_sleb128, + 0x0A => ValueFormat::DW_EH_PE_sdata2, + 0x0B => ValueFormat::DW_EH_PE_sdata4, + 0x0C => ValueFormat::DW_EH_PE_sdata8, + _ => panic!("Invalid header value format"), + } + } + fn application(&self) -> ValueApplication { + match self.0 >> 4 { + 0x0 => ValueApplication::DW_EH_PE_absptr, + 0x1 => ValueApplication::DW_EH_PE_pcrel, + 0x2 => ValueApplication::DW_EH_PE_textrel, + 0x3 => ValueApplication::DW_EH_PE_datarel, + 0x4 => ValueApplication::DW_EH_PE_funcrel, + 0x5 => ValueApplication::DW_EH_PE_aligned, + v => panic!("invalid header value application: {v}"), + } + } +} + +impl fmt::Debug for EhHeaderEncoded { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?} | {:?}", self.application(), self.format()) + } } 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}"); + 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() { + trace!("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}", + "dwarf info; map: ({:p}, {:x}), dlfo_map_end: {:p}", out.dlfo_map_start, text_len, out.dlfo_eh_frame @@ -59,8 +149,59 @@ pub fn dwarf_info(addr: *const ffi::c_void) -> Option { return None; } - Some(DwarfInfo { - eh_frame: out.dlfo_eh_frame as _, - }) + let header = &*out.dlfo_eh_frame.cast::(); + if header.version != 1 { + trace!("eh_frame_hdr version is not 1"); + return None; + } + + trace!("eh_frame_hdr: {:#?}", header); + + let Some(ptr) = header.eh_frame(addr) else { + trace!("could not find .eh_frame"); + return None; + }; + trace!("eh_frame pointer: {ptr:?}"); + + trace!("eh_frame start: {:?}", core::slice::from_raw_parts(ptr, 10)); + + crate::stdext::abort(); } } + +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +enum ValueFormat { + /// Unsigned value is encoded using the Little Endian Base 128 (LEB128) as defined by DWARF Debugging Information Format, Revision 2.0.0 (July 27, 1993). + DW_EH_PE_uleb128 = 0x01, + /// A 2 bytes unsigned value. + DW_EH_PE_udata2 = 0x02, + /// A 4 bytes unsigned value. + DW_EH_PE_udata4 = 0x03, + /// An 8 bytes unsigned value. + DW_EH_PE_udata8 = 0x04, + /// Signed value is encoded using the Little Endian Base 128 (LEB128) as defined by DWARF Debugging Information Format, Revision 2.0.0 (July 27, 1993). + DW_EH_PE_sleb128 = 0x09, + /// A 2 bytes signed value. + DW_EH_PE_sdata2 = 0x0A, + /// A 4 bytes signed value. + DW_EH_PE_sdata4 = 0x0B, + /// An 8 bytes signed value. + DW_EH_PE_sdata8 = 0x0C, +} + +#[derive(Debug)] +#[repr(u8)] +enum ValueApplication { + DW_EH_PE_absptr = 0x00, + /// Value is relative to the current program counter. + DW_EH_PE_pcrel = 0x10, + /// Value is relative to the beginning of the .text section. + DW_EH_PE_textrel = 0x20, + /// Value is relative to the beginning of the .got or .eh_frame_hdr section. + DW_EH_PE_datarel = 0x30, + /// Value is relative to the beginning of the function. + DW_EH_PE_funcrel = 0x40, + /// Value is aligned to an address unit sized boundary. + DW_EH_PE_aligned = 0x50, +} diff --git a/src/lib.rs b/src/lib.rs index ce2bb12..992b608 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,8 +20,8 @@ pub unsafe extern "C" fn _UnwindRaiseException( exception_object: *mut uw::_Unwind_Exception, ) -> uw::_Unwind_Reason_Code { trace!("someone raised an exception with addr {exception_object:p}"); - let di = crate::dwarf::dwarf_info(arch::get_rip() as _).unwrap(); - crate::dwarf::uwutables(di.eh_frame); + let _di = crate::dwarf::dwarf_info(arch::get_rip() as _).unwrap(); + crate::dwarf::uwutables(core::ptr::null()); stdext::abort(); } diff --git a/src/stdext.rs b/src/stdext.rs index 72977dd..0a8a090 100644 --- a/src/stdext.rs +++ b/src/stdext.rs @@ -22,15 +22,14 @@ 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)*)) + // We separate out the format_args for rust-analyzer support. + match format_args!($($tt)*) { + args => { + $crate::stdext::print(::core::format_args!("UWUWIND TRACE | uwuwind/{}:{}: {}\n", file!(), line!(), args)).expect("failed to trace") + } + } }; } @@ -49,11 +48,6 @@ fn errno() -> i32 { 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. diff --git a/test.c b/test.c index 93cb891..03fd6bc 100644 --- a/test.c +++ b/test.c @@ -1,8 +1,16 @@ -#include +#include #define __USE_GNU -#include -#include +#include +#include -int main() { - printf("%d", PT_GNU_EH_FRAME); +void *get_eh_frame(void *addr) +{ + struct dl_find_object out; + int ret = _dl_find_object(addr, &out); + if (ret != 0) + { + return NULL; + } + + return out.dlfo_eh_frame; } \ No newline at end of file