use std::collections::BTreeMap;
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub enum EvaluationResult {
DirectValue(DirectValueResult),
MemoryLocation(LocationResult),
Optimized,
Composite(Vec<PieceResult>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum DirectValueResult {
Constant(i64),
AbsoluteAddress(u64),
ImplicitValue(Vec<u8>),
RegisterValue(u16),
ComputedValue {
steps: Vec<ComputeStep>,
result_size: MemoryAccessSize,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum LocationResult {
Address(u64),
RegisterAddress {
register: u16, offset: Option<i64>,
size: Option<u64>, },
ComputedLocation {
steps: Vec<ComputeStep>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum CfaResult {
RegisterPlusOffset {
register: u16, offset: i64,
},
Expression { steps: Vec<ComputeStep> },
}
#[derive(Debug, Clone, PartialEq)]
pub struct CallerFrameRecovery {
pub cfa_steps: Vec<ComputeStep>,
pub return_address_register: u16,
pub caller_pc_steps: Vec<ComputeStep>,
pub register_recovery_steps: BTreeMap<u16, Vec<ComputeStep>>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PieceResult {
pub location: EvaluationResult,
pub size: u64,
pub bit_offset: Option<u64>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EntryValueCase {
pub caller_return_pc: u64,
pub value_steps: Vec<ComputeStep>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ComputeStep {
LoadRegister(u16),
PushConstant(i64),
Dereference {
size: MemoryAccessSize,
},
Add,
Sub,
Mul,
Div,
Mod,
And,
Or,
Xor,
Shl,
Shr,
Shra,
Not,
Neg,
Abs,
Dup,
Drop,
Swap,
Rot,
Pick(u8),
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
If {
then_branch: Vec<ComputeStep>,
else_branch: Vec<ComputeStep>,
},
EntryValueLookup {
caller_pc_steps: Vec<ComputeStep>,
cases: Vec<EntryValueCase>,
},
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MemoryAccessSize {
U8, U16, U32, U64, }
impl MemoryAccessSize {
pub fn bytes(&self) -> usize {
match self {
MemoryAccessSize::U8 => 1,
MemoryAccessSize::U16 => 2,
MemoryAccessSize::U32 => 4,
MemoryAccessSize::U64 => 8,
}
}
pub fn from_size(size: u64) -> Self {
match size {
1 => MemoryAccessSize::U8,
2 => MemoryAccessSize::U16,
4 => MemoryAccessSize::U32,
8 => MemoryAccessSize::U64,
_ if size <= 8 => MemoryAccessSize::U64, _ => MemoryAccessSize::U64, }
}
}
impl EvaluationResult {
pub fn as_constant(&self) -> Option<i64> {
match self {
EvaluationResult::DirectValue(DirectValueResult::Constant(c)) => Some(*c),
_ => None,
}
}
pub fn merge_with_cfa(self, cfa: CfaResult, frame_offset: i64) -> Self {
match cfa {
CfaResult::RegisterPlusOffset { register, offset } => {
EvaluationResult::MemoryLocation(LocationResult::RegisterAddress {
register,
offset: Some(offset.saturating_add(frame_offset)),
size: None,
})
}
CfaResult::Expression { mut steps } => {
steps.push(ComputeStep::PushConstant(frame_offset));
steps.push(ComputeStep::Add);
EvaluationResult::MemoryLocation(LocationResult::ComputedLocation { steps })
}
}
}
}
impl DirectValueResult {
pub fn is_compile_time_constant(&self) -> bool {
matches!(
self,
DirectValueResult::Constant(_) | DirectValueResult::ImplicitValue(_)
)
}
fn steps_to_expression(steps: &[ComputeStep]) -> String {
use ghostscope_platform::register_mapping::dwarf_reg_to_name;
let mut stack: Vec<String> = Vec::new();
for step in steps {
match step {
ComputeStep::LoadRegister(r) => {
let reg_name = dwarf_reg_to_name(*r).unwrap_or("r?").to_string();
stack.push(reg_name);
}
ComputeStep::PushConstant(v) => {
if *v >= 0 && *v <= 0xFF {
stack.push(format!("{v}"));
} else {
stack.push(format!("0x{v:x}"));
}
}
ComputeStep::Add => {
if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
if a.chars()
.all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
&& b.parse::<i64>().is_ok()
&& b.parse::<i64>().unwrap().abs() < 1000
{
stack.push(format!("{a}+{b}"));
} else {
stack.push(format!("({a}+{b})"));
}
} else {
stack.push("?+?".to_string());
}
}
ComputeStep::Sub => {
if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
stack.push(format!("({a}-{b})"));
} else {
stack.push("?-?".to_string());
}
}
ComputeStep::Mul => {
if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
stack.push(format!("{a}*{b}"));
} else {
stack.push("?*?".to_string());
}
}
ComputeStep::Div => {
if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
stack.push(format!("({a}/{b})"));
} else {
stack.push("?/?".to_string());
}
}
ComputeStep::Mod => {
if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
stack.push(format!("({a}%{b})"));
} else {
stack.push("?%?".to_string());
}
}
ComputeStep::And => {
if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
stack.push(format!("({a}&{b})"));
} else {
stack.push("?&?".to_string());
}
}
ComputeStep::Or => {
if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
stack.push(format!("({a}|{b})"));
} else {
stack.push("?|?".to_string());
}
}
ComputeStep::Xor => {
if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
stack.push(format!("({a}^{b})"));
} else {
stack.push("?^?".to_string());
}
}
ComputeStep::Shl => {
if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
stack.push(format!("({a}<<{b})"));
} else {
stack.push("?<<?".to_string());
}
}
ComputeStep::Shr => {
if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
stack.push(format!("({a}>>{b})"));
} else {
stack.push("?>>?".to_string());
}
}
ComputeStep::Shra => {
if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
stack.push(format!("({a}>>>{b})"));
} else {
stack.push("?>>>?".to_string());
}
}
ComputeStep::Not => {
if let Some(a) = stack.pop() {
stack.push(format!("~{a}"));
} else {
stack.push("~?".to_string());
}
}
ComputeStep::Neg => {
if let Some(a) = stack.pop() {
stack.push(format!("-{a}"));
} else {
stack.push("-?".to_string());
}
}
ComputeStep::Abs => {
if let Some(a) = stack.pop() {
stack.push(format!("|{a}|"));
} else {
stack.push("|?|".to_string());
}
}
ComputeStep::Dereference { size } => {
if let Some(a) = stack.pop() {
stack.push(format!("*({a} as {size})"));
} else {
stack.push(format!("*(? as {size})"));
}
}
ComputeStep::Dup => {
if let Some(top) = stack.last() {
stack.push(top.clone());
}
}
ComputeStep::Drop => {
stack.pop();
}
ComputeStep::Swap => {
if stack.len() >= 2 {
let len = stack.len();
stack.swap(len - 1, len - 2);
}
}
ComputeStep::Rot => {
if stack.len() >= 3 {
let len = stack.len();
let third = stack.remove(len - 3);
stack.push(third);
}
}
ComputeStep::Pick(n) => {
if stack.len() > *n as usize {
let idx = stack.len() - 1 - (*n as usize);
let val = stack[idx].clone();
stack.push(val);
} else {
stack.push("?".to_string());
}
}
ComputeStep::Eq => {
if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
stack.push(format!("({a}=={b})"));
} else {
stack.push("?==?".to_string());
}
}
ComputeStep::Ne => {
if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
stack.push(format!("({a}!={b})"));
} else {
stack.push("?!=?".to_string());
}
}
ComputeStep::Lt => {
if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
stack.push(format!("({a}<{b})"));
} else {
stack.push("?<?".to_string());
}
}
ComputeStep::Le => {
if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
stack.push(format!("({a}<={b})"));
} else {
stack.push("?<=?".to_string());
}
}
ComputeStep::Gt => {
if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
stack.push(format!("({a}>{b})"));
} else {
stack.push("?>?".to_string());
}
}
ComputeStep::Ge => {
if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
stack.push(format!("({a}>={b})"));
} else {
stack.push("?>=?".to_string());
}
}
ComputeStep::If {
then_branch,
else_branch,
} => {
if let Some(cond) = stack.pop() {
stack.push(format!("if {cond} then ... else ..."));
} else {
stack.push("if ? then ... else ...".to_string());
}
_ = then_branch;
_ = else_branch;
}
ComputeStep::EntryValueLookup { cases, .. } => {
stack.push(format!("entry_value[{} cases]", cases.len()));
}
}
}
stack.pop().unwrap_or_else(|| "?".to_string())
}
}
impl LocationResult {
pub fn is_simple(&self) -> bool {
matches!(
self,
LocationResult::Address(_) | LocationResult::RegisterAddress { .. }
)
}
fn steps_to_expression(steps: &[ComputeStep]) -> String {
DirectValueResult::steps_to_expression(steps)
}
}
impl fmt::Display for EvaluationResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EvaluationResult::DirectValue(dv) => write!(f, "[DirectValue] {dv}"),
EvaluationResult::MemoryLocation(loc) => write!(f, "[Memory] {loc}"),
EvaluationResult::Optimized => write!(f, "<optimized out>"),
EvaluationResult::Composite(pieces) => {
write!(f, "Composite[{} pieces]", pieces.len())
}
}
}
}
impl fmt::Display for DirectValueResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use ghostscope_platform::register_mapping::dwarf_reg_to_name;
match self {
DirectValueResult::Constant(c) => {
if *c >= 0 && *c <= 0xFF {
write!(f, "{c} (0x{c:x})")
} else {
write!(f, "0x{c:x}")
}
}
DirectValueResult::AbsoluteAddress(addr) => write!(f, "&@0x{addr:x}"),
DirectValueResult::RegisterValue(r) => {
if let Some(name) = dwarf_reg_to_name(*r) {
write!(f, "{name}")
} else {
write!(f, "r{r}")
}
}
DirectValueResult::ImplicitValue(bytes) => {
if bytes.len() <= 8 {
write!(f, "implicit[")?;
for (i, b) in bytes.iter().enumerate() {
if i > 0 {
write!(f, " ")?;
}
write!(f, "{b:02x}")?;
}
write!(f, "]")
} else {
write!(f, "implicit[{} bytes]", bytes.len())
}
}
DirectValueResult::ComputedValue {
steps,
result_size: _,
} => {
write!(f, "=")?;
let expr = Self::steps_to_expression(steps);
write!(f, "{expr}")
}
}
}
}
impl fmt::Display for LocationResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use ghostscope_platform::register_mapping::dwarf_reg_to_name;
match self {
LocationResult::Address(addr) => write!(f, "@0x{addr:x}"),
LocationResult::RegisterAddress {
register,
offset,
size,
} => {
let reg_name = dwarf_reg_to_name(*register).unwrap_or("r?");
match (offset, size) {
(Some(o), Some(s)) => {
let offset = *o;
if offset >= 0 {
write!(f, "@[{reg_name}+{offset}]:{s}")
} else {
let neg = -offset;
write!(f, "@[{reg_name}-{neg}]:{s}")
}
}
(Some(o), None) => {
let offset = *o;
if offset >= 0 {
write!(f, "@[{reg_name}+{offset}]")
} else {
let neg = -offset;
write!(f, "@[{reg_name}-{neg}]")
}
}
(None, Some(s)) => write!(f, "@[{reg_name}]:{s}"),
(None, None) => write!(f, "@[{reg_name}]"),
}
}
LocationResult::ComputedLocation { steps } => {
write!(f, "@[")?;
let expr = Self::steps_to_expression(steps);
write!(f, "{expr}]")
}
}
}
}
impl fmt::Display for MemoryAccessSize {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MemoryAccessSize::U8 => write!(f, "u8"),
MemoryAccessSize::U16 => write!(f, "u16"),
MemoryAccessSize::U32 => write!(f, "u32"),
MemoryAccessSize::U64 => write!(f, "u64"),
}
}
}
impl fmt::Display for ComputeStep {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use ghostscope_platform::register_mapping::dwarf_reg_to_name;
match self {
ComputeStep::LoadRegister(r) => {
if let Some(name) = dwarf_reg_to_name(*r) {
write!(f, "load {name}")
} else {
write!(f, "load r{r}")
}
}
ComputeStep::PushConstant(v) => write!(f, "push {v}"),
ComputeStep::Dereference { size } => write!(f, "deref {size}"),
ComputeStep::Add => write!(f, "add"),
ComputeStep::Sub => write!(f, "sub"),
ComputeStep::Mul => write!(f, "mul"),
ComputeStep::Div => write!(f, "div"),
ComputeStep::Mod => write!(f, "mod"),
ComputeStep::And => write!(f, "and"),
ComputeStep::Or => write!(f, "or"),
ComputeStep::Xor => write!(f, "xor"),
ComputeStep::Shl => write!(f, "shl"),
ComputeStep::Shr => write!(f, "shr"),
ComputeStep::Shra => write!(f, "shra"),
ComputeStep::Not => write!(f, "not"),
ComputeStep::Neg => write!(f, "neg"),
ComputeStep::Abs => write!(f, "abs"),
ComputeStep::Dup => write!(f, "dup"),
ComputeStep::Drop => write!(f, "drop"),
ComputeStep::Swap => write!(f, "swap"),
ComputeStep::Rot => write!(f, "rot"),
ComputeStep::Pick(n) => write!(f, "pick {n}"),
ComputeStep::Eq => write!(f, "eq"),
ComputeStep::Ne => write!(f, "ne"),
ComputeStep::Lt => write!(f, "lt"),
ComputeStep::Le => write!(f, "le"),
ComputeStep::Gt => write!(f, "gt"),
ComputeStep::Ge => write!(f, "ge"),
ComputeStep::If {
then_branch,
else_branch,
} => {
write!(
f,
"if[then:{} else:{}]",
then_branch.len(),
else_branch.len()
)
}
ComputeStep::EntryValueLookup { cases, .. } => {
write!(f, "entry_value_lookup[cases:{}]", cases.len())
}
}
}
}
#[cfg(test)]
mod tests {
use super::{CfaResult, EvaluationResult, LocationResult};
#[test]
fn merge_with_cfa_saturates_register_plus_offset() {
let merged = EvaluationResult::Optimized.merge_with_cfa(
CfaResult::RegisterPlusOffset {
register: 7,
offset: i64::MAX - 2,
},
10,
);
assert_eq!(
merged,
EvaluationResult::MemoryLocation(LocationResult::RegisterAddress {
register: 7,
offset: Some(i64::MAX),
size: None,
})
);
}
}