Implement frame unwinder

This commit is contained in:
Gary Guo 2021-08-25 21:37:41 +01:00
parent 8b06d29ed8
commit dd94e27b1f
2 changed files with 143 additions and 0 deletions

142
src/frame.rs Normal file
View File

@ -0,0 +1,142 @@
use gimli::{
BaseAddresses, CfaRule, EvaluationResult, Expression, Location, RegisterRule,
UninitializedUnwindContext, UnwindTableRow, Value,
};
use crate::arch::*;
use crate::find_fde::{self, FDEFinder, FDESearchResult};
use crate::util::*;
#[derive(Debug)]
pub struct Frame {
fde_result: FDESearchResult,
row: UnwindTableRow<StaticSlice>,
}
impl Frame {
pub fn from_context(ctx: &Context) -> Result<Option<Self>, gimli::Error> {
let mut ra = ctx[Arch::RA];
// Reached end of stack
if ra == 0 {
return Ok(None);
}
// RA points to the *next* instruction, so move it back 1 byte for the call instruction.
ra -= 1;
let fde_result = match find_fde::get_finder().find_fde(ra as _) {
Some(v) => v,
None => return Ok(None),
};
let mut unwinder = UninitializedUnwindContext::new();
let row = fde_result
.fde
.unwind_info_for_address(
&fde_result.eh_frame,
&fde_result.bases,
&mut unwinder,
ra as _,
)?
.clone();
Ok(Some(Self { fde_result, row }))
}
fn evaluate_expression(
&self,
ctx: &Context,
expr: Expression<StaticSlice>,
) -> Result<usize, gimli::Error> {
let mut eval = expr.evaluation(self.fde_result.fde.cie().encoding());
let mut result = eval.evaluate()?;
loop {
match result {
EvaluationResult::Complete => break,
EvaluationResult::RequiresMemory { address, .. } => {
let value = unsafe { (address as usize as *const usize).read_unaligned() };
result = eval.resume_with_memory(Value::Generic(value as _))?;
}
EvaluationResult::RequiresRegister { register, .. } => {
let value = ctx[register];
result = eval.resume_with_register(Value::Generic(value as _))?;
}
EvaluationResult::RequiresRelocatedAddress(address) => {
let value = unsafe { (address as usize as *const usize).read_unaligned() };
result = eval.resume_with_memory(Value::Generic(value as _))?;
}
_ => unreachable!(),
}
}
Ok(
match eval
.result()
.pop()
.ok_or(gimli::Error::PopWithEmptyStack)?
.location
{
Location::Address { address } => address as usize,
_ => unreachable!(),
},
)
}
pub fn unwind(&self, ctx: &Context) -> Result<Context, gimli::Error> {
let row = &self.row;
let mut new_ctx = ctx.clone();
let cfa = match *row.cfa() {
CfaRule::RegisterAndOffset { register, offset } => {
ctx[register].wrapping_add(offset as usize)
}
CfaRule::Expression(expr) => self.evaluate_expression(ctx, expr)?,
};
new_ctx[Arch::SP] = cfa as _;
new_ctx[Arch::RA] = 0;
for (reg, rule) in row.registers() {
let value = match *rule {
RegisterRule::Undefined | RegisterRule::SameValue => ctx[*reg],
RegisterRule::Offset(offset) => unsafe {
*((cfa.wrapping_add(offset as usize)) as *const usize)
},
RegisterRule::ValOffset(offset) => cfa.wrapping_add(offset as usize),
RegisterRule::Register(r) => ctx[r],
RegisterRule::Expression(expr) => {
let addr = self.evaluate_expression(ctx, expr)?;
unsafe { *(addr as *const usize) }
}
RegisterRule::ValExpression(expr) => self.evaluate_expression(ctx, expr)?,
RegisterRule::Architectural => unreachable!(),
};
new_ctx[*reg] = value;
}
Ok(new_ctx)
}
pub fn bases(&self) -> &BaseAddresses {
&self.fde_result.bases
}
pub fn personality(&self) -> Option<usize> {
self.fde_result
.fde
.personality()
.map(|x| unsafe { deref_pointer(x) })
}
pub fn lsda(&self) -> usize {
self.fde_result
.fde
.lsda()
.map(|x| unsafe { deref_pointer(x) })
.unwrap_or(0)
}
pub fn initial_address(&self) -> usize {
self.fde_result.fde.initial_address() as _
}
}

View File

@ -5,4 +5,5 @@
mod arch;
mod find_fde;
mod frame;
mod util;