use gimli::{Encoding, EndianSlice, Evaluation, EvaluationResult, LittleEndian, Location, Value};
use crate::error::{Error, Result};
#[derive(Debug, Clone)]
pub enum ExprResult {
Address(u64),
Register(u16),
Constant(u64),
OptimizedOut,
Pieces(Vec<ExprPiece>),
}
#[derive(Debug, Clone)]
pub struct ExprPiece {
pub location: ExprResult,
pub size_in_bits: u64,
pub bit_offset: u64,
}
pub struct EvalContext<'a> {
pub registers: &'a [(u16, u64)],
pub cfa: u64,
pub frame_base: Option<u64>,
pub read_memory: &'a dyn Fn(u64, usize) -> Result<Vec<u8>>,
}
impl<'a> EvalContext<'a> {
fn register_value(&self, reg: gimli::Register) -> Option<u64> {
let num = reg.0;
self.registers
.iter()
.find(|(r, _)| *r == num)
.map(|(_, v)| *v)
}
}
pub fn evaluate(
expr_bytes: &[u8],
encoding: Encoding,
ctx: &EvalContext<'_>,
) -> Result<ExprResult> {
let slice = EndianSlice::new(expr_bytes, LittleEndian);
let mut eval = Evaluation::new_in(slice, encoding);
eval.set_initial_value(0);
let mut result = eval
.evaluate()
.map_err(|e| Error::Other(format!("DWARF eval start: {}", e)))?;
loop {
match result {
EvaluationResult::Complete => break,
EvaluationResult::RequiresRegister {
register,
base_type: _,
} => {
let val = ctx.register_value(register).ok_or_else(|| {
Error::Other(format!(
"DWARF expr needs register {} but not available",
register.0
))
})?;
result = eval
.resume_with_register(Value::Generic(val))
.map_err(|e| Error::Other(format!("DWARF eval register: {}", e)))?;
}
EvaluationResult::RequiresFrameBase => {
let fb = ctx.frame_base.ok_or_else(|| {
Error::Other("DWARF expr needs frame base but not available".into())
})?;
result = eval
.resume_with_frame_base(fb)
.map_err(|e| Error::Other(format!("DWARF eval frame_base: {}", e)))?;
}
EvaluationResult::RequiresMemory { address, size, .. } => {
let data = (ctx.read_memory)(address, size as usize)?;
let val = read_bytes_as_u64(&data);
result = eval
.resume_with_memory(Value::Generic(val))
.map_err(|e| Error::Other(format!("DWARF eval memory: {}", e)))?;
}
EvaluationResult::RequiresCallFrameCfa => {
result = eval
.resume_with_call_frame_cfa(ctx.cfa)
.map_err(|e| Error::Other(format!("DWARF eval CFA: {}", e)))?;
}
EvaluationResult::RequiresTls { .. } => {
return Err(Error::Other("DWARF TLS variables not supported".into()));
}
EvaluationResult::RequiresAtLocation { .. } => {
return Err(Error::Other("DWARF DW_OP_call* not supported".into()));
}
EvaluationResult::RequiresRelocatedAddress(addr) => {
result = eval
.resume_with_relocated_address(addr)
.map_err(|e| Error::Other(format!("DWARF eval reloc: {}", e)))?;
}
EvaluationResult::RequiresIndexedAddress { .. } => {
return Err(Error::Other(
"DWARF indexed addresses (split DWARF) not supported".into(),
));
}
EvaluationResult::RequiresBaseType(_) => {
return Err(Error::Other("DWARF typed expressions not supported".into()));
}
EvaluationResult::RequiresEntryValue(_) => {
return Err(Error::Other("DWARF DW_OP_entry_value not supported".into()));
}
EvaluationResult::RequiresParameterRef(_) => {
return Err(Error::Other(
"DWARF DW_OP_GNU_parameter_ref not supported".into(),
));
}
}
}
let pieces = eval.result();
if pieces.is_empty() {
return Ok(ExprResult::OptimizedOut);
}
if pieces.len() == 1 && pieces[0].bit_offset.is_none() {
return Ok(convert_location(&pieces[0].location));
}
let expr_pieces = pieces
.iter()
.map(|p| ExprPiece {
location: convert_location(&p.location),
size_in_bits: p.size_in_bits.unwrap_or(0),
bit_offset: p.bit_offset.unwrap_or(0),
})
.collect();
Ok(ExprResult::Pieces(expr_pieces))
}
fn convert_location(loc: &Location<EndianSlice<'_, LittleEndian>>) -> ExprResult {
match loc {
Location::Address { address } => ExprResult::Address(*address),
Location::Register { register } => ExprResult::Register(register.0),
Location::Value { value } => match value {
Value::Generic(v) => ExprResult::Constant(*v),
Value::I8(v) => ExprResult::Constant(*v as u64),
Value::U8(v) => ExprResult::Constant(*v as u64),
Value::I16(v) => ExprResult::Constant(*v as u64),
Value::U16(v) => ExprResult::Constant(*v as u64),
Value::I32(v) => ExprResult::Constant(*v as u64),
Value::U32(v) => ExprResult::Constant(*v as u64),
Value::I64(v) => ExprResult::Constant(*v as u64),
Value::U64(v) => ExprResult::Constant(*v),
Value::F32(v) => ExprResult::Constant((*v).to_bits() as u64),
Value::F64(v) => ExprResult::Constant((*v).to_bits()),
},
Location::Empty => ExprResult::OptimizedOut,
_ => ExprResult::OptimizedOut,
}
}
fn read_bytes_as_u64(data: &[u8]) -> u64 {
let mut buf = [0u8; 8];
let len = data.len().min(8);
buf[..len].copy_from_slice(&data[..len]);
u64::from_le_bytes(buf)
}
#[cfg(test)]
mod tests {
use super::*;
fn test_encoding() -> Encoding {
Encoding {
address_size: 8,
format: gimli::Format::Dwarf64,
version: 4,
}
}
fn null_read_memory(_addr: u64, _len: usize) -> Result<Vec<u8>> {
Ok(vec![0; 8])
}
#[test]
fn eval_simple_addr() {
let bytes = [
0x03, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let ctx = EvalContext {
registers: &[],
cfa: 0,
frame_base: None,
read_memory: &null_read_memory,
};
let result = evaluate(&bytes, test_encoding(), &ctx).unwrap();
match result {
ExprResult::Address(addr) => assert_eq!(addr, 0x1000),
other => panic!("expected Address, got {:?}", other),
}
}
#[test]
fn eval_fbreg() {
let bytes = [
0x91, 0x70, ];
let ctx = EvalContext {
registers: &[],
cfa: 0,
frame_base: Some(0x7fff0100),
read_memory: &null_read_memory,
};
let result = evaluate(&bytes, test_encoding(), &ctx).unwrap();
match result {
ExprResult::Address(addr) => assert_eq!(addr, 0x7fff0100_u64.wrapping_sub(16)),
other => panic!("expected Address, got {:?}", other),
}
}
#[test]
fn eval_reg() {
let bytes = [0x50]; let ctx = EvalContext {
registers: &[(0, 42)],
cfa: 0,
frame_base: None,
read_memory: &null_read_memory,
};
let result = evaluate(&bytes, test_encoding(), &ctx).unwrap();
match result {
ExprResult::Register(reg) => assert_eq!(reg, 0),
other => panic!("expected Register, got {:?}", other),
}
}
#[test]
fn eval_lit_plus_stack_value() {
let bytes = [
0x35, 0x9f, ];
let ctx = EvalContext {
registers: &[],
cfa: 0,
frame_base: None,
read_memory: &null_read_memory,
};
let result = evaluate(&bytes, test_encoding(), &ctx).unwrap();
match result {
ExprResult::Constant(val) => assert_eq!(val, 5),
other => panic!("expected Constant(5), got {:?}", other),
}
}
#[test]
fn eval_breg_offset() {
let bytes = [
0x77, 0x08, ];
let ctx = EvalContext {
registers: &[(7, 0x7fff0000)],
cfa: 0,
frame_base: None,
read_memory: &null_read_memory,
};
let result = evaluate(&bytes, test_encoding(), &ctx).unwrap();
match result {
ExprResult::Address(addr) => assert_eq!(addr, 0x7fff0008),
other => panic!("expected Address, got {:?}", other),
}
}
#[test]
fn read_bytes_as_u64_various_sizes() {
assert_eq!(read_bytes_as_u64(&[0x42]), 0x42);
assert_eq!(read_bytes_as_u64(&[0x01, 0x02]), 0x0201);
assert_eq!(
read_bytes_as_u64(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]),
0x0807060504030201
);
}
}