From 1eb4b7a90af10147a2b799dbe9927d779a5cae8e Mon Sep 17 00:00:00 2001 From: van den Bosch <38101647+m-rtz@users.noreply.github.com> Date: Thu, 29 Jun 2023 10:31:05 +0200 Subject: [PATCH] Pcode parser rewrite (#417) --- src/cwe_checker_lib/src/ghidra_pcode/mod.rs | 282 +------- .../src/ghidra_pcode/pcode_op_simple/mod.rs | 378 +++++++++++ .../src/ghidra_pcode/pcode_op_simple/tests.rs | 607 ++++++++++++++++++ .../src/ghidra_pcode/pcode_operations.rs | 149 +++++ src/cwe_checker_lib/src/ghidra_pcode/tests.rs | 335 +--------- 5 files changed, 1170 insertions(+), 581 deletions(-) create mode 100644 src/cwe_checker_lib/src/ghidra_pcode/pcode_op_simple/mod.rs create mode 100644 src/cwe_checker_lib/src/ghidra_pcode/pcode_op_simple/tests.rs create mode 100644 src/cwe_checker_lib/src/ghidra_pcode/pcode_operations.rs diff --git a/src/cwe_checker_lib/src/ghidra_pcode/mod.rs b/src/cwe_checker_lib/src/ghidra_pcode/mod.rs index 11497bf1f..bbf3ceb05 100644 --- a/src/cwe_checker_lib/src/ghidra_pcode/mod.rs +++ b/src/cwe_checker_lib/src/ghidra_pcode/mod.rs @@ -8,6 +8,10 @@ use crate::pcode::{ExpressionType, JmpType}; use anyhow::{anyhow, Result}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +mod pcode_operations; +use pcode_operations::*; +mod pcode_op_simple; +use pcode_op_simple::*; /// The project struct for deserialization of the ghidra pcode extractor JSON. #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] @@ -38,12 +42,9 @@ impl ProjectSimple { for blk in func.blocks { for inst in blk.instructions { for op in inst.pcode_ops { - if PcodeOperation::ExpressionType(ExpressionType::LOAD) == op.pcode_mnemonic - || PcodeOperation::ExpressionType(ExpressionType::STORE) - == op.pcode_mnemonic - { - println!("{:?}", op.pcode_mnemonic); - println!("{:?}", op.into_ir_def(&inst.address)); + if matches!(op.pcode_mnemonic, PcodeOperation::ExpressionType(_)) { + dbg!(&op); + op.into_ir_def(&inst.address); } } } @@ -81,18 +82,17 @@ impl VarnodeSimple { /// virtual registers. /// /// Returns `Err` if the addressspace is neither `"const"`, `"register"` nor `"unique"`. - fn into_ir_expr(self) -> Result { + fn into_ir_expr(&self) -> Result { match self.address_space.as_str() { "const" => { let constant = Bitvector::from_u64(u64::from_str_radix(self.id.trim_start_matches("0x"), 16)?); - Ok(Expression::Const( constant.into_resize_unsigned(self.size.into()), )) } "register" => Ok(Expression::Var(Variable { - name: self.id, + name: self.id.clone(), size: ByteSize::new(self.size), is_temp: false, })), @@ -118,245 +118,41 @@ impl VarnodeSimple { } None } -} -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] -pub struct PcodeOpSimple { - pub pcode_index: u64, - pub pcode_mnemonic: PcodeOperation, - pub input0: VarnodeSimple, - pub input1: Option, - pub input2: Option, - pub output: Option, -} - -impl PcodeOpSimple { - /// Returns `true` if at least one input is ram located. - fn has_implicit_load(&self) -> bool { - if self.input0.address_space == "ram" { - return true; - } - if let Some(varnode) = &self.input1 { - if varnode.address_space == "ram" { - return true; - } - } - if let Some(varnode) = &self.input2 { - if varnode.address_space == "ram" { - return true; - } - } - false - } - // Returns `true` if the output is ram located. - fn has_implicit_store(&self) -> bool { - if let Some(varnode) = &self.output { - if varnode.address_space == "ram" { - return true; - } - } - false - } - /// Returns artificial `Def::Load` instructions, if the operants are ram located. - /// Otherwise returns empty `Vec`. + /// Returns `Term`, if the varnode describes an implicit load operation. /// - /// The created instructions use the virtual register `$load_tempX`, whereby `X` is - /// either `0`, `1`or `2` representing which input is used. - /// The created `Tid` is named `instr_
__load`. - fn create_implicit_loads(&self, address: &String) -> Vec> { - let mut explicit_loads = vec![]; - if self.input0.address_space == "ram" { - let load0 = Def::Load { - var: Variable { - name: "$load_temp0".into(), - size: self.input0.size.into(), - is_temp: true, - }, - address: Expression::Const( - self.input0 - .get_ram_address() - .expect("varnode's addressspace is not ram"), - ), - }; - explicit_loads.push(Term { - tid: Tid { - id: format!("instr_{}_{}_load0", address, self.pcode_index), - address: address.to_string(), - }, - term: load0, - }) - } - if let Some(varnode) = &self.input1 { - if varnode.address_space == "ram" { - let load1 = Def::Load { - var: Variable { - name: "$load_temp1".into(), - size: varnode.size.into(), - is_temp: true, - }, - address: Expression::Const( - varnode - .get_ram_address() - .expect("varnode's addressspace is not ram"), - ), - }; - explicit_loads.push(Term { - tid: Tid { - id: format!("instr_{}_{}_load1", address, self.pcode_index), - address: address.to_string(), - }, - term: load1, - }) - } - } - - if let Some(varnode) = &self.input2 { - if varnode.address_space == "ram" { - let load2 = Def::Load { - var: Variable { - name: "$load_temp2".into(), - size: varnode.size.into(), - is_temp: true, - }, - address: Expression::Const( - varnode - .get_ram_address() - .expect("varnode's addressspace is not ram"), - ), - }; - explicit_loads.push(Term { - tid: Tid { - id: format!("instr_{}_{}_load2", address, self.pcode_index), - address: address.to_string(), - }, - term: load2, - }) - } - } - - explicit_loads - } - - /// Translates a single pcode operation into at leas one `Def`. + /// Changes the varnode's `id` and `address_space` to the virtual variable. /// - /// Adds additional `Def::Load`, if the pcode operation performs implicit loads from ram - fn into_ir_def(self, address: &String) -> Vec> { - let mut defs = vec![]; - // if the pcode operation contains implicit load operations, prepend them. - if self.has_implicit_load() { - let mut explicit_loads = self.create_implicit_loads(address); - defs.append(&mut explicit_loads); - } - if self.has_implicit_store() { - todo!() - } - - let def = match self.pcode_mnemonic { - PcodeOperation::ExpressionType(expr_type) => self.create_def(address, expr_type), - PcodeOperation::JmpType(jmp_type) => todo!(), + /// Panics, if varnode's address_space is not `ram` + fn into_explicit_load( + &mut self, + var_name: String, + tid_suffix: String, + address: &String, + pcode_index: u64, + ) -> Term { + let load = Def::Load { + var: Variable { + name: var_name.clone(), + size: self.size.into(), + is_temp: true, + }, + address: Expression::Const( + self.get_ram_address() + .expect("varnode's addressspace is not ram"), + ), }; - defs.push(def); - defs - } - - /// Creates `Def::Store`, `Def::Load` or `Def::Assign` according to the pcode operations' - /// expression type. - fn create_def(self, address: &String, expr_type: ExpressionType) -> Term { - match expr_type { - ExpressionType::LOAD => self.create_load(address), - ExpressionType::STORE => self.create_store(address), - _ => todo!(), - } - } - - /// Translates pcode load operation into `Def::Load` - /// - /// Pcode load instruction: - /// https://spinsel.dev/assets/2020-06-17-ghidra-brainfuck-processor-1/ghidra_docs/language_spec/html/pcodedescription.html#cpui_load - /// Note: input0 ("Constant ID of space to load from") is not considered. - /// - /// Panics, if any of the following applies: - /// * `output` is `None` - /// * load destination is not a variable - /// * `input1` is `None` - /// * `into_ir_expr()` returns `Err` on any varnode - fn create_load(self, address: &String) -> Term { - if !matches!( - self.pcode_mnemonic, - PcodeOperation::ExpressionType(ExpressionType::LOAD) - ) { - panic!("Pcode operation is not LOAD") - } - let target = self.output.expect("Load without output"); - if let Expression::Var(var) = target - .into_ir_expr() - .expect("Load target translation failed") - { - let source = self - .input1 - .expect("Load without source") - .into_ir_expr() - .expect("Load source address translation failed"); - - let def = Def::Load { - var, - address: source, - }; - Term { - tid: Tid { - id: format!("instr_{}_{}", address, self.pcode_index), - address: address.to_string(), - }, - term: def, - } - } else { - panic!("Load target is not a variable") - } - } - - /// Translates pcode store operation into `Def::Store` - /// - /// Pcode load instruction: - /// https://spinsel.dev/assets/2020-06-17-ghidra-brainfuck-processor-1/ghidra_docs/language_spec/html/pcodedescription.html#cpui_store - /// Note: input0 ("Constant ID of space to store into") is not considered. - /// - /// Panics, if any of the following applies: - /// * `input1` is None - /// * `input2` is None - /// * `into_ir_expr()` returns `Err` on any varnode - fn create_store(self, address: &String) -> Term { - if !matches!( - self.pcode_mnemonic, - PcodeOperation::ExpressionType(ExpressionType::STORE) - ) { - panic!("Pcode operation is not STORE") - } - let target_expr = self - .input1 - .expect("Store without target") - .into_ir_expr() - .expect("Store target translation failed."); - - let data = self.input2.expect("Store without source data"); - if !matches!(data.address_space.as_str(), "unique" | "const" | "variable") { - panic!("Store source data is not a variable, temp variable nor constant.") - } - - let source_expr = data - .into_ir_expr() - .expect("Store source translation failed"); - let def = Def::Store { - address: target_expr, - value: source_expr, - }; + // Change varnode to newly introduced explicit variable + self.id = var_name.into(); + self.address_space = "unique".into(); Term { tid: Tid { - id: format!("instr_{}_{}", address, self.pcode_index), + id: format!("instr_{}_{}_{}", address, pcode_index, tid_suffix), address: address.to_string(), }, - term: def, + term: load, } } } @@ -431,15 +227,5 @@ pub struct CallingConventionsProperties { pub killed_by_call_register: Vec, } -/// P-Code operation wrapper type -/// -/// Wrapps expression and jump types for direct deserializations. -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] -#[serde(untagged)] -pub enum PcodeOperation { - ExpressionType(ExpressionType), - JmpType(JmpType), -} - #[cfg(test)] mod tests; diff --git a/src/cwe_checker_lib/src/ghidra_pcode/pcode_op_simple/mod.rs b/src/cwe_checker_lib/src/ghidra_pcode/pcode_op_simple/mod.rs new file mode 100644 index 000000000..be7cdb760 --- /dev/null +++ b/src/cwe_checker_lib/src/ghidra_pcode/pcode_op_simple/mod.rs @@ -0,0 +1,378 @@ +use super::*; +use serde::{Deserialize, Serialize}; +mod jumps; + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] +pub struct PcodeOpSimple { + pub pcode_index: u64, + pub pcode_mnemonic: PcodeOperation, + pub input0: VarnodeSimple, + pub input1: Option, + pub input2: Option, + pub output: Option, +} + +impl PcodeOpSimple { + /// Returns `true` if at least one input is ram located. + pub fn has_implicit_load(&self) -> bool { + if self.input0.address_space == "ram" { + return true; + } + if let Some(varnode) = &self.input1 { + if varnode.address_space == "ram" { + return true; + } + } + if let Some(varnode) = &self.input2 { + if varnode.address_space == "ram" { + return true; + } + } + false + } + // Returns `true` if the output is ram located. + pub fn has_implicit_store(&self) -> bool { + if let Some(varnode) = &self.output { + if varnode.address_space == "ram" { + return true; + } + } + false + } + /// Returns artificial `Def::Load` instructions, if the operants are ram located. + /// Otherwise returns empty `Vec`. Changes ram varnodes into virtual register varnodes + /// using the explicitly loaded value. + /// + /// The created instructions use the virtual register `$load_tempX`, whereby `X` is + /// either `0`, `1`or `2` representing which input is used. + /// The created `Tid` is named `instr_
__load`. + pub fn create_implicit_loads(&mut self, address: &String) -> Vec> { + let mut explicit_loads = vec![]; + if self.input0.address_space == "ram" { + explicit_loads.push(self.input0.into_explicit_load( + "$load_temp0".to_string(), + "load0".to_string(), + address, + self.pcode_index, + )); + } + if let Some(varnode) = self.input1.as_mut() { + if varnode.address_space == "ram" { + explicit_loads.push(varnode.into_explicit_load( + "$load_temp1".to_string(), + "load1".to_string(), + address, + self.pcode_index, + )); + } + } + if let Some(varnode) = self.input2.as_mut() { + if varnode.address_space == "ram" { + explicit_loads.push(varnode.into_explicit_load( + "$load_temp2".to_string(), + "load2".to_string(), + address, + self.pcode_index, + )); + } + } + + explicit_loads + } + + /// Translates a single pcode operation into at leas one `Def`. + /// + /// Adds additional `Def::Load`, if the pcode operation performs implicit loads from ram + pub fn into_ir_def(mut self, address: &String) -> Vec> { + let mut defs = vec![]; + // if the pcode operation contains implicit load operations, prepend them. + if self.has_implicit_load() { + let mut explicit_loads = self.create_implicit_loads(address); + defs.append(&mut explicit_loads); + } + + let def = match self.pcode_mnemonic { + PcodeOperation::ExpressionType(expr_type) => self.create_def(address, expr_type), + PcodeOperation::JmpType(jmp_type) => todo!(), + }; + + defs.push(def); + defs + } + + /// Creates `Def::Store`, `Def::Load` or `Def::Assign` according to the pcode operations' + /// expression type. + fn create_def(&self, address: &String, expr_type: ExpressionType) -> Term { + match expr_type { + ExpressionType::LOAD => self.create_load(address), + ExpressionType::STORE => self.create_store(address), + ExpressionType::COPY => self.create_assign(address), + ExpressionType::SUBPIECE => self.create_subpiece(address), + _ if expr_type.into_ir_unop().is_some() => self.create_unop(address), + _ if expr_type.into_ir_biop().is_some() => self.create_biop(address), + _ if expr_type.into_ir_cast().is_some() => self.create_castop(address), + _ => panic!("Unsupported pcode operation"), + } + } + + /// Translates pcode load operation into `Def::Load` + /// + /// Pcode load instruction: + /// ($GHIDRA_PATH)/docs/languages/html/pcoderef.html#cpui_load + /// Note: input0 ("Constant ID of space to load from") is not considered. + /// + /// Panics, if any of the following applies: + /// * `output` is `None` + /// * load destination is not a variable + /// * `input1` is `None` + /// * `into_ir_expr()` returns `Err` on any varnode + pub fn create_load(&self, address: &String) -> Term { + if !matches!( + self.pcode_mnemonic, + PcodeOperation::ExpressionType(ExpressionType::LOAD) + ) { + panic!("Pcode operation is not LOAD") + } + let target = self.output.as_ref().expect("Load without output"); + if let Expression::Var(var) = target + .into_ir_expr() + .expect("Load target translation failed") + { + let source = self + .input1 + .as_ref() + .expect("Load without source") + .into_ir_expr() + .expect("Load source address translation failed"); + + let def = Def::Load { + var, + address: source, + }; + Term { + tid: Tid { + id: format!("instr_{}_{}", address, self.pcode_index), + address: address.to_string(), + }, + term: def, + } + } else { + panic!("Load target is not a variable") + } + } + + /// Translates pcode store operation into `Def::Store` + /// + /// Pcode store instruction: + /// ($GHIDRA_PATH)/docs/languages/html/pcoderef.html#cpui_store + /// Note: input0 ("Constant ID of space to store into") is not considered. + /// + /// Panics, if any of the following applies: + /// * `input1` is None + /// * `input2` is None + /// * `into_ir_expr()` returns `Err` on any varnode + fn create_store(&self, address: &String) -> Term { + if !matches!( + self.pcode_mnemonic, + PcodeOperation::ExpressionType(ExpressionType::STORE) + ) { + panic!("Pcode operation is not STORE") + } + let target_expr = self + .input1 + .as_ref() + .expect("Store without target") + .into_ir_expr() + .expect("Store target translation failed."); + + let data = self.input2.as_ref().expect("Store without source data"); + if !matches!(data.address_space.as_str(), "unique" | "const" | "register") { + panic!("Store source data is not a variable, temp variable nor constant.") + } + + let source_expr = data + .into_ir_expr() + .expect("Store source translation failed"); + let def = Def::Store { + address: target_expr, + value: source_expr, + }; + + Term { + tid: Tid { + id: format!("instr_{}_{}", address, self.pcode_index), + address: address.to_string(), + }, + term: def, + } + } + + /// Translates pcode SUBPIECE instruction into `Def` with `Expression::Subpiece`. + /// + /// ($GHIDRA_PATH)/docs/languages/html/pcoderef.html#cpui_subpiece + /// + /// Panics, if + /// * self.input1 is `None` or cannot be translated into `Expression:Const` + /// * Amount of bytes to truncate cannot be translated into `u64` + /// * `into_ir_expr()` returns `Err` `on self.input0` + fn create_subpiece(&self, address: &String) -> Term { + if let Expression::Const(truncate) = self + .input1 + .as_ref() + .expect("input0 of subpiece is None") + .into_ir_expr() + .expect("Subpiece truncation number translation failed") + { + let expr = Expression::Subpiece { + low_byte: truncate.try_to_u64().unwrap().into(), + size: self + .output + .as_ref() + .expect("Subpiece output is None") + .size + .into(), + arg: Box::new( + self.input0 + .into_ir_expr() + .expect("Subpiece source data translation failed"), + ), + }; + self.wrap_in_assign_or_store(address, expr) + } else { + panic!("Number of truncation bytes is not a constant") + } + } + + /// Translates pcode operation with one input into `Term` with unary `Expression`. + /// The mapping is implemented in `into_ir_unop`. + /// + /// Panics if, + /// * `self.pcode_mnemonic` is not `PcodeOperation::ExpressionType` + /// * `self.output` is `None` or `into_it_expr()` returns not an `Expression::Var` + /// * `into_ir_expr()` returns `Err` on `self.output` or `self.input0` + fn create_unop(&self, address: &String) -> Term { + if let PcodeOperation::ExpressionType(expr_type) = self.pcode_mnemonic { + let expr = Expression::UnOp { + op: expr_type + .into_ir_unop() + .expect("Translation into unary operation type failed"), + arg: Box::new(self.input0.into_ir_expr().unwrap()), + }; + self.wrap_in_assign_or_store(address, expr) + } else { + panic!("Not an expression type") + } + } + + /// Translates a pcode operation with two inputs into `Term` with binary `Expression`. + /// The mapping is implemented in `into_ir_biop`. + /// + /// Panics if, + /// * `self.pcode_mnemonic` is not `PcodeOperation::ExpressionType` + /// * `self.output` is `None` or `into_it_expr()` returns not an `Expression::Var` + /// * `into_ir_expr()` returns `Err` on `self.output`, `self.input0` or `self.input1` + pub fn create_biop(&self, address: &String) -> Term { + if let PcodeOperation::ExpressionType(expr_type) = self.pcode_mnemonic { + let expr = Expression::BinOp { + op: expr_type + .into_ir_biop() + .expect("Translation into binary operation type failed"), + lhs: Box::new(self.input0.into_ir_expr().unwrap()), + rhs: Box::new( + self.input1 + .as_ref() + .expect("No input1 for binary operation") + .into_ir_expr() + .unwrap(), + ), + }; + self.wrap_in_assign_or_store(address, expr) + } else { + panic!("Not an expression type") + } + } + + /// Translates a cast pcode operation into `Term` with `Expression::Cast`. + /// The mapping is implemented in `into_ir_castop`. + /// + /// Panics if, + /// * `self.pcode_mnemonic` is not `PcodeOperation::ExpressionType` + /// * `self.output` is `None` or `into_it_expr()` returns not an `Expression::Var` + /// * `into_ir_expr()` returns `Err` on `self.output` or `self.input0` + pub fn create_castop(&self, address: &String) -> Term { + if let PcodeOperation::ExpressionType(expr_type) = self.pcode_mnemonic { + let expr = Expression::Cast { + op: expr_type + .into_ir_cast() + .expect("Translation into cast operation failed"), + size: self + .output + .clone() + .expect("No output for cast operation") + .size + .into(), + arg: Box::new(self.input0.into_ir_expr().unwrap()), + }; + self.wrap_in_assign_or_store(address, expr) + } else { + panic!("Not an expression type") + } + } + + /// Translates `PcodeOperation::COPY` into `Term` containing `Def::Assign`. + pub fn create_assign(&self, address: &String) -> Term { + if let PcodeOperation::ExpressionType(ExpressionType::COPY) = self.pcode_mnemonic { + let expr = self.input0.into_ir_expr().unwrap(); + self.wrap_in_assign_or_store(address, expr) + } else { + panic!("PcodeOperation is not COPY") + } + } + + /// Helper function for creating a Def::Assign operation, or Def::Store if an implicit + /// store instruction is present. + /// + /// Panics if, + /// * for Assign case: self.output is `None` or `into_ir_expr()` returns `Err` + /// * for Assign case: self.output is not `Expression::Var` + /// * for Store case: self.output is `None` or `get_ram_address()` returns `None` + pub fn wrap_in_assign_or_store(&self, address: &String, expr: Expression) -> Term { + let tid = Tid { + id: format!("instr_{}_{}", address, self.pcode_index), + address: address.to_string(), + }; + if self.has_implicit_store() { + return Term { + tid, + term: Def::Store { + address: Expression::Const( + self.output + .as_ref() + .expect("No output varnode") + .get_ram_address() + .expect("Output varnode is not ram"), + ), + value: expr, + }, + }; + } else { + if let Expression::Var(var) = self + .output + .as_ref() + .expect("No output varnode") + .into_ir_expr() + .unwrap() + { + Term { + tid, + term: Def::Assign { var, value: expr }, + } + } else { + panic!("Output varnode is not a variable") + } + } + } +} + +#[cfg(test)] +mod tests; diff --git a/src/cwe_checker_lib/src/ghidra_pcode/pcode_op_simple/tests.rs b/src/cwe_checker_lib/src/ghidra_pcode/pcode_op_simple/tests.rs new file mode 100644 index 000000000..ecf1f3e52 --- /dev/null +++ b/src/cwe_checker_lib/src/ghidra_pcode/pcode_op_simple/tests.rs @@ -0,0 +1,607 @@ +use super::*; +use crate::ghidra_pcode::tests::*; +use crate::ghidra_pcode::ExpressionType::*; +use crate::ghidra_pcode::PcodeOperation::ExpressionType; +use crate::ghidra_pcode::PcodeOperation::JmpType; +use crate::pcode::JmpType::*; +use crate::variable; +use crate::{def, expr}; + +/// Simplified construction of pcode operation with `pcode_index: 1` and +/// pcode_mnemonic: ExpressionType(INT_ADD). +fn mock_pcode_op_add( + input0: VarnodeSimple, + input1: Option, + output: Option, +) -> PcodeOpSimple { + PcodeOpSimple { + pcode_index: 1, + pcode_mnemonic: ExpressionType(INT_ADD), + input0, + input1, + input2: None, + output, + } +} + +impl PcodeOpSimple { + fn with_mnemonic(&self, mnemonic: PcodeOperation) -> PcodeOpSimple { + PcodeOpSimple { + pcode_index: self.pcode_index, + pcode_mnemonic: mnemonic, + input0: self.input0.clone(), + input1: self.input1.clone(), + input2: self.input2.clone(), + output: self.output.clone(), + } + } + + fn with_varnodes( + mut self, + input0: VarnodeSimple, + input1: Option, + input2: Option, + output: Option, + ) -> PcodeOpSimple { + self.input0 = input0; + self.input1 = input1; + self.input2 = input2; + self.output = output; + self + } +} + +#[test] +fn test_pcode_op_has_implicit_load() { + let ram_varnode = mock_varnode("ram", "0x42", 8); + let varnode = mock_varnode("register", "RAX", 8); + let pcode_op = PcodeOpSimple { + pcode_index: 1, + pcode_mnemonic: ExpressionType(STORE), + input0: ram_varnode.clone(), + input1: None, + input2: None, + output: None, + }; + assert_eq!(pcode_op.has_implicit_load(), true); + assert_eq!( + pcode_op + .clone() + .with_varnodes(varnode.clone(), Some(ram_varnode.clone()), None, None) + .has_implicit_load(), + true + ); + assert_eq!( + pcode_op + .clone() + .with_varnodes(varnode.clone(), None, Some(ram_varnode.clone()), None) + .has_implicit_load(), + true + ); + assert_eq!( + pcode_op + .clone() + .with_varnodes(varnode.clone(), None, None, None) + .has_implicit_load(), + false + ); + assert_eq!( + pcode_op + .clone() + .with_varnodes(varnode.clone(), Some(varnode.clone()), None, None) + .has_implicit_load(), + false + ); + assert_eq!( + pcode_op + .with_varnodes(varnode.clone(), None, Some(varnode.clone()), None) + .has_implicit_load(), + false + ); +} + +#[test] +fn test_pcode_op_has_implicit_store() { + let ram_varnode = mock_varnode("ram", "0x42", 8); + let varnode = mock_varnode("register", "RAX", 8); + assert_eq!( + mock_pcode_op_add(varnode.clone(), None, Some(ram_varnode)).has_implicit_store(), + true + ); + assert_eq!( + mock_pcode_op_add(varnode.clone(), None, Some(varnode)).has_implicit_store(), + false + ); +} + +#[test] +fn test_implicit_load_translation() { + let ram_varnode = mock_varnode("ram", "0x42", 8); + let varnode = mock_varnode("register", "RAX", 8); + let load0_target = Variable { + name: "$load_temp0".into(), + size: 8.into(), + is_temp: true, + }; + let expected_load0 = Term { + tid: Tid { + id: "".into(), + address: "0x1234".into(), + }, + term: Def::Load { + var: load0_target.clone(), + address: expr!("0x42:8"), + }, + }; + + let mut load1_target = load0_target.clone(); + load1_target.name = "$load_temp1".into(); + let mut expected_load1 = expected_load0.clone(); + expected_load1.term = Def::Load { + var: load1_target.clone(), + address: expr!("0x42:8"), + }; + + let mut load2_target = load0_target.clone(); + load2_target.name = "$load_temp2".into(); + let mut expected_load2 = expected_load0.clone(); + expected_load2.term = Def::Load { + var: load2_target.clone(), + address: expr!("0x42:8"), + }; + // No implicit load + assert_eq!( + mock_pcode_op_add(varnode.clone(), None, None).create_implicit_loads(&"0x1234".to_string()), + vec![] + ); + // input0 is implicit load + assert_eq!( + mock_pcode_op_add(ram_varnode.clone(), None, None) + .create_implicit_loads(&"0x1234".to_string()), + vec![expected_load0 + .clone() + .with_tid_id("instr_0x1234_1_load0".into())] + ); + // input1 is implicit load + assert_eq!( + mock_pcode_op_add(varnode.clone(), Some(ram_varnode.clone()), None) + .create_implicit_loads(&"0x1234".to_string()), + vec![expected_load1 + .clone() + .with_tid_id("instr_0x1234_1_load1".into())] + ); + // input2 is implicit load + assert_eq!( + PcodeOpSimple { + pcode_index: 1, + pcode_mnemonic: ExpressionType(STORE), + input0: varnode.clone(), + input1: None, + input2: Some(ram_varnode.clone()), + output: None + } + .create_implicit_loads(&"0x1234".to_string()), + vec![expected_load2 + .clone() + .with_tid_id("instr_0x1234_1_load2".into())] + ); + // input0, input1 and input2 are implicit loads + assert_eq!( + PcodeOpSimple { + pcode_index: 1, + pcode_mnemonic: ExpressionType(INT_ZEXT), + input0: ram_varnode.clone(), + input1: Some(ram_varnode.clone()), + input2: Some(ram_varnode.clone()), + output: None + } + .create_implicit_loads(&"0x1234".to_string()), + vec![ + expected_load0.with_tid_id("instr_0x1234_1_load0".into()), + expected_load1.with_tid_id("instr_0x1234_1_load1".into()), + expected_load2.with_tid_id("instr_0x1234_1_load2".into()), + ] + ); +} + +#[test] +fn test_create_load() { + let load_target = mock_varnode("register", "RAX", 8); + let source = mock_varnode("const", "0x0012345", 8); + let pcode_op = PcodeOpSimple { + pcode_index: 1, + pcode_mnemonic: ExpressionType(LOAD), + input0: source.clone(), + input1: Some(source), + input2: None, + output: Some(load_target), + }; + + assert_eq!( + pcode_op.create_load(&"0xFFFFFF".to_string()), + Term { + tid: Tid { + id: "instr_0xFFFFFF_1".into(), + address: "0xFFFFFF".into() + }, + term: def!["RAX:8 := Load from 0x0012345:8"].term + } + ); +} + +#[test] +#[should_panic] +fn test_create_load_not_load() { + mock_pcode_op_add(mock_varnode("space", "id", 8), None, None).create_load(&"0x123".to_string()); +} + +#[test] +#[should_panic] +fn test_create_load_no_output() { + PcodeOpSimple { + pcode_index: 1, + pcode_mnemonic: ExpressionType(LOAD), + input0: mock_varnode("space", "id", 8), + input1: Some(mock_varnode("const", "0x200", 8)), + input2: None, + output: None, + } + .create_load(&"0x123".to_string()); +} + +#[test] +#[should_panic] +fn test_create_load_no_source() { + PcodeOpSimple { + pcode_index: 1, + pcode_mnemonic: ExpressionType(LOAD), + input0: mock_varnode("space", "id", 8), + input1: None, + input2: None, + output: Some(mock_varnode("register", "RAX", 8)), + } + .create_load(&"0x123".to_string()); +} + +#[test] +#[should_panic] +fn test_create_load_target_not_var() { + PcodeOpSimple { + pcode_index: 1, + pcode_mnemonic: ExpressionType(LOAD), + input0: mock_varnode("space", "id", 8), + input1: Some(mock_varnode("const", "0x200", 8)), + input2: None, + output: Some(mock_varnode("const", "0x4321", 8)), + } + .create_load(&"0x123".to_string()); +} + +#[test] +fn test_create_store() { + let data = mock_varnode("const", "0x0042", 8); + let target = mock_varnode("register", "RAX", 8); + let pcode_op = PcodeOpSimple { + pcode_index: 5, + pcode_mnemonic: ExpressionType(STORE), + input0: mock_varnode("space", "id", 8), + input1: Some(target), + input2: Some(data), + output: None, + }; + assert_eq!( + pcode_op.create_store(&"0x00ABCDEF".to_string()), + Term { + tid: Tid { + id: "instr_0x00ABCDEF_5".into(), + address: "0x00ABCDEF".into() + }, + term: def!["Store at RAX:8 := 0x0042:8"].term + } + ) +} + +#[test] +#[should_panic] +fn test_create_store_not_store() { + mock_pcode_op_add(mock_varnode("space", "id", 8), None, None) + .create_store(&"0x123".to_string()); +} + +#[test] +#[should_panic] +fn test_create_store_no_target() { + PcodeOpSimple { + pcode_index: 1, + pcode_mnemonic: ExpressionType(STORE), + input0: mock_varnode("space", "id", 8), + input1: None, + input2: Some(mock_varnode("const", "0x4321", 8)), + output: None, + } + .create_store(&"0x123".to_string()); +} + +#[test] +#[should_panic] +fn test_create_store_from_ram() { + PcodeOpSimple { + pcode_index: 1, + pcode_mnemonic: ExpressionType(STORE), + input0: mock_varnode("space", "id", 8), + input1: Some(mock_varnode("const", "0x4321", 8)), + input2: Some(mock_varnode("ram", "0xFFFF01", 8)), + output: None, + } + .create_store(&"0x123".to_string()); +} + +#[test] +fn test_create_subpice() { + let op = PcodeOpSimple { + pcode_index: 1, + pcode_mnemonic: ExpressionType(SUBPIECE), + input0: mock_varnode("const", "0xAABBCCDD", 8), + input1: Some(mock_varnode("const", "0x3", 1)), + input2: None, + output: Some(mock_varnode("register", "EAX", 4)), + }; + let expected_expr = Expression::Subpiece { + low_byte: 3.into(), + size: 4.into(), + arg: Box::new(Expression::Const(Bitvector::from_u64(0xAABBCCDD))), + }; + let expected = Term { + tid: Tid { + id: "instr_0x1234_1".to_string(), + address: "0x1234".to_string(), + }, + term: Def::Assign { + var: variable!("EAX:4"), + value: expected_expr, + }, + }; + assert_eq!(op.create_subpiece(&"0x1234".to_string()), expected); +} + +#[test] +#[should_panic] +fn test_create_subpiece_with_non_constant() { + PcodeOpSimple { + pcode_index: 1, + pcode_mnemonic: ExpressionType(SUBPIECE), + input0: mock_varnode("const", "0xABCDEF", 8), + input1: Some(mock_varnode("register", "RAX", 8)), + input2: None, + output: Some(mock_varnode("register", "EAX", 4)), + } + .create_subpiece(&"0x1234".to_string()); +} + +#[test] +fn test_create_unop() { + let op = PcodeOpSimple { + pcode_index: 1, + pcode_mnemonic: ExpressionType(INT_NEGATE), + input0: mock_varnode("register", "RAX", 8), + input1: None, + input2: None, + output: Some(mock_varnode("register", "RAX", 8)), + }; + let mut expected = def!["instr_0x1234_1: RAX:8 = -(RAX:8)"]; + expected.tid.address = "0x1234".to_string(); + assert_eq!(op.create_unop(&"0x1234".to_string()), expected); + + expected.term = def!["RAX:8 = ¬(RAX:8)"].term; + assert_eq!( + op.with_mnemonic(ExpressionType(BOOL_NEGATE)) + .create_unop(&"0x1234".to_string()), + expected + ) +} + +#[test] +#[should_panic] +fn test_create_unop_not_expression_type() { + PcodeOpSimple { + pcode_index: 1, + pcode_mnemonic: JmpType(CALL), + input0: mock_varnode("register", "RAX", 8), + input1: None, + input2: None, + output: Some(mock_varnode("register", "RAX", 8)), + } + .create_unop(&"0xFFFF".to_string()); +} + +#[test] +#[should_panic] +fn test_create_unop_not_unop_type() { + PcodeOpSimple { + pcode_index: 1, + pcode_mnemonic: ExpressionType(INT_AND), + input0: mock_varnode("register", "RAX", 8), + input1: None, + input2: None, + output: Some(mock_varnode("register", "RAX", 8)), + } + .create_unop(&"0xFFFF".to_string()); +} + +#[test] +fn test_create_biop() { + let op = mock_pcode_op_add( + mock_varnode("register", "RAX", 8), + Some(mock_varnode("const", "0xCAFE", 4)), + Some(mock_varnode("register", "RAX", 8)), + ); + + let expected = Term { + tid: Tid { + id: "instr_0x1234_1".into(), + address: "0x1234".into(), + }, + term: def!["RAX:8 = RAX:8 + 0xCAFE:4"].term, + }; + assert_eq!(op.create_biop(&"0x1234".to_string()), expected) +} + +#[test] +#[should_panic] +fn test_create_biop_not_expression_type() { + PcodeOpSimple { + pcode_index: 1, + pcode_mnemonic: JmpType(CALL), + input0: mock_varnode("register", "RAX", 8), + input1: Some(mock_varnode("const", "0xCAFE", 4)), + input2: None, + output: Some(mock_varnode("register", "RAX", 8)), + } + .create_biop(&"0x1234".to_string()); +} + +#[test] +#[should_panic] +fn test_create_biop_not_biop_type() { + PcodeOpSimple { + pcode_index: 1, + pcode_mnemonic: ExpressionType(COPY), + input0: mock_varnode("register", "RAX", 8), + input1: Some(mock_varnode("const", "0xCAFE", 4)), + input2: None, + output: Some(mock_varnode("register", "RAX", 8)), + } + .create_biop(&"0x1234".to_string()); +} + +#[test] +fn test_create_cast_op() { + let op = PcodeOpSimple { + pcode_index: 9, + pcode_mnemonic: PcodeOperation::ExpressionType(INT_ZEXT), + input0: mock_varnode("const", "0x1", 1), + input1: None, + input2: None, + output: Some(mock_varnode("register", "RDI", 8)), + }; + let expected_expr = Expression::Cast { + op: CastOpType::IntZExt, + size: 8.into(), + arg: Box::new(expr!("0x1:1")), + }; + let expected = Term { + tid: Tid { + id: "instr_0x4321_9".into(), + address: "0x4321".into(), + }, + term: Def::Assign { + var: variable!("RDI:8"), + value: expected_expr, + }, + }; + assert_eq!(op.create_castop(&"0x4321".to_string()), expected); +} + +#[test] +#[should_panic] +fn test_create_castop_not_expression_type() { + PcodeOpSimple { + pcode_index: 1, + pcode_mnemonic: JmpType(CALL), + input0: mock_varnode("register", "RAX", 8), + input1: Some(mock_varnode("const", "0xCAFE", 4)), + input2: None, + output: Some(mock_varnode("register", "RAX", 8)), + } + .create_castop(&"0x1234".to_string()); +} + +#[test] +#[should_panic] +fn test_create_castop_not_castop_type() { + PcodeOpSimple { + pcode_index: 1, + pcode_mnemonic: ExpressionType(COPY), + input0: mock_varnode("register", "RAX", 8), + input1: Some(mock_varnode("const", "0xCAFE", 4)), + input2: None, + output: Some(mock_varnode("register", "RAX", 8)), + } + .create_castop(&"0x1234".to_string()); +} + +#[test] +fn test_create_assign() { + let op = PcodeOpSimple { + pcode_index: 2, + pcode_mnemonic: ExpressionType(COPY), + input0: mock_varnode("const", "0x42", 1), + input1: None, + input2: None, + output: Some(mock_varnode("register", "ZF", 1)), + }; + let expected = Term { + tid: Tid { + id: "instr_0x1111_2".to_string(), + address: "0x1111".to_string(), + }, + term: def!["ZF:1 = 0x42:1"].term, + }; + assert_eq!(expected, op.create_assign(&"0x1111".to_string())) +} + +#[test] +#[should_panic] +fn test_create_assign_not_copy_type() { + PcodeOpSimple { + pcode_index: 1, + pcode_mnemonic: ExpressionType(BOOL_AND), + input0: mock_varnode("register", "RAX", 8), + input1: Some(mock_varnode("const", "0xCAFE", 4)), + input2: None, + output: Some(mock_varnode("register", "RAX", 8)), + } + .create_assign(&"0x1234".to_string()); +} + +#[test] +fn test_wrap_in_assign_or_store() { + let mut op = mock_pcode_op_add( + mock_varnode("register", "EAX", 4), + Some(mock_varnode("const", "0xCAFE", 4)), + Some(mock_varnode("register", "EAX", 4)), + ); + + let expr = expr!("EAX:4 + 0xCAFE:4"); + // test Assign + let mut expected = Term { + tid: Tid { + id: "instr_0xAFFE_1".to_string(), + address: "0xAFFE".to_string(), + }, + term: def!["EAX:4 = EAX:4 + 0xCAFE:4"].term, + }; + assert_eq!( + expected, + op.wrap_in_assign_or_store(&"0xAFFE".to_string(), expr.clone()) + ); + + // test Store + op.output = Some(mock_varnode("ram", "0x1234", 4)); + expected.term = def!["Store at 0x1234:4 := EAX:4 + 0xCAFE:4"].term; + assert_eq!( + op.wrap_in_assign_or_store(&"0xAFFE".to_string(), expr), + expected + ) +} + +#[test] +#[should_panic] +fn test_wrap_in_assign_or_store_output_not_variable_nor_implicit_store() { + mock_pcode_op_add( + mock_varnode("register", "EAX", 4), + Some(mock_varnode("const", "0xCAFE", 4)), + Some(mock_varnode("const", "0xFFFF", 4)), + ) + .wrap_in_assign_or_store(&"0x1234".to_string(), expr!("0x1111:4")); +} diff --git a/src/cwe_checker_lib/src/ghidra_pcode/pcode_operations.rs b/src/cwe_checker_lib/src/ghidra_pcode/pcode_operations.rs new file mode 100644 index 000000000..a9ff25506 --- /dev/null +++ b/src/cwe_checker_lib/src/ghidra_pcode/pcode_operations.rs @@ -0,0 +1,149 @@ +//! This module models ghidra pcode operations and implements their mapping to +//! [UnOpType](crate::intermediate_representation::UnOpType), +//! [BinOpType](crate::intermediate_representation::BinOpType) and +//! [CastOpType](crate::intermediate_representation::CastOpType). + +use crate::{ + intermediate_representation::{BinOpType, CastOpType, Expression, Jmp, Tid, UnOpType}, + pcode::{ExpressionType, JmpType}, +}; +use serde::{Deserialize, Serialize}; + +/// P-Code operation wrapper type +/// +/// Wrapps expression and jump types for direct deserializations. +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] +#[serde(untagged)] +pub enum PcodeOperation { + ExpressionType(ExpressionType), + JmpType(JmpType), +} + +impl ExpressionType { + /// Returns the IR `UnOpType`, otherwise `None`. + pub fn into_ir_unop(&self) -> Option { + use ExpressionType::*; + match self { + INT_NEGATE => Some(UnOpType::IntNegate), + INT_2COMP => Some(UnOpType::Int2Comp), + BOOL_NEGATE => Some(UnOpType::BoolNegate), + FLOAT_NEG => Some(UnOpType::FloatNegate), + FLOAT_ABS => Some(UnOpType::FloatAbs), + FLOAT_SQRT => Some(UnOpType::FloatSqrt), + FLOAT_CEIL => Some(UnOpType::FloatCeil), + FLOAT_FLOOR => Some(UnOpType::FloatFloor), + FLOAT_ROUND => Some(UnOpType::FloatRound), + FLOAT_NAN => Some(UnOpType::FloatNaN), + _ => None, + } + } + + /// Returns the IR `BinOpType`, otherwise `None`. + pub fn into_ir_biop(&self) -> Option { + use ExpressionType::*; + match self { + PIECE => Some(BinOpType::Piece), + INT_EQUAL => Some(BinOpType::IntEqual), + INT_NOTEQUAL => Some(BinOpType::IntNotEqual), + INT_LESS => Some(BinOpType::IntLess), + INT_SLESS => Some(BinOpType::IntSLess), + INT_LESSEQUAL => Some(BinOpType::IntLessEqual), + INT_SLESSEQUAL => Some(BinOpType::IntSLessEqual), + INT_ADD => Some(BinOpType::IntAdd), + INT_SUB => Some(BinOpType::IntSub), + INT_CARRY => Some(BinOpType::IntCarry), + INT_SCARRY => Some(BinOpType::IntSCarry), + INT_SBORROW => Some(BinOpType::IntSBorrow), + INT_XOR => Some(BinOpType::IntXOr), + INT_AND => Some(BinOpType::IntAnd), + INT_OR => Some(BinOpType::IntOr), + INT_LEFT => Some(BinOpType::IntLeft), + INT_RIGHT => Some(BinOpType::IntRight), + INT_SRIGHT => Some(BinOpType::IntSRight), + INT_MULT => Some(BinOpType::IntMult), + INT_DIV => Some(BinOpType::IntDiv), + INT_REM => Some(BinOpType::IntRem), + INT_SDIV => Some(BinOpType::IntSDiv), + INT_SREM => Some(BinOpType::IntSRem), + BOOL_XOR => Some(BinOpType::BoolXOr), + BOOL_AND => Some(BinOpType::BoolAnd), + BOOL_OR => Some(BinOpType::BoolOr), + FLOAT_EQUAL => Some(BinOpType::FloatEqual), + FLOAT_NOTEQUAL => Some(BinOpType::FloatNotEqual), + FLOAT_LESS => Some(BinOpType::FloatLess), + FLOAT_LESSEQUAL => Some(BinOpType::FloatLessEqual), + FLOAT_ADD => Some(BinOpType::FloatAdd), + FLOAT_SUB => Some(BinOpType::FloatSub), + FLOAT_MULT => Some(BinOpType::FloatMult), + FLOAT_DIV => Some(BinOpType::FloatDiv), + _ => None, + } + } + + /// Returns the IR `CastOpType`, otherwise `None`. + pub fn into_ir_cast(&self) -> Option { + use ExpressionType::*; + match self { + INT_ZEXT => Some(CastOpType::IntZExt), + INT_SEXT => Some(CastOpType::IntSExt), + INT2FLOAT => Some(CastOpType::Int2Float), + FLOAT2FLOAT => Some(CastOpType::Float2Float), + TRUNC => Some(CastOpType::Trunc), + POPCOUNT => Some(CastOpType::PopCount), + _ => None, + } + } +} + +impl JmpType { + pub fn into_ir_branch(&self, target: Tid) -> Jmp { + if matches!(self, JmpType::BRANCH) { + Jmp::Branch(target) + } else { + panic!("Not a branch operation") + } + } + + pub fn into_ir_cbranch(&self, target: Tid, condition: Expression) -> Jmp { + if matches!(self, JmpType::CBRANCH) { + Jmp::CBranch { target, condition } + } else { + panic!("Not a conditional branch operation") + } + } + + pub fn into_ir_return(&self, expression: Expression) -> Jmp { + if matches!(self, JmpType::RETURN) { + Jmp::Return(expression) + } else { + panic!("Not a return operation") + } + } + + pub fn into_ir_branch_indirect(&self, target: Expression, return_: Option) -> Jmp { + if matches!(self, JmpType::CALLIND) { + Jmp::CallInd { target, return_ } + } else { + panic!("Not a call indirect operation") + } + } + + pub fn into_ir_call(&self, target: Tid, return_: Option) -> Jmp { + if matches!(self, JmpType::CALL) { + Jmp::Call { target, return_ } + } else { + panic!("Not a call operation") + } + } + + pub fn into_ir_call_other(&self, description: String, return_: Option) -> Jmp { + if matches!(self, JmpType::CALLOTHER) { + Jmp::CallOther { + description, + return_, + } + } else { + panic!("Not a call operation") + } + } +} diff --git a/src/cwe_checker_lib/src/ghidra_pcode/tests.rs b/src/cwe_checker_lib/src/ghidra_pcode/tests.rs index 7698b628c..74de2f089 100644 --- a/src/cwe_checker_lib/src/ghidra_pcode/tests.rs +++ b/src/cwe_checker_lib/src/ghidra_pcode/tests.rs @@ -1,9 +1,7 @@ use super::*; -use crate::ghidra_pcode::PcodeOperation::ExpressionType; -use crate::pcode::ExpressionType::*; -use crate::{bitvec, def, expr, variable}; +use crate::{bitvec, variable}; -fn mock_varnode(addressspace: &str, id: &str, size: u64) -> VarnodeSimple { +pub fn mock_varnode(addressspace: &str, id: &str, size: u64) -> VarnodeSimple { VarnodeSimple { address_space: addressspace.to_string(), id: id.to_string(), @@ -11,50 +9,6 @@ fn mock_varnode(addressspace: &str, id: &str, size: u64) -> VarnodeSimple { } } -/// Simplified construction of pcode operation with `pcode_index: 1` and -/// pcode_mnemonic: ExpressionType(INT_ADD). -fn mock_pcode_op_add( - input0: VarnodeSimple, - input1: Option, - output: Option, -) -> PcodeOpSimple { - PcodeOpSimple { - pcode_index: 1, - pcode_mnemonic: ExpressionType(INT_ADD), - input0, - input1, - input2: None, - output, - } -} - -impl PcodeOpSimple { - fn with_mnemonic(&self, mnemonic: PcodeOperation) -> PcodeOpSimple { - PcodeOpSimple { - pcode_index: self.pcode_index, - pcode_mnemonic: mnemonic, - input0: self.input0.clone(), - input1: self.input1.clone(), - input2: self.input2.clone(), - output: self.output.clone(), - } - } - - fn with_varnodes( - mut self, - input0: VarnodeSimple, - input1: Option, - input2: Option, - output: Option, - ) -> PcodeOpSimple { - self.input0 = input0; - self.input1 = input1; - self.input2 = input2; - self.output = output; - self - } -} - #[test] fn test_varnode_into_const() { if let Expression::Const(c) = mock_varnode("const", "0x0", 8).into_ir_expr().unwrap() { @@ -114,288 +68,3 @@ fn test_alternative_varnode_into_ram_address() { None ); } - -#[test] -fn test_pcode_op_has_implicit_load() { - let ram_varnode = mock_varnode("ram", "0x42", 8); - let varnode = mock_varnode("register", "RAX", 8); - let pcode_op = PcodeOpSimple { - pcode_index: 1, - pcode_mnemonic: ExpressionType(STORE), - input0: ram_varnode.clone(), - input1: None, - input2: None, - output: None, - }; - assert_eq!(pcode_op.has_implicit_load(), true); - assert_eq!( - pcode_op - .clone() - .with_varnodes(varnode.clone(), Some(ram_varnode.clone()), None, None) - .has_implicit_load(), - true - ); - assert_eq!( - pcode_op - .clone() - .with_varnodes(varnode.clone(), None, Some(ram_varnode.clone()), None) - .has_implicit_load(), - true - ); - assert_eq!( - pcode_op - .clone() - .with_varnodes(varnode.clone(), None, None, None) - .has_implicit_load(), - false - ); - assert_eq!( - pcode_op - .clone() - .with_varnodes(varnode.clone(), Some(varnode.clone()), None, None) - .has_implicit_load(), - false - ); - assert_eq!( - pcode_op - .with_varnodes(varnode.clone(), None, Some(varnode.clone()), None) - .has_implicit_load(), - false - ); -} - -#[test] -fn test_pcode_op_has_implicit_store() { - let ram_varnode = mock_varnode("ram", "0x42", 8); - let varnode = mock_varnode("register", "RAX", 8); - assert_eq!( - mock_pcode_op_add(varnode.clone(), None, Some(ram_varnode)).has_implicit_store(), - true - ); - assert_eq!( - mock_pcode_op_add(varnode.clone(), None, Some(varnode)).has_implicit_store(), - false - ); -} - -#[test] -fn test_implicit_load_translation() { - let ram_varnode = mock_varnode("ram", "0x42", 8); - let varnode = mock_varnode("register", "RAX", 8); - let load0_target = Variable { - name: "$load_temp0".into(), - size: 8.into(), - is_temp: true, - }; - let expected_load0 = Term { - tid: Tid { - id: "".into(), - address: "0x1234".into(), - }, - term: Def::Load { - var: load0_target.clone(), - address: expr!("0x42:8"), - }, - }; - - let mut load1_target = load0_target.clone(); - load1_target.name = "$load_temp1".into(); - let mut expected_load1 = expected_load0.clone(); - expected_load1.term = Def::Load { - var: load1_target.clone(), - address: expr!("0x42:8"), - }; - - let mut load2_target = load0_target.clone(); - load2_target.name = "$load_temp2".into(); - let mut expected_load2 = expected_load0.clone(); - expected_load2.term = Def::Load { - var: load2_target.clone(), - address: expr!("0x42:8"), - }; - // No implicit load - assert_eq!( - mock_pcode_op_add(varnode.clone(), None, None).create_implicit_loads(&"0x1234".to_string()), - vec![] - ); - // input0 is implicit load - assert_eq!( - mock_pcode_op_add(ram_varnode.clone(), None, None) - .create_implicit_loads(&"0x1234".to_string()), - vec![expected_load0 - .clone() - .with_tid_id("instr_0x1234_1_load0".into())] - ); - // input1 is implicit load - assert_eq!( - mock_pcode_op_add(varnode.clone(), Some(ram_varnode.clone()), None) - .create_implicit_loads(&"0x1234".to_string()), - vec![expected_load1 - .clone() - .with_tid_id("instr_0x1234_1_load1".into())] - ); - // input2 is implicit load - assert_eq!( - PcodeOpSimple { - pcode_index: 1, - pcode_mnemonic: ExpressionType(STORE), - input0: varnode.clone(), - input1: None, - input2: Some(ram_varnode.clone()), - output: None - } - .create_implicit_loads(&"0x1234".to_string()), - vec![expected_load2 - .clone() - .with_tid_id("instr_0x1234_1_load2".into())] - ); - // input0, input1 and input2 are implicit loads - assert_eq!( - PcodeOpSimple { - pcode_index: 1, - pcode_mnemonic: ExpressionType(INT_ZEXT), - input0: ram_varnode.clone(), - input1: Some(ram_varnode.clone()), - input2: Some(ram_varnode.clone()), - output: None - } - .create_implicit_loads(&"0x1234".to_string()), - vec![ - expected_load0.with_tid_id("instr_0x1234_1_load0".into()), - expected_load1.with_tid_id("instr_0x1234_1_load1".into()), - expected_load2.with_tid_id("instr_0x1234_1_load2".into()), - ] - ); -} - -#[test] -fn test_create_load() { - let load_target = mock_varnode("register", "RAX", 8); - let source = mock_varnode("const", "0x0012345", 8); - let pcode_op = PcodeOpSimple { - pcode_index: 1, - pcode_mnemonic: ExpressionType(LOAD), - input0: source.clone(), - input1: Some(source), - input2: None, - output: Some(load_target), - }; - - assert_eq!( - pcode_op.create_load(&"0xFFFFFF".to_string()), - Term { - tid: Tid { - id: "instr_0xFFFFFF_1".into(), - address: "0xFFFFFF".into() - }, - term: def!["RAX:8 := Load from 0x0012345:8"].term - } - ); -} - -#[test] -#[should_panic] -fn test_create_load_not_load() { - mock_pcode_op_add(mock_varnode("space", "id", 8), None, None).create_load(&"0x123".to_string()); -} - -#[test] -#[should_panic] -fn test_create_load_no_output() { - PcodeOpSimple { - pcode_index: 1, - pcode_mnemonic: ExpressionType(LOAD), - input0: mock_varnode("space", "id", 8), - input1: Some(mock_varnode("const", "0x200", 8)), - input2: None, - output: None, - } - .create_load(&"0x123".to_string()); -} - -#[test] -#[should_panic] -fn test_create_load_no_source() { - PcodeOpSimple { - pcode_index: 1, - pcode_mnemonic: ExpressionType(LOAD), - input0: mock_varnode("space", "id", 8), - input1: None, - input2: None, - output: Some(mock_varnode("register", "RAX", 8)), - } - .create_load(&"0x123".to_string()); -} - -#[test] -#[should_panic] -fn test_create_load_target_not_var() { - PcodeOpSimple { - pcode_index: 1, - pcode_mnemonic: ExpressionType(LOAD), - input0: mock_varnode("space", "id", 8), - input1: Some(mock_varnode("const", "0x200", 8)), - input2: None, - output: Some(mock_varnode("const", "0x4321", 8)), - } - .create_load(&"0x123".to_string()); -} - -#[test] -fn test_create_store() { - let data = mock_varnode("const", "0x0042", 8); - let target = mock_varnode("register", "RAX", 8); - let pcode_op = PcodeOpSimple { - pcode_index: 5, - pcode_mnemonic: ExpressionType(STORE), - input0: mock_varnode("space", "id", 8), - input1: Some(target), - input2: Some(data), - output: None, - }; - assert_eq!( - pcode_op.create_store(&"0x00ABCDEF".to_string()), - Term { - tid: Tid { - id: "instr_0x00ABCDEF_5".into(), - address: "0x00ABCDEF".into() - }, - term: def!["Store at RAX:8 := 0x0042:8"].term - } - ) -} - -#[test] -#[should_panic] -fn test_create_store_not_store() { - mock_pcode_op_add(mock_varnode("space", "id", 8), None, None) - .create_store(&"0x123".to_string()); -} - -#[test] -#[should_panic] -fn test_create_store_no_target() { - PcodeOpSimple { - pcode_index: 1, - pcode_mnemonic: ExpressionType(STORE), - input0: mock_varnode("space", "id", 8), - input1: None, - input2: Some(mock_varnode("const", "0x4321", 8)), - output: None, - } - .create_store(&"0x123".to_string()); -} - -#[test] -#[should_panic] -fn test_create_store_from_ram() { - PcodeOpSimple { - pcode_index: 1, - pcode_mnemonic: ExpressionType(STORE), - input0: mock_varnode("space", "id", 8), - input1: Some(mock_varnode("const", "0x4321", 8)), - input2: Some(mock_varnode("ram", "0xFFFF01", 8)), - output: None, - } - .create_store(&"0x123".to_string()); -}