Wrote stack unwinder. NEEDTO: fix NoUnwindInfo
Probably incorrect PC was set.
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
//! Implements the core stack unwinding logic.
|
||||
//!
|
||||
//! # TODOs
|
||||
//!
|
||||
//! * Support evaluation of DWARF expressions, this might not be required
|
||||
//! however, because Rust doesn't tend to use this DWARF feature.
|
||||
|
||||
use fallible_iterator::FallibleIterator;
|
||||
use gimli::{
|
||||
CfaRule, EndianSlice, LittleEndian, Register, RegisterRule, StoreOnHeap,
|
||||
UnwindContext, UnwindSection, X86_64,
|
||||
};
|
||||
|
||||
use super::eh_info::EhInfo;
|
||||
|
||||
/// Implements the core stack unwinding logic by parsing the call frame
|
||||
/// information. This also stores current (DWARF) register values.
|
||||
///
|
||||
/// # Sources
|
||||
///
|
||||
/// Taken from [lesenechal.fr](https://lesenechal.fr/en/linux/unwinding-the-stack-the-hard-way)
|
||||
/// with some additional features to be added soon.
|
||||
pub struct Unwinder {
|
||||
/// The call frame information.
|
||||
eh_info: EhInfo,
|
||||
/// An [UnwindContext] used by Gimli for optimisations.
|
||||
unwind_ctx: UnwindContext<usize, StoreOnHeap>,
|
||||
/// The current values of ABI/architecture independent registers. There are
|
||||
/// used by DWARF.
|
||||
regs: RegisterSet,
|
||||
/// The current CFA address.
|
||||
cfa: u64,
|
||||
/// Is this the first iteration?
|
||||
is_first: bool,
|
||||
}
|
||||
|
||||
// TODO: Use map_err et al.
|
||||
impl FallibleIterator for Unwinder {
|
||||
type Item = CallFrame;
|
||||
type Error = UnwinderError;
|
||||
|
||||
/// Returns call frames of calling functions. This may be called to produce
|
||||
/// a stack trace or otherwise support physical unwinding.
|
||||
fn next(&mut self) -> Result<Option<Self::Item>, Self::Error> {
|
||||
// Gets the current program counter from the DWARF register set.
|
||||
let Some(pc) = self.regs.get_pc() else {
|
||||
return Err(UnwinderError::NoPcRegister);
|
||||
};
|
||||
|
||||
if self.is_first {
|
||||
self.is_first = false;
|
||||
|
||||
return Ok(Some(CallFrame { pc }));
|
||||
}
|
||||
|
||||
// This is a row in the virtual unwind table AKA the CFI which will help
|
||||
// us find the CFA (canonical frame address) for a given program
|
||||
// counter.
|
||||
let Ok(row) = self.eh_info.hdr_table.unwind_info_for_address(
|
||||
&self.eh_info.eh_frame,
|
||||
&self.eh_info.base_addrs,
|
||||
&mut self.unwind_ctx,
|
||||
pc,
|
||||
|section, bases, offset|
|
||||
// Finds a DWARF CIE using an offset as given.
|
||||
section.cie_from_offset(bases, offset),
|
||||
) else {
|
||||
return Err(UnwinderError::NoUnwindInfo);
|
||||
};
|
||||
|
||||
// We compute the CFA (canonical frame address from its rule).
|
||||
// TODO: Support other rules such as DWARF expressions.
|
||||
match row.cfa() {
|
||||
CfaRule::RegisterAndOffset { register, offset } => {
|
||||
let Some(reg_val) = self.regs.get(*register) else {
|
||||
return Err(UnwinderError::CfaRuleUnknownRegister(
|
||||
*register,
|
||||
));
|
||||
};
|
||||
|
||||
self.cfa = (reg_val as i64 + offset) as u64;
|
||||
}
|
||||
|
||||
// TODO: Support other rules for computing the CFA.
|
||||
_ => return Err(UnwinderError::UnsupportedCfaRule),
|
||||
}
|
||||
|
||||
for reg in RegisterSet::iter() {
|
||||
match row.register(reg) {
|
||||
RegisterRule::Undefined => self.regs.undef(reg),
|
||||
RegisterRule::SameValue => (),
|
||||
RegisterRule::Offset(offset) => {
|
||||
// Adds the given offset to the register contents and
|
||||
// retrieve the value from the stack at address CFA +
|
||||
// offset.
|
||||
let ptr = (self.cfa as i64 + offset) as u64 as *const usize;
|
||||
|
||||
self.regs.set(reg, unsafe { ptr.read() } as u64)?
|
||||
}
|
||||
|
||||
_ => {
|
||||
return Err(UnwinderError::UnimplementedRegisterRule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the new value for %rip from the function return value and
|
||||
// subtract one from it, because the address actually points the the
|
||||
// next instruction after the call.
|
||||
let Some(pc) = self.regs.get_ret() else {
|
||||
return Err(UnwinderError::NoReturnAddr);
|
||||
};
|
||||
// REVIEWME: Must be a nicer way of doing this.
|
||||
let pc = pc - 1;
|
||||
|
||||
self.regs.set_pc(pc);
|
||||
|
||||
// Set %rsp to the CFA. This simulates returning from the function,
|
||||
// destroying the call frame, so we were able to virtually unwind (to
|
||||
// the caller function).
|
||||
self.regs.set_stack_ptr(self.cfa);
|
||||
|
||||
Ok(Some(CallFrame { pc }))
|
||||
}
|
||||
// fn next(&mut self) -> Option<Result<Option<CallFrame>, UnwinderError>> {}
|
||||
}
|
||||
|
||||
impl Unwinder {
|
||||
pub fn new(eh_info: EhInfo, regset: RegisterSet) -> Self {
|
||||
Self {
|
||||
eh_info,
|
||||
regs: regset,
|
||||
unwind_ctx: UnwindContext::new(),
|
||||
cfa: 0,
|
||||
is_first: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The set of registers used by DWARF. This struct allows future portability if
|
||||
/// we want to target other architectures than x86_64.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct RegisterSet {
|
||||
rip: Option<u64>,
|
||||
rsp: Option<u64>,
|
||||
rbp: Option<u64>,
|
||||
/// The return address register.
|
||||
ret: Option<u64>,
|
||||
}
|
||||
|
||||
impl RegisterSet {
|
||||
pub const fn get(&self, reg: Register) -> Option<u64> {
|
||||
match reg {
|
||||
X86_64::RSP => self.rsp,
|
||||
X86_64::RBP => self.rbp,
|
||||
X86_64::RA => self.ret,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn set(
|
||||
&mut self,
|
||||
reg: Register,
|
||||
val: u64,
|
||||
) -> Result<(), UnwinderError> {
|
||||
*match reg {
|
||||
X86_64::RSP => &mut self.rsp,
|
||||
X86_64::RBP => &mut self.rbp,
|
||||
X86_64::RA => &mut self.ret,
|
||||
_ => return Err(UnwinderError::UnexpectedRegister(reg)),
|
||||
} = Some(val);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const fn undef(&mut self, reg: Register) {
|
||||
*match reg {
|
||||
X86_64::RSP => &mut self.rsp,
|
||||
X86_64::RBP => &mut self.rbp,
|
||||
X86_64::RA => &mut self.ret,
|
||||
_ => return,
|
||||
} = None;
|
||||
}
|
||||
|
||||
const fn get_pc(&self) -> Option<u64> {
|
||||
self.rip
|
||||
}
|
||||
|
||||
pub const fn set_pc(&mut self, val: u64) {
|
||||
self.rip = Some(val);
|
||||
}
|
||||
|
||||
const fn get_ret(&self) -> Option<u64> {
|
||||
self.ret
|
||||
}
|
||||
|
||||
pub const fn set_stack_ptr(&mut self, val: u64) {
|
||||
self.rsp = Some(val);
|
||||
}
|
||||
|
||||
fn iter() -> impl Iterator<Item = Register> {
|
||||
[X86_64::RSP, X86_64::RBP, X86_64::RA].into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
/// The current instruction pointer.
|
||||
#[derive(Debug)]
|
||||
pub struct CallFrame {
|
||||
/// The current instruction pointer.
|
||||
pub pc: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// A list of errors that could occur whilst unwinding the stack.
|
||||
pub enum UnwinderError {
|
||||
UnexpectedRegister(Register),
|
||||
UnsupportedCfaRule,
|
||||
UnimplementedRegisterRule,
|
||||
CfaRuleUnknownRegister(Register),
|
||||
NoUnwindInfo,
|
||||
NoPcRegister,
|
||||
NoReturnAddr,
|
||||
}
|
||||
Reference in New Issue
Block a user