Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions ghostscope-dwarf/src/dwarf_expr/call_site.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
//! DWARF call-site expression helpers.

use crate::{
binary::DwarfReader,
core::ComputeStep,
dwarf_expr::{errors as expr_errors, modes::DwarfExprMode, ExpressionEvaluator},
};
use gimli::{Operation, Reader};

pub(crate) struct ParsedCallSiteParameter {
pub(crate) callee_register: u16,
pub(crate) caller_value_steps: Vec<ComputeStep>,
}

pub(crate) fn target_address(
dwarf: &gimli::Dwarf<DwarfReader>,
unit: &gimli::Unit<DwarfReader>,
entry: &gimli::DebuggingInformationEntry<DwarfReader>,
) -> Option<u64> {
address_attr(dwarf, unit, entry, gimli::constants::DW_AT_call_target)
.or_else(|| target_expr_address(unit, entry))
}

pub(crate) fn parameter(
dwarf: &gimli::Dwarf<DwarfReader>,
unit: &gimli::Unit<DwarfReader>,
entry: &gimli::DebuggingInformationEntry<DwarfReader>,
return_pc: u64,
) -> Option<ParsedCallSiteParameter> {
let callee_register = target_register(unit, entry)?;
let caller_value_steps = value_steps(dwarf, unit, entry, return_pc)?;
Some(ParsedCallSiteParameter {
callee_register,
caller_value_steps,
})
}

fn target_expr_address(
unit: &gimli::Unit<DwarfReader>,
entry: &gimli::DebuggingInformationEntry<DwarfReader>,
) -> Option<u64> {
let attr = entry.attr(gimli::constants::DW_AT_call_target)?;
let gimli::AttributeValue::Exprloc(expr) = attr.value() else {
return None;
};
let first = expr_errors::soft_optional(
DwarfExprMode::CallSiteValue,
crate::dwarf_expr::ops::parse_single_op(
expr.0,
unit.encoding(),
"DW_AT_call_target expression",
),
)?;
match first {
Operation::Address { address } => Some(address),
_ => None,
}
}

fn target_register(
unit: &gimli::Unit<DwarfReader>,
entry: &gimli::DebuggingInformationEntry<DwarfReader>,
) -> Option<u16> {
let attr = entry.attr(gimli::constants::DW_AT_location)?;
let gimli::AttributeValue::Exprloc(expr) = attr.value() else {
return None;
};
let first = expr_errors::soft_optional(
DwarfExprMode::CallSiteValue,
crate::dwarf_expr::ops::parse_single_op(
expr.0,
unit.encoding(),
"DW_AT_location call-site parameter expression",
),
)?;
match first {
Operation::Register { register } => Some(register.0),
_ => None,
}
}

fn value_steps(
dwarf: &gimli::Dwarf<DwarfReader>,
unit: &gimli::Unit<DwarfReader>,
entry: &gimli::DebuggingInformationEntry<DwarfReader>,
return_pc: u64,
) -> Option<Vec<ComputeStep>> {
let expr = [
gimli::constants::DW_AT_call_value,
gimli::constants::DW_AT_GNU_call_site_value,
]
.into_iter()
.find_map(|attr_name| {
let attr = entry.attr(attr_name)?;
match attr.value() {
gimli::AttributeValue::Exprloc(expr) => Some(expr),
_ => None,
}
})?;
expr_errors::soft_value(
DwarfExprMode::CallSiteValue,
ExpressionEvaluator::parse_expression_to_steps_in_unit(
expr.0.to_slice().ok().as_deref().unwrap_or(&[]),
expr.0.endian(),
unit,
dwarf,
return_pc,
None,
None,
None,
),
)
.or_else(|| call_value_register_fallback(expr, unit.encoding()))
}

fn call_value_register_fallback(
expr: gimli::Expression<DwarfReader>,
encoding: gimli::Encoding,
) -> Option<Vec<ComputeStep>> {
let first = expr_errors::soft_optional(
DwarfExprMode::CallSiteValue,
crate::dwarf_expr::ops::parse_single_op(
expr.0,
encoding,
"DW_AT_call_value fallback expression",
),
)?;
let Operation::EntryValue { expression: inner } = first else {
return None;
};
let inner_op = expr_errors::soft_optional(
DwarfExprMode::CallSiteValue,
crate::dwarf_expr::ops::parse_single_op(
inner,
encoding,
"DW_AT_call_value fallback entry_value inner expression",
),
)?;
match inner_op {
Operation::Register { register } => Some(vec![ComputeStep::LoadRegister(register.0)]),
_ => None,
}
}

fn address_attr(
dwarf: &gimli::Dwarf<DwarfReader>,
unit: &gimli::Unit<DwarfReader>,
entry: &gimli::DebuggingInformationEntry<DwarfReader>,
attr_name: gimli::DwAt,
) -> Option<u64> {
let attr = entry.attr(attr_name)?;
match attr.value() {
gimli::AttributeValue::Addr(addr) => Some(addr),
gimli::AttributeValue::DebugAddrIndex(index) => dwarf.address(unit, index).ok(),
_ => None,
}
}
136 changes: 136 additions & 0 deletions ghostscope-dwarf/src/dwarf_expr/cfa.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
//! CFA DWARF expression lowering.

use crate::{
core::{ComputeStep, MemoryAccessSize, Result},
dwarf_expr::{errors as expr_errors, modes::DwarfExprMode},
};
use anyhow::anyhow;
use gimli::Reader;

/// Parse CFA DWARF expression operations into a `ComputeStep` sequence.
pub(crate) fn parse_expression<R>(reader: R, encoding: gimli::Encoding) -> Result<Vec<ComputeStep>>
where
R: Reader<Offset = usize>,
{
let mut steps = Vec::new();

for op in expr_errors::hard(
DwarfExprMode::Cfa,
crate::dwarf_expr::ops::parse_ops(reader, encoding, "CFA expression"),
)? {
match op {
gimli::Operation::Register { register } => {
steps.push(ComputeStep::LoadRegister(register.0));
}
gimli::Operation::RegisterOffset {
register, offset, ..
} => {
steps.push(ComputeStep::LoadRegister(register.0));
if offset != 0 {
steps.push(ComputeStep::PushConstant(offset));
steps.push(ComputeStep::Add);
}
}
gimli::Operation::PlusConstant { value } => {
steps.push(ComputeStep::PushConstant(value as i64));
steps.push(ComputeStep::Add);
}
gimli::Operation::UnsignedConstant { value } => {
steps.push(ComputeStep::PushConstant(value as i64));
}
gimli::Operation::SignedConstant { value } => {
steps.push(ComputeStep::PushConstant(value));
}
gimli::Operation::Deref { size, space, .. } => {
if space {
return Err(anyhow!("unsupported CFA expression operation: {:?}", op));
}
let size = match size {
1 => MemoryAccessSize::U8,
2 => MemoryAccessSize::U16,
4 => MemoryAccessSize::U32,
8 => MemoryAccessSize::U64,
_ => {
return Err(anyhow!(
"unsupported CFA expression dereference size {} in operation: {:?}",
size,
op
))
}
};
steps.push(ComputeStep::Dereference { size });
}
gimli::Operation::Plus => steps.push(ComputeStep::Add),
gimli::Operation::Minus => steps.push(ComputeStep::Sub),
gimli::Operation::Mul => steps.push(ComputeStep::Mul),
gimli::Operation::And => steps.push(ComputeStep::And),
gimli::Operation::Or => steps.push(ComputeStep::Or),
gimli::Operation::Xor => steps.push(ComputeStep::Xor),
gimli::Operation::Nop => {}
_ => {
return Err(anyhow!("unsupported CFA expression operation: {:?}", op));
}
}
}

Ok(steps)
}

#[cfg(test)]
mod tests {
use super::parse_expression;
use crate::core::{ComputeStep, MemoryAccessSize};
use gimli::{EndianSlice, RunTimeEndian};

fn test_encoding() -> gimli::Encoding {
gimli::Encoding {
format: gimli::Format::Dwarf32,
version: 4,
address_size: 8,
}
}

fn parse_test_expr(bytes: &[u8]) -> crate::core::Result<Vec<ComputeStep>> {
parse_expression(
EndianSlice::new(bytes, RunTimeEndian::Little),
test_encoding(),
)
}

#[test]
fn cfa_expression_parses_unsigned_constant() {
let steps = parse_test_expr(&[0x10, 0x2a]).expect("DW_OP_constu should parse");
assert_eq!(steps, vec![ComputeStep::PushConstant(42)]);
}

#[test]
fn cfa_expression_parses_signed_constant() {
let steps = parse_test_expr(&[0x11, 0x7f]).expect("DW_OP_consts should parse");
assert_eq!(steps, vec![ComputeStep::PushConstant(-1)]);
}

#[test]
fn cfa_expression_parses_dereference() {
let steps = parse_test_expr(&[0x70, 0x00, 0x06]).expect("DW_OP_deref should parse");
assert_eq!(
steps,
vec![
ComputeStep::LoadRegister(0),
ComputeStep::Dereference {
size: MemoryAccessSize::U64,
},
]
);
}

#[test]
fn cfa_expression_rejects_unknown_opcode_after_valid_prefix() {
let error = parse_test_expr(&[0x70, 0x00, 0xff])
.expect_err("unknown CFI expression opcode must not be skipped");

assert!(
error.to_string().contains("failed to parse"),
"unexpected error: {error}"
);
}
}
31 changes: 31 additions & 0 deletions ghostscope-dwarf/src/dwarf_expr/const_eval.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//! Constant-only DWARF expression helpers.

use crate::{
binary::DwarfReader,
core::Result,
dwarf_expr::{errors as expr_errors, modes::DwarfExprMode},
};

pub(crate) fn eval_const_offset(
expr: &gimli::Expression<DwarfReader>,
encoding: gimli::Encoding,
) -> Result<Option<u64>> {
let Some(op) = expr_errors::hard(
DwarfExprMode::ConstOffset,
crate::dwarf_expr::ops::parse_single_op(
expr.0.clone(),
encoding,
"constant DWARF expression",
),
)?
else {
return Ok(None);
};

match op {
gimli::Operation::UnsignedConstant { value } => Ok(Some(value)),
gimli::Operation::SignedConstant { value } if value >= 0 => Ok(Some(value as u64)),
gimli::Operation::PlusConstant { value } => Ok(Some(value)),
_ => Ok(None),
}
}
Loading
Loading