mirror of
https://github.com/Noratrieb/uwuwind.git
synced 2026-01-14 08:35:09 +01:00
uwuwind
This commit is contained in:
commit
66fedc6178
16 changed files with 436 additions and 0 deletions
5
.cargo/config.toml
Normal file
5
.cargo/config.toml
Normal file
|
|
@ -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
|
||||
]
|
||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
72
Cargo.lock
generated
Normal file
72
Cargo.lock
generated
Normal file
|
|
@ -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",
|
||||
]
|
||||
19
Cargo.toml
Normal file
19
Cargo.toml
Normal file
|
|
@ -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"
|
||||
61
a.cpp
Normal file
61
a.cpp
Normal file
|
|
@ -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<decltype(_dl_find_object) *>(
|
||||
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<uintptr_t>(findResult.dlfo_map_start);
|
||||
info.text_segment_length = static_cast<size_t>(
|
||||
(char *)findResult.dlfo_map_end - (char *)findResult.dlfo_map_start);
|
||||
|
||||
// Record the start of PT_GNU_EH_FRAME.
|
||||
info.dwarf_index_section =
|
||||
reinterpret_cast<uintptr_t>(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<LocalAddressSpace>::EHHeaderInfo hdrInfo;
|
||||
if (!EHHeaderParser<LocalAddressSpace>::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<bool>(found);
|
||||
}
|
||||
26
src/arch.rs
Normal file
26
src/arch.rs
Normal file
|
|
@ -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
|
||||
}
|
||||
57
src/dwarf.rs
Normal file
57
src/dwarf.rs
Normal file
|
|
@ -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<DwarfInfo> {
|
||||
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;
|
||||
}
|
||||
36
src/identify.rs
Normal file
36
src/identify.rs
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
38
src/lib.rs
Normal file
38
src/lib.rs
Normal file
|
|
@ -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);
|
||||
}
|
||||
35
src/main.rs
Normal file
35
src/main.rs
Normal file
|
|
@ -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::<uw::_Unwind_Exception>());
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
52
src/uw.rs
Normal file
52
src/uw.rs
Normal file
|
|
@ -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;
|
||||
22
src/walk/fp.rs
Normal file
22
src/walk/fp.rs
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
1
src/walk/mod.rs
Normal file
1
src/walk/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub mod fp;
|
||||
BIN
test
Executable file
BIN
test
Executable file
Binary file not shown.
8
test.c
Normal file
8
test.c
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#include<stdio.h>
|
||||
#define __USE_GNU
|
||||
#include<dlfcn.h>
|
||||
#include<elf.h>
|
||||
|
||||
int main() {
|
||||
printf("%d", PT_GNU_EH_FRAME);
|
||||
}
|
||||
3
test.rs
Normal file
3
test.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
panic!();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue