use gimli::{EvaluationResult, Expression, Operation};
use crate::loader::DwarfSlice;
pub fn evaluate_member_offset(
expr: Expression<DwarfSlice<'_>>,
encoding: gimli::Encoding,
) -> crate::error::Result<Option<u64>> {
let mut eval = expr.evaluation(encoding);
eval.set_initial_value(0);
loop {
match eval.evaluate() {
Ok(EvaluationResult::Complete) => {
let result = eval.result();
if result.is_empty() {
return Ok(None);
}
match result[0].location {
gimli::Location::Address { address } => return Ok(Some(address)),
gimli::Location::Value { value } => {
let addr_mask = match encoding.address_size {
0 => return Ok(None), 8 => !0u64,
size if size < 8 => (1u64 << (size * 8)) - 1,
_ => return Ok(None), };
match value.to_u64(addr_mask) {
Ok(v) => return Ok(Some(v)),
Err(_) => return Ok(None),
}
}
_ => return Ok(None),
}
}
Ok(EvaluationResult::RequiresMemory { .. }) => return Ok(None),
Ok(EvaluationResult::RequiresRegister { .. }) => return Ok(None),
Ok(EvaluationResult::RequiresFrameBase) => return Ok(None),
Ok(EvaluationResult::RequiresTls(_)) => return Ok(None),
Ok(EvaluationResult::RequiresCallFrameCfa) => return Ok(None),
Ok(EvaluationResult::RequiresAtLocation(_)) => return Ok(None),
Ok(EvaluationResult::RequiresEntryValue(_)) => return Ok(None),
Ok(EvaluationResult::RequiresParameterRef(_)) => return Ok(None),
Ok(EvaluationResult::RequiresRelocatedAddress(addr)) => {
if eval.resume_with_relocated_address(addr).is_err() {
return Ok(None);
}
}
Ok(EvaluationResult::RequiresIndexedAddress { .. }) => return Ok(None),
Ok(EvaluationResult::RequiresBaseType(_)) => return Ok(None),
Err(_) => return Ok(None),
}
}
}
pub fn try_simple_offset(
expr: Expression<DwarfSlice<'_>>,
encoding: gimli::Encoding,
) -> Option<u64> {
let mut ops = expr.operations(encoding);
let value = match ops.next().ok().flatten()? {
Operation::PlusConstant { value } => value,
Operation::UnsignedConstant { value } => value,
_ => return None,
};
if ops.next().ok().flatten().is_some() {
return None;
}
Some(value)
}
#[cfg(test)]
mod tests {
use super::*;
use gimli::{Encoding, EndianSlice, Format, RunTimeEndian};
fn encoding(address_size: u8) -> Encoding {
Encoding { address_size, format: Format::Dwarf32, version: 4 }
}
fn expr(bytes: &[u8]) -> Expression<DwarfSlice<'_>> {
Expression(EndianSlice::new(bytes, RunTimeEndian::Little))
}
fn uleb(mut value: u64) -> Vec<u8> {
let mut out = Vec::new();
loop {
let mut byte = (value & 0x7f) as u8;
value >>= 7;
if value != 0 {
byte |= 0x80;
}
out.push(byte);
if value == 0 {
break;
}
}
out
}
#[test]
fn try_simple_offset_plus_uconst() {
let mut bytes = vec![0x23]; bytes.extend_from_slice(&uleb(7));
assert_eq!(try_simple_offset(expr(&bytes), encoding(8)), Some(7));
}
#[test]
fn try_simple_offset_constu() {
let mut bytes = vec![0x10]; bytes.extend_from_slice(&uleb(42));
assert_eq!(try_simple_offset(expr(&bytes), encoding(8)), Some(42));
}
#[test]
fn try_simple_offset_rejects_extra_ops() {
let bytes = vec![0x23, 0x01, 0x23, 0x01]; assert_eq!(try_simple_offset(expr(&bytes), encoding(8)), None);
}
#[test]
fn evaluate_member_offset_returns_value() {
let bytes = vec![0x23, 0x05]; let value = evaluate_member_offset(expr(&bytes), encoding(8)).unwrap();
assert_eq!(value, Some(5));
}
#[test]
fn evaluate_member_offset_invalid_address_size() {
let bytes = vec![0x10, 0x01]; let value = evaluate_member_offset(expr(&bytes), encoding(0)).unwrap();
assert!(value.is_some());
}
#[test]
fn evaluate_member_offset_empty_expression() {
let bytes: [u8; 0] = [];
let value = evaluate_member_offset(expr(&bytes), encoding(8)).unwrap();
assert_eq!(value, Some(0));
}
}