Files
FoundryOS/kernel/src/std/unwind/unwinder.rs
T

230 lines
6.9 KiB
Rust

//! 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.
pub 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);
};
// 0xffffffff8000014b b - 7 = 11 - 7 = 4
if self.is_first {
self.is_first = false;
return Ok(Some(CallFrame { pc, symbol: 0 }));
}
// 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 Some(pc) = pc.checked_sub(1) else {
// REVIEWME: This should handle underflow now.
return Ok(None);
};
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, symbol: 0 }))
}
}
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,
/// The symbol of the function.
pub symbol: usize,
}
#[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,
}