use std::collections::HashMap;
use std::path::Path;
use std::rc::Rc;
use gimli::{
BaseAddresses, CfaRule, EhFrame, EndianRcSlice, Register, RegisterRule, RunTimeEndian,
UnwindContext, UnwindSection,
};
use object::{Object, ObjectSection};
use crate::error::{Error, Result};
use crate::types::VirtAddr;
type GimliReader = EndianRcSlice<RunTimeEndian>;
#[derive(Debug, Clone)]
pub struct UnwindFrame {
pub pc: VirtAddr,
pub cfa: u64,
}
pub struct Unwinder {
eh_frame: EhFrame<GimliReader>,
bases: BaseAddresses,
}
impl Unwinder {
pub fn load(path: &Path) -> Result<Self> {
let data =
std::fs::read(path).map_err(|e| Error::Other(format!("read ELF for unwind: {}", e)))?;
let obj = object::File::parse(&*data)
.map_err(|e| Error::Other(format!("parse ELF for unwind: {}", e)))?;
let eh_frame_data = obj
.section_by_name(".eh_frame")
.and_then(|s| s.data().ok())
.unwrap_or(&[]);
let eh_frame_addr = obj
.section_by_name(".eh_frame")
.map(|s| s.address())
.unwrap_or(0);
let text_addr = obj
.section_by_name(".text")
.map(|s| s.address())
.unwrap_or(0);
let reader = EndianRcSlice::new(Rc::from(eh_frame_data), RunTimeEndian::Little);
let mut eh_frame = EhFrame::from(reader);
eh_frame.set_address_size(8);
let bases = BaseAddresses::default()
.set_eh_frame(eh_frame_addr)
.set_text(text_addr);
Ok(Unwinder { eh_frame, bases })
}
pub fn walk_stack(
&self,
initial_pc: u64,
initial_regs: &[(u16, u64)],
read_memory: &dyn Fn(u64, usize) -> Result<Vec<u8>>,
) -> Result<Vec<UnwindFrame>> {
let mut frames = Vec::new();
let mut pc = initial_pc;
let mut regs: HashMap<Register, u64> = initial_regs
.iter()
.map(|&(r, v)| (Register(r), v))
.collect();
let mut ctx = UnwindContext::new();
for depth in 0..256 {
let cfa_val = regs.get(&Register(7)).copied().unwrap_or(0); frames.push(UnwindFrame {
pc: VirtAddr(pc),
cfa: cfa_val,
});
let lookup_pc = if depth == 0 { pc } else { pc.saturating_sub(1) };
let fde = match self.eh_frame.fde_for_address(
&self.bases,
lookup_pc,
|section, bases, offset| section.cie_from_offset(bases, offset),
) {
Ok(fde) => fde,
Err(_) => break,
};
let row =
match fde.unwind_info_for_address(&self.eh_frame, &self.bases, &mut ctx, lookup_pc)
{
Ok(row) => row,
Err(_) => break,
};
let cfa = match row.cfa() {
CfaRule::RegisterAndOffset { register, offset } => {
let reg_val = regs.get(register).copied().unwrap_or(0);
(reg_val as i64 + *offset) as u64
}
CfaRule::Expression(_) => break, };
if let Some(frame) = frames.last_mut() {
frame.cfa = cfa;
}
let ra_register = fde.cie().return_address_register();
let new_pc = match row.register(ra_register) {
RegisterRule::Offset(offset) => {
let addr = (cfa as i64 + offset) as u64;
let bytes = read_memory(addr, 8)?;
u64::from_le_bytes(bytes[..8].try_into().unwrap())
}
RegisterRule::Register(reg) => regs.get(®).copied().unwrap_or(0),
RegisterRule::Undefined => break,
_ => break,
};
if new_pc == 0 {
break;
}
let mut new_regs = HashMap::new();
new_regs.insert(Register(7), cfa);
for (®, &val) in ®s {
match row.register(reg) {
RegisterRule::SameValue => {
new_regs.insert(reg, val);
}
RegisterRule::Offset(offset) => {
let addr = (cfa as i64 + offset) as u64;
if let Ok(bytes) = read_memory(addr, 8) {
new_regs
.insert(reg, u64::from_le_bytes(bytes[..8].try_into().unwrap()));
}
}
RegisterRule::Register(other) => {
if let Some(&v) = regs.get(&other) {
new_regs.insert(reg, v);
}
}
RegisterRule::ValOffset(offset) => {
new_regs.insert(reg, (cfa as i64 + offset) as u64);
}
_ => {} }
}
pc = new_pc;
regs = new_regs;
}
Ok(frames)
}
pub fn return_address(
&self,
pc: u64,
initial_regs: &[(u16, u64)],
read_memory: &dyn Fn(u64, usize) -> Result<Vec<u8>>,
) -> Result<Option<u64>> {
let regs: HashMap<Register, u64> = initial_regs
.iter()
.map(|&(r, v)| (Register(r), v))
.collect();
let mut ctx = UnwindContext::new();
let fde = match self
.eh_frame
.fde_for_address(&self.bases, pc, |section, bases, offset| {
section.cie_from_offset(bases, offset)
}) {
Ok(fde) => fde,
Err(_) => return Ok(None),
};
let row = match fde.unwind_info_for_address(&self.eh_frame, &self.bases, &mut ctx, pc) {
Ok(row) => row,
Err(_) => return Ok(None),
};
let cfa = match row.cfa() {
CfaRule::RegisterAndOffset { register, offset } => {
let reg_val = regs.get(register).copied().unwrap_or(0);
(reg_val as i64 + *offset) as u64
}
_ => return Ok(None),
};
let ra_register = fde.cie().return_address_register();
match row.register(ra_register) {
RegisterRule::Offset(offset) => {
let addr = (cfa as i64 + offset) as u64;
let bytes = read_memory(addr, 8)?;
Ok(Some(u64::from_le_bytes(bytes[..8].try_into().unwrap())))
}
RegisterRule::Register(reg) => Ok(regs.get(®).copied()),
_ => Ok(None),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unwind_frame_creation() {
let frame = UnwindFrame {
pc: VirtAddr(0x401000),
cfa: 0x7fffffffde00,
};
assert_eq!(frame.pc, VirtAddr(0x401000));
assert_eq!(frame.cfa, 0x7fffffffde00);
}
#[test]
fn empty_walk_returns_at_least_one_frame() {
let reader = EndianRcSlice::new(Rc::from(&[] as &[u8]), RunTimeEndian::Little);
let mut eh_frame = EhFrame::from(reader);
eh_frame.set_address_size(8);
let bases = BaseAddresses::default();
let unwinder = Unwinder { eh_frame, bases };
let regs = vec![(7u16, 0x7fffffffde00u64), (16, 0x401000)]; let frames = unwinder
.walk_stack(0x401000, ®s, &|_, _| Ok(vec![0; 8]))
.unwrap();
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].pc, VirtAddr(0x401000));
}
}