Unverified Commit 809e26f4 by Melvin Klimke Committed by GitHub

Subregister access encoding (#114)

Now subregisters are accessed using SUBPIECE instructions. This should fix the issue that we could not assign subregister accesses to the corresponding base register in some cases.
parent f0f82225
use super::ByteSize;
use std::collections::HashMap;
use super::Variable;
use crate::prelude::*;
use super::{ByteSize, Def};
use crate::{pcode::RegisterProperties, prelude::*};
/// An expression is a calculation rule
/// on how to compute a certain value given some variables (register values) as input.
......@@ -132,6 +134,210 @@ impl Expression {
}
}
}
/// This function checks for sub registers in pcode instruction and casts them into
/// SUBPIECE expressions with the base register as argument. It also checks whether
/// the given Term<Def> has a output sub register and if so, casts it into its
/// corresponding base register.
/// Lastly, it checks whether the following pcode instruction is a zero extension of
/// the currently overwritten sub register. If so, the zero extension is wrapped around
/// the current instruction and the TID of the zero extension instruction is returned
/// for later removal.
/// If there is no zero extension but an output register, the multiple SUBPIECEs are put
/// together to the size of the corresponding output base register using the PIECE instruction.
/// A few examples:
/// 1. From: EAX = COPY EDX;
/// To: RAX = COPY PIECE(SUBPIECE(RAX, 4, 4), SUBPIECE(RDX, 0, 4));
///
/// 2. From: AH = AH INT_XOR AH;
/// To: RAX = PIECE(PIECE(SUBPIECE(RAX, 2, 6), (SUBPIECE(RAX, 1, 1) INT_XOR SUBPIECE(RAX, 1, 1)), SUBPIECE(RAX, 0, 1));
///
/// 3. FROM EAX = COPY EDX && RAX = INT_ZEXT EAX;
/// To: RAX = INT_ZEXT SUBPIECE(RDX, 0, 4);
pub fn cast_sub_registers_to_base_register_subpieces(
&mut self,
output: Option<&mut Variable>,
register_map: &HashMap<&String, &RegisterProperties>,
peeked: Option<&&mut Term<Def>>,
) -> Option<Tid> {
let mut output_base_size: Option<ByteSize> = None;
let mut output_base_register: Option<&&RegisterProperties> = None;
let mut output_sub_register: Option<&RegisterProperties> = None;
let mut zero_extend_tid: Option<Tid> = None;
if let Some(output_value) = output {
if let Some(register) = register_map.get(&output_value.name) {
if *register.register != *register.base_register {
output_sub_register = Some(register);
output_base_register = register_map.get(&register.base_register);
output_value.name = register.base_register.clone();
output_value.size = output_base_register.unwrap().size;
output_base_size = Some(output_value.size);
if let Some(peek) = peeked {
zero_extend_tid = peek.check_for_zero_extension(
output_value.name.clone(),
output_sub_register.unwrap().register.clone(),
);
}
}
}
}
self.replace_input_sub_register(register_map);
// based on the zero extension and base register output, either piece the subpieces together,
// zero extend the expression or do nothing (e.g. if output is a virtual register, no further actions should be taken)
self.piece_zero_extend_or_none(
zero_extend_tid.clone(),
output_base_register,
output_base_size,
output_sub_register,
);
zero_extend_tid
}
/// This function recursively iterates into the expression and checks whether a sub register was used.
/// If so, the sub register is turned into a SUBPIECE of the corresponding base register.
fn replace_input_sub_register(&mut self, register_map: &HashMap<&String, &RegisterProperties>) {
match self {
Expression::BinOp { lhs, rhs, .. } => {
lhs.replace_input_sub_register(register_map);
rhs.replace_input_sub_register(register_map);
}
Expression::UnOp { arg, .. } | Expression::Cast { arg, .. } => {
arg.replace_input_sub_register(register_map)
}
Expression::Subpiece { arg, .. } => {
let truncated: &mut Expression = arg;
// Check whether the truncated data source is a sub register and if so,
// change it to its corresponding base register.
match truncated {
Expression::Var(variable) => {
if let Some(register) = register_map.get(&variable.name) {
if variable.name != *register.base_register {
variable.name = register.base_register.clone();
variable.size =
register_map.get(&register.base_register).unwrap().size
}
}
}
_ => arg.replace_input_sub_register(register_map),
}
}
Expression::Var(variable) => {
if let Some(register) = register_map.get(&variable.name) {
if variable.name != *register.base_register {
self.create_subpiece_from_sub_register(
register.base_register.clone(),
register.size,
register.lsb,
register_map,
);
}
}
}
_ => (),
}
}
/// This function creates a SUBPIECE expression
/// from a sub_register containing the corresponding base register.
fn create_subpiece_from_sub_register(
&mut self,
base: String,
size: ByteSize,
lsb: ByteSize,
register_map: &HashMap<&String, &RegisterProperties>,
) {
*self = Expression::Subpiece {
low_byte: lsb,
size,
arg: Box::new(Expression::Var(Variable {
name: base.clone(),
size: register_map.get(&base).unwrap().size,
is_temp: false,
})),
};
}
/// This function either wraps the current expression into a
/// 1. zero extension expression: if the next instruction is a zero extension
/// of the currently overwritten sub register
/// 2. piece expression: if no zero extension is done the a sub register is overwritten
/// or does nothing in case there is no overwritten sub register.
fn piece_zero_extend_or_none(
&mut self,
zero_extend: Option<Tid>,
output_base_register: Option<&&RegisterProperties>,
output_size: Option<ByteSize>,
sub_register: Option<&RegisterProperties>,
) {
if zero_extend.is_some() {
*self = Expression::Cast {
op: CastOpType::IntZExt,
size: output_size.unwrap(),
arg: Box::new(self.clone()),
}
} else if output_base_register.is_some() {
self.piece_two_expressions_together(
*output_base_register.unwrap(),
sub_register.unwrap(),
);
}
}
/// This function puts multiple SUBPIECE into PIECE of the size of the
/// base register. Depending on the position of the LSB of the sub register,
/// also nested PIECE instruction are possible.
fn piece_two_expressions_together(
&mut self,
output_base_register: &RegisterProperties,
sub_register: &RegisterProperties,
) {
let base_size: ByteSize = output_base_register.size;
let base_name: &String = &output_base_register.register;
let sub_size: ByteSize = sub_register.size;
let sub_lsb: ByteSize = sub_register.lsb;
let base_subpiece = Box::new(Expression::Var(Variable {
name: base_name.clone(),
size: base_size,
is_temp: false,
}));
// Build PIECE as PIECE(lhs:PIECE(lhs:higher subpiece, rhs:sub register), rhs:lower subpiece)
if sub_register.lsb > ByteSize::new(0) {
*self = Expression::BinOp {
op: BinOpType::Piece,
lhs: Box::new(Expression::BinOp {
op: BinOpType::Piece,
lhs: Box::new(Expression::Subpiece {
low_byte: sub_lsb + sub_size,
size: base_size - (sub_lsb + sub_size),
arg: base_subpiece.clone(),
}),
rhs: Box::new(self.clone()),
}),
rhs: Box::new(Expression::Subpiece {
low_byte: ByteSize::new(0),
size: sub_lsb,
arg: base_subpiece,
}),
}
}
// Build PIECE as PIECE(lhs: high subpiece, rhs: sub register)
else {
*self = Expression::BinOp {
op: BinOpType::Piece,
lhs: Box::new(Expression::Subpiece {
low_byte: sub_size,
size: base_size - sub_size,
arg: base_subpiece,
}),
rhs: Box::new(self.clone()),
}
}
}
}
/// The type/mnemonic of a binary operation
......@@ -199,28 +405,4 @@ pub enum UnOpType {
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn trivial_expression_substitution() {
let mut expr = Expression::BinOp {
op: BinOpType::IntXOr,
lhs: Box::new(Expression::Var(Variable {
name: "RAX".into(),
size: ByteSize::new(8),
is_temp: false,
})),
rhs: Box::new(Expression::Var(Variable {
name: "RAX".into(),
size: ByteSize::new(8),
is_temp: false,
})),
};
expr.substitute_trivial_operations();
assert_eq!(
expr,
Expression::Const(Bitvector::zero(ByteSize::new(8).into()))
);
}
}
mod tests;
use super::*;
struct Setup<'a> {
register_map: HashMap<&'a String, &'a RegisterProperties>,
eax_name: String,
rax_name: String,
ecx_name: String,
rcx_name: String,
eax_register: RegisterProperties,
rax_register: RegisterProperties,
ecx_register: RegisterProperties,
rcx_register: RegisterProperties,
higher_byte_register: RegisterProperties,
int_sub_expr: Expression,
int_sub_subpiece_expr: Expression,
eax_variable: Expression,
rax_variable: Expression,
}
impl<'a> Setup<'a> {
fn new() -> Self {
Self {
register_map: HashMap::new(),
eax_name: String::from("EAX"),
rax_name: String::from("RAX"),
ecx_name: String::from("ECX"),
rcx_name: String::from("RCX"),
eax_register: RegisterProperties {
register: String::from("EAX"),
base_register: String::from("RAX"),
lsb: ByteSize::new(0),
size: ByteSize::new(4),
},
rax_register: RegisterProperties {
register: String::from("RAX"),
base_register: String::from("RAX"),
lsb: ByteSize::new(0),
size: ByteSize::new(8),
},
ecx_register: RegisterProperties {
register: String::from("ECX"),
base_register: String::from("RCX"),
lsb: ByteSize::new(0),
size: ByteSize::new(4),
},
rcx_register: RegisterProperties {
register: String::from("RCX"),
base_register: String::from("RCX"),
lsb: ByteSize::new(0),
size: ByteSize::new(8),
},
higher_byte_register: RegisterProperties {
register: String::from("AH"),
base_register: String::from("RAX"),
lsb: ByteSize::new(1),
size: ByteSize::new(1),
},
int_sub_expr: Expression::BinOp {
op: BinOpType::IntSub,
lhs: Box::new(Expression::Var(Variable {
name: String::from("EAX"),
size: ByteSize::new(4),
is_temp: false,
})),
rhs: Box::new(Expression::Var(Variable {
name: String::from("ECX"),
size: ByteSize::new(4),
is_temp: false,
})),
},
int_sub_subpiece_expr: Expression::BinOp {
op: BinOpType::IntSub,
lhs: Box::new(Expression::Subpiece {
low_byte: ByteSize::new(0),
size: ByteSize::new(4),
arg: Box::new(Expression::Var(Variable {
name: String::from("RAX"),
size: ByteSize::new(8),
is_temp: false,
})),
}),
rhs: Box::new(Expression::Subpiece {
low_byte: ByteSize::new(0),
size: ByteSize::new(4),
arg: Box::new(Expression::Var(Variable {
name: String::from("RCX"),
size: ByteSize::new(8),
is_temp: false,
})),
}),
},
eax_variable: Expression::Var(Variable {
name: String::from("EAX"),
size: ByteSize::new(4),
is_temp: false,
}),
rax_variable: Expression::Var(Variable {
name: String::from("RAX"),
size: ByteSize::new(8),
is_temp: false,
}),
}
}
}
#[test]
fn trivial_expression_substitution() {
let setup = Setup::new();
let mut expr = Expression::BinOp {
op: BinOpType::IntXOr,
lhs: Box::new(setup.rax_variable.clone()),
rhs: Box::new(setup.rax_variable.clone()),
};
expr.substitute_trivial_operations();
assert_eq!(
expr,
Expression::Const(Bitvector::zero(ByteSize::new(8).into()))
);
}
#[test]
fn subpiece_creation() {
let setup = Setup::new();
let lsb = ByteSize::new(0);
let size = ByteSize::new(4);
let mut register_map = setup.register_map.clone();
register_map.insert(&setup.eax_name, &setup.eax_register);
register_map.insert(&setup.rax_name, &setup.rax_register);
let mut expr = setup.eax_variable.clone();
let expected_expr = Expression::Subpiece {
low_byte: ByteSize::new(0),
size: ByteSize::new(4),
arg: Box::new(setup.rax_variable.clone()),
};
expr.create_subpiece_from_sub_register(setup.rax_name.clone(), size, lsb, &register_map);
assert_eq!(expr, expected_expr);
}
#[test]
fn piecing_expressions_together() {
let setup = Setup::new();
// Simple test:
// Input: EAX = INT_SUB SUBPIECE(RAX, 0, 4), SUBPIECE(RCX, 0, 4)
// Expected Output: RAX = PIECE(SUBPIECE(RAX, 4, 4), INT_SUB SUBPIECE(RAX, 0, 4), SUBPIECE(RCX, 0, 4))
let mut expr = setup.int_sub_subpiece_expr.clone();
let expected_expr = Expression::BinOp {
op: BinOpType::Piece,
lhs: Box::new(Expression::Subpiece {
low_byte: ByteSize::new(4),
size: ByteSize::new(4),
arg: Box::new(setup.rax_variable.clone()),
}),
rhs: Box::new(setup.int_sub_subpiece_expr.clone()),
};
// More complex test:
// Input: EAX = INT_SUB SUBPIECE(RAX, 1, 1), 0:1;
// Expected Output: RAX = PIECE[ PIECE(SUBPIECE(RAX, 2, 6), INT_SUB SUBPIECE(RAX, 1, 1)), SUBPIECE(RAX, 0, 1) ]
let mut higher_byte_exp = Expression::BinOp {
op: BinOpType::IntSub,
lhs: Box::new(Expression::Subpiece {
low_byte: ByteSize::new(1),
size: ByteSize::new(1),
arg: Box::new(setup.rax_variable.clone()),
}),
rhs: Box::new(Expression::Const(Bitvector::zero(ByteSize::new(1).into()))),
};
let expected_higher_byte_expr = Expression::BinOp {
op: BinOpType::Piece,
lhs: Box::new(Expression::BinOp {
op: BinOpType::Piece,
lhs: Box::new(Expression::Subpiece {
low_byte: ByteSize::new(2),
size: ByteSize::new(6),
arg: Box::new(setup.rax_variable.clone()),
}),
rhs: Box::new(Expression::BinOp {
op: BinOpType::IntSub,
lhs: Box::new(Expression::Subpiece {
low_byte: ByteSize::new(1),
size: ByteSize::new(1),
arg: Box::new(setup.rax_variable.clone()),
}),
rhs: Box::new(Expression::Const(Bitvector::zero(ByteSize::new(1).into()))),
}),
}),
rhs: Box::new(Expression::Subpiece {
low_byte: ByteSize::new(0),
size: ByteSize::new(1),
arg: Box::new(setup.rax_variable.clone()),
}),
};
expr.piece_two_expressions_together(&setup.rax_register, &setup.eax_register);
higher_byte_exp
.piece_two_expressions_together(&setup.rax_register, &setup.higher_byte_register);
assert_eq!(expr, expected_expr);
assert_eq!(higher_byte_exp, expected_higher_byte_expr);
}
#[test]
fn piecing_extending_or_none() {
let setup = Setup::new();
let zero_extend: Option<Tid> = Some(Tid::new("zero_tid"));
let output_size: Option<ByteSize> = Some(ByteSize::new(8));
let mut expr = setup.int_sub_expr.clone();
let expected_expr_with_zero_extend = Expression::Cast {
op: CastOpType::IntZExt,
size: ByteSize::new(8),
arg: Box::new(setup.int_sub_expr.clone()),
};
// Test assumes that the next instruction is a zero extension of the current output
expr.piece_zero_extend_or_none(
zero_extend,
Some(&&setup.rax_register),
output_size,
Some(&setup.eax_register),
);
assert_eq!(expr, expected_expr_with_zero_extend);
expr = setup.int_sub_expr.clone();
// Test assumes there is no output (i.e. virtual register output)
expr.piece_zero_extend_or_none(None, None, None, None);
assert_eq!(expr, setup.int_sub_expr);
expr = setup.int_sub_subpiece_expr.clone();
let expected_expr_with_piecing = Expression::BinOp {
op: BinOpType::Piece,
lhs: Box::new(Expression::Subpiece {
low_byte: ByteSize::new(4),
size: ByteSize::new(4),
arg: Box::new(setup.rax_variable.clone()),
}),
rhs: Box::new(setup.int_sub_subpiece_expr.clone()),
};
// Test assume output is a base register and the input needs to be pieced together
expr.piece_zero_extend_or_none(
None,
Some(&&setup.rax_register),
output_size,
Some(&setup.eax_register),
);
assert_eq!(expr, expected_expr_with_piecing);
}
#[test]
fn sub_register_check() {
let setup = Setup::new();
let mut expr = setup.int_sub_expr.clone();
let mut register_map = setup.register_map.clone();
register_map.insert(&setup.eax_name, &setup.eax_register);
register_map.insert(&setup.rax_name, &setup.rax_register);
register_map.insert(&setup.ecx_name, &setup.ecx_register);
register_map.insert(&setup.rcx_name, &setup.rcx_register);
expr.replace_input_sub_register(&register_map);
assert_eq!(expr, setup.int_sub_subpiece_expr);
}
#[test]
fn processing_sub_registers() {
let setup = Setup::new();
let mut register_map = setup.register_map.clone();
register_map.insert(&setup.eax_name, &setup.eax_register);
register_map.insert(&setup.rax_name, &setup.rax_register);
register_map.insert(&setup.ecx_name, &setup.ecx_register);
register_map.insert(&setup.rcx_name, &setup.rcx_register);
// Test Case: Subregister output
let out_sub = Variable {
name: setup.eax_name.clone(),
size: ByteSize::new(4),
is_temp: false,
};
// Test Case: Baseregister output
let mut out_base = Variable {
name: setup.rax_name.clone(),
size: ByteSize::new(8),
is_temp: false,
};
// Test Case: Virtual register output
let mut out_virtual = Variable {
name: String::from("$u560"),
size: ByteSize::new(8),
is_temp: true,
};
// Test Case: Following instruction is a zero extend
let mut def_term_ext = Term {
tid: Tid::new("int_zext"),
term: Def::Assign {
var: out_base.clone(),
value: Expression::Cast {
op: CastOpType::IntZExt,
size: ByteSize::new(8),
arg: Box::new(setup.eax_variable.clone()),
},
},
};
// Test Case: Following instruction is not a zero extend
let mut def_term = Term {
tid: Tid::new("int_sext"),
term: Def::Assign {
var: out_base.clone(),
value: Expression::Cast {
op: CastOpType::IntSExt,
size: ByteSize::new(8),
arg: Box::new(setup.eax_variable.clone()),
},
},
};
// 1. Test: peeked is a zero extension and output is a sub register
// Expects: Sub register casted to base and zero extension detected
let def_term_ext_pointer = &mut def_term_ext;
let mut peeked = Some(&def_term_ext_pointer);
let mut sub_reg_output = out_sub.clone();
let mut output = Some(&mut sub_reg_output);
let mut expr = setup.int_sub_expr.clone();
let mut expected_expr = Expression::Cast {
op: CastOpType::IntZExt,
size: ByteSize::new(8),
arg: Box::new(setup.int_sub_subpiece_expr.clone()),
};
expr.cast_sub_registers_to_base_register_subpieces(output, &register_map, peeked);
assert_eq!(expr, expected_expr);
// 2. Test: peeked is not a zero extend and output is a sub register
// Expects: Piece input together to get the base register size
let def_term_pointer = &mut def_term;
peeked = Some(&def_term_pointer);
expr = setup.int_sub_expr.clone();
expected_expr = Expression::BinOp {
op: BinOpType::Piece,
lhs: Box::new(Expression::Subpiece {
low_byte: ByteSize::new(4),
size: ByteSize::new(4),
arg: Box::new(setup.rax_variable.clone()),
}),
rhs: Box::new(setup.int_sub_subpiece_expr.clone()),
};
let mut sub_reg_output = out_sub.clone();
output = Some(&mut sub_reg_output);
expr.cast_sub_registers_to_base_register_subpieces(output, &register_map, peeked);
assert_eq!(expr, expected_expr);
// 3. Test: peek is neglectable and output is a base register
let def_term_pointer = &mut def_term;
peeked = Some(&def_term_pointer);
expr = setup.int_sub_expr.clone();
output = Some(&mut out_base);
expr.cast_sub_registers_to_base_register_subpieces(output, &register_map, peeked);
assert_eq!(expr, setup.int_sub_subpiece_expr);
// 4. Test: peek is neglectable and output is a virtual register
let def_term_pointer = &mut def_term;
peeked = Some(&def_term_pointer);
expr = setup.int_sub_expr.clone();
output = Some(&mut out_virtual);
expr.cast_sub_registers_to_base_register_subpieces(output, &register_map, peeked);
assert_eq!(expr, setup.int_sub_subpiece_expr);
}
use super::{ByteSize, Expression, Variable};
use super::{ByteSize, CastOpType, Expression, Variable};
use crate::prelude::*;
use crate::utils::log::LogMessage;
use std::collections::HashSet;
......@@ -68,6 +68,36 @@ pub enum Def {
Assign { var: Variable, value: Expression },
}
impl Term<Def> {
/// This function checks whether the instruction
/// is a zero extension of the overwritten sub register of the previous instruction.
/// If so, returns its TID
pub fn check_for_zero_extension(
&self,
output_name: String,
output_sub_register: String,
) -> Option<Tid> {
match &self.term {
Def::Assign { var, value } if output_name == var.name => match value {
Expression::Cast { op, arg, .. } => {
let argument: &Expression = arg;
match op {
CastOpType::IntZExt => match argument {
Expression::Var(var) if var.name == output_sub_register => {
Some(self.tid.clone())
}
_ => None,
},
_ => None,
}
}
_ => None,
},
_ => None,
}
}
}
/// A `Jmp` instruction affects the control flow of a program, i.e. it may change the instruction pointer.
/// With the exception of `CallOther`, it has no other side effects.
///
......@@ -442,6 +472,8 @@ impl Project {
#[cfg(test)]
mod tests {
use crate::intermediate_representation::BinOpType;
use super::*;
impl Blk {
......@@ -539,4 +571,88 @@ mod tests {
.is_err());
assert_eq!(jmp_term.term, Jmp::Branch(Tid::new("dummy_blk")));
}
#[test]
fn zero_extension_check() {
let eax_variable = Expression::Var(Variable {
name: String::from("EAX"),
size: ByteSize::new(4),
is_temp: false,
});
let int_sub_expr = Expression::BinOp {
op: BinOpType::IntSub,
lhs: Box::new(Expression::Var(Variable {
name: String::from("EAX"),
size: ByteSize::new(4),
is_temp: false,
})),
rhs: Box::new(Expression::Var(Variable {
name: String::from("ECX"),
size: ByteSize::new(4),
is_temp: false,
})),
};
let zero_extend_def = Term {
tid: Tid::new("zero_tid"),
term: Def::Assign {
var: Variable {
name: String::from("RAX"),
size: ByteSize::new(8),
is_temp: false,
},
value: Expression::Cast {
op: CastOpType::IntZExt,
size: ByteSize::new(8),
arg: Box::new(eax_variable.clone()),
},
},
};
// An expression that is a zero extension but does not directly contain a variable
let zero_extend_but_no_var_def = Term {
tid: Tid::new("zero_tid"),
term: Def::Assign {
var: Variable {
name: String::from("RAX"),
size: ByteSize::new(8),
is_temp: false,
},
value: Expression::Cast {
op: CastOpType::IntZExt,
size: ByteSize::new(8),
arg: Box::new(int_sub_expr.clone()),
},
},
};
let non_zero_extend_def = Term {
tid: Tid::new("zero_tid"),
term: Def::Assign {
var: Variable {
name: String::from("RAX"),
size: ByteSize::new(8),
is_temp: false,
},
value: Expression::Cast {
op: CastOpType::IntSExt,
size: ByteSize::new(8),
arg: Box::new(eax_variable.clone()),
},
},
};
assert_eq!(
zero_extend_def.check_for_zero_extension(String::from("RAX"), String::from("EAX")),
Some(Tid::new("zero_tid"))
);
assert_eq!(
zero_extend_but_no_var_def
.check_for_zero_extension(String::from("RAX"), String::from("EAX")),
None
);
assert_eq!(
non_zero_extend_def.check_for_zero_extension(String::from("RAX"), String::from("EAX")),
None
);
}
}
use std::collections::{HashMap, HashSet};
use super::{Expression, ExpressionType, RegisterProperties, Variable};
use crate::intermediate_representation::Arg as IrArg;
use crate::intermediate_representation::Blk as IrBlk;
......@@ -410,10 +412,103 @@ pub struct Project {
impl From<Project> for IrProject {
/// Convert a project parsed from Ghidra to the internally used IR.
fn from(project: Project) -> IrProject {
let program = Term {
let mut program: Term<IrProgram> = Term {
tid: project.program.tid,
term: project.program.term.into(),
};
let register_map: HashMap<&String, &RegisterProperties> = project
.register_properties
.iter()
.map(|p| (&p.register, p))
.collect();
let mut zero_extend_tids: HashSet<Tid> = HashSet::new();
// iterates over definitions and checks whether sub registers are used
// if so, they are swapped with subpieces of base registers
for sub in program.term.subs.iter_mut() {
for blk in sub.term.blocks.iter_mut() {
let mut def_iter = blk.term.defs.iter_mut().peekable();
while let Some(def) = def_iter.next() {
let peeked_def = def_iter.peek();
match &mut def.term {
IrDef::Assign { var, value } => {
if let Some(zero_tid) = value
.cast_sub_registers_to_base_register_subpieces(
Some(var),
&register_map,
peeked_def,
)
{
zero_extend_tids.insert(zero_tid);
}
}
IrDef::Load { var, address } => {
if let Some(zero_tid) = address
.cast_sub_registers_to_base_register_subpieces(
Some(var),
&register_map,
peeked_def,
)
{
zero_extend_tids.insert(zero_tid);
}
}
IrDef::Store { address, value } => {
address.cast_sub_registers_to_base_register_subpieces(
None,
&register_map,
peeked_def,
);
value.cast_sub_registers_to_base_register_subpieces(
None,
&register_map,
peeked_def,
);
}
}
}
for jmp in blk.term.jmps.iter_mut() {
match &mut jmp.term {
IrJmp::BranchInd(dest) => {
dest.cast_sub_registers_to_base_register_subpieces(
None,
&register_map,
None,
);
}
IrJmp::CBranch { condition, .. } => {
condition.cast_sub_registers_to_base_register_subpieces(
None,
&register_map,
None,
);
}
IrJmp::CallInd { target, .. } => {
target.cast_sub_registers_to_base_register_subpieces(
None,
&register_map,
None,
);
}
IrJmp::Return(dest) => {
dest.cast_sub_registers_to_base_register_subpieces(
None,
&register_map,
None,
);
}
_ => (),
}
}
// Remove all tagged zero extension instruction that came after a sub register instruction
// since it has been wrapped around the former instruction.
blk.term.defs.retain(|def| {
if zero_extend_tids.contains(&def.tid) {
return false;
}
true
});
}
}
IrProject {
program,
cpu_architecture: project.cpu_architecture,
......@@ -454,374 +549,4 @@ impl Project {
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn def_deserialization() {
let def: Def = serde_json::from_str(
r#"
{
"lhs": {
"name": "CF",
"size": 1,
"is_virtual": false
},
"rhs": {
"mnemonic": "INT_CARRY",
"input0": {
"name": "RDX",
"size": 8,
"is_virtual": false
},
"input1": {
"name": "RDI",
"size": 8,
"is_virtual": false
}
}
}
"#,
)
.unwrap();
let _: IrDef = def.into();
let def: Def = serde_json::from_str(
r#"
{
"lhs": {
"address": "004053e8",
"size": 4,
"is_virtual": false
},
"rhs": {
"mnemonic": "INT_XOR",
"input0": {
"name": "$load_temp0",
"size": 4,
"is_virtual": true
},
"input1": {
"name": "$U4780",
"size": 4,
"is_virtual": true
}
}
}
"#,
)
.unwrap();
let _: IrDef = def.into();
}
#[test]
fn label_deserialization() {
let _: Label = serde_json::from_str(
r#"
{
"Direct": {
"id": "blk_00103901",
"address": "00103901"
}
}
"#,
)
.unwrap();
let _: Label = serde_json::from_str(
r#"
{
"Indirect": {
"name": "00109ef0",
"size": 8,
"is_virtual": false
}
}
"#,
)
.unwrap();
}
#[test]
fn jmp_deserialization() {
let jmp_term: Term<Jmp> = serde_json::from_str(
r#"
{
"tid": {
"id": "instr_00102014_2",
"address": "00102014"
},
"term": {
"type_": "CALL",
"mnemonic": "CALLIND",
"call": {
"target": {
"Indirect": {
"name": "RAX",
"size": 8,
"is_virtual": false
}
},
"return": {
"Direct": {
"id": "blk_00102016",
"address": "00102016"
}
}
}
}
}
"#,
)
.unwrap();
let _: IrJmp = jmp_term.term.into();
}
#[test]
fn blk_deserialization() {
let block_term: Term<Blk> = serde_json::from_str(
r#"
{
"tid": {
"id": "blk_00101000",
"address": "00101000"
},
"term": {
"defs": [],
"jmps": []
}
}
"#,
)
.unwrap();
let _: IrBlk = block_term.term.into();
}
#[test]
fn arg_deserialization() {
let _: Arg = serde_json::from_str(
r#"
{
"var": {
"name": "RDI",
"size": 8,
"is_virtual": false
},
"intent": "INPUT"
}
"#,
)
.unwrap();
let _: Arg = serde_json::from_str(
r#"
{
"location": {
"mnemonic": "LOAD",
"input0": {
"address": "0x4",
"size": 4,
"is_virtual": false
}
},
"intent": "INPUT"
}
"#,
)
.unwrap();
}
#[test]
fn sub_deserialization() {
let sub_term: Term<Sub> = serde_json::from_str(
r#"
{
"tid": {
"id": "sub_00101000",
"address": "00101000"
},
"term": {
"name": "sub_name",
"blocks": []
}
}
"#,
)
.unwrap();
let _: IrSub = sub_term.term.into();
}
#[test]
fn extern_symbol_deserialization() {
let symbol: ExternSymbol = serde_json::from_str(
r#"
{
"tid": {
"id": "sub_08048410",
"address": "08048410"
},
"addresses": [
"08048410"
],
"name": "atoi",
"calling_convention": "__cdecl",
"arguments": [
{
"location": {
"mnemonic": "LOAD",
"input0": {
"address": "0x4",
"size": 4,
"is_virtual": false
}
},
"intent": "INPUT"
},
{
"var": {
"name": "EAX",
"size": 4,
"is_virtual": false
},
"intent": "OUTPUT"
}
],
"no_return": false
}
"#,
)
.unwrap();
let _: IrExternSymbol = symbol.into();
}
#[test]
fn program_deserialization() {
let program_term: Term<Program> = serde_json::from_str(
r#"
{
"tid": {
"id": "prog_00101000",
"address": "00101000"
},
"term": {
"subs": [],
"extern_symbols": [],
"entry_points":[]
}
}
"#,
)
.unwrap();
let _: IrProgram = program_term.term.into();
}
#[test]
fn project_deserialization() {
let project: Project = serde_json::from_str(
r#"
{
"program": {
"tid": {
"id": "prog_08048000",
"address": "08048000"
},
"term": {
"subs": [],
"extern_symbols": [],
"entry_points":[]
}
},
"stack_pointer_register": {
"name": "RSP",
"size": 8,
"is_virtual": false
},
"cpu_architecture": "x86_64",
"register_properties": [
{
"register": "AH",
"base_register": "EAX",
"lsb": 2,
"size": 1
}
],
"register_calling_convention": [
{
"calling_convention": "default",
"parameter_register": [],
"return_register": [],
"unaffected_register": [],
"killed_by_call_register": []
}
]
}
"#,
)
.unwrap();
let _: IrProject = project.into();
}
#[test]
fn add_load_defs_for_implicit_ram_access() {
let mut blk: Blk = Blk {
defs: Vec::new(),
jmps: Vec::new(),
};
blk.defs.push(
serde_json::from_str(
r#"
{
"tid": {
"id": "instr_001053f8_0",
"address": "001053f8"
},
"term": {
"lhs": {
"name": "EDI",
"value": null,
"address": null,
"size": 4,
"is_virtual": false
},
"rhs": {
"mnemonic": "COPY",
"input0": {
"name": null,
"value": null,
"address": "0010a018",
"size": 4,
"is_virtual": false
},
"input1": null,
"input2": null
}
}
}
"#,
)
.unwrap(),
);
blk.add_load_defs_for_implicit_ram_access();
assert_eq!(
blk.defs[0]
.term
.lhs
.as_ref()
.unwrap()
.name
.as_ref()
.unwrap(),
"$load_temp0"
);
assert_eq!(
blk.defs[1]
.term
.rhs
.input0
.as_ref()
.unwrap()
.name
.as_ref()
.unwrap(),
"$load_temp0"
);
assert_eq!(blk.defs.len(), 2);
}
}
mod tests;
use super::*;
use crate::intermediate_representation::{BinOpType, CastOpType, Variable as IrVariable};
struct Setup {
project: Project,
sub_t: Term<Sub>,
blk_t: Term<Blk>,
def_0_t: Term<Def>,
def_1_t: Term<Def>,
def_2_t: Term<Def>,
def_3_t: Term<Def>,
def_4_t: Term<Def>,
def_5_t: Term<Def>,
jmp_t: Term<Jmp>,
}
impl Setup {
fn new() -> Self {
Self {
project: serde_json::from_str(
r#"
{
"program": {
"tid": {
"id": "prog_08048000",
"address": "08048000"
},
"term": {
"subs": [],
"extern_symbols": [],
"entry_points":[]
}
},
"stack_pointer_register": {
"name": "RSP",
"size": 8,
"is_virtual": false
},
"cpu_architecture": "x86_64",
"register_properties": [
{
"register": "AH",
"base_register": "RAX",
"lsb": 1,
"size": 1
},
{
"register": "AL",
"base_register": "RAX",
"lsb": 0,
"size": 1
},
{
"register": "AX",
"base_register": "RAX",
"lsb": 0,
"size": 2
},
{
"register": "EAX",
"base_register": "RAX",
"lsb": 0,
"size": 4
},
{
"register": "RAX",
"base_register": "RAX",
"lsb": 0,
"size": 8
},
{
"register": "EDI",
"base_register": "RDI",
"lsb": 0,
"size": 4
},
{
"register": "RDI",
"base_register": "RDI",
"lsb": 0,
"size": 8
}
],
"register_calling_convention": [
{
"calling_convention": "default",
"parameter_register": [],
"return_register": [],
"unaffected_register": [],
"killed_by_call_register": []
}
]
}
"#,
)
.unwrap(),
sub_t: serde_json::from_str(
r#"
{
"tid": {
"id": "sub_00101000",
"address": "00101000"
},
"term": {
"name": "sub_name",
"blocks": []
}
}
"#,
)
.unwrap(),
blk_t: serde_json::from_str(
r#"
{
"tid": {
"id": "blk_00101000",
"address": "00101000"
},
"term": {
"defs": [],
"jmps": []
}
}
"#,
)
.unwrap(),
def_0_t: serde_json::from_str(
r#"
{
"tid": {
"id": "instr_001053f8_0",
"address": "001053f8"
},
"term": {
"lhs": {
"name": "EDI",
"value": null,
"address": null,
"size": 4,
"is_virtual": false
},
"rhs": {
"mnemonic": "LOAD",
"input0": null,
"input1": {
"name": "EDI",
"value": null,
"address": null,
"size": 4,
"is_virtual": false
},
"input2": null
}
}
}
"#,
)
.unwrap(),
def_1_t: serde_json::from_str(
r#"
{
"tid": {
"id": "instr_001053f8_1",
"address": "001053f8"
},
"term": {
"lhs": {
"name": "AH",
"value": null,
"address": null,
"size": 1,
"is_virtual": false
},
"rhs": {
"mnemonic": "INT_XOR",
"input0": {
"name": "AH",
"value": null,
"address": null,
"size": 1,
"is_virtual": false
},
"input1": {
"name": "AH",
"value": null,
"address": null,
"size": 1,
"is_virtual": false
},
"input2": null
}
}
}
"#,
)
.unwrap(),
def_2_t: serde_json::from_str(
r#"
{
"tid": {
"id": "instr_001053f8_2",
"address": "001053f8"
},
"term": {
"lhs": {
"name": "EAX",
"value": null,
"address": null,
"size": 4,
"is_virtual": false
},
"rhs": {
"mnemonic": "COPY",
"input0": {
"name": "EDI",
"value": null,
"address": null,
"size": 4,
"is_virtual": false
},
"input1": null,
"input2": null
}
}
}
"#,
)
.unwrap(),
def_3_t: serde_json::from_str(
r#"
{
"tid": {
"id": "instr_001053f8_3",
"address": "001053f8"
},
"term": {
"lhs": {
"name": "RAX",
"value": null,
"address": null,
"size": 8,
"is_virtual": false
},
"rhs": {
"mnemonic": "INT_ZEXT",
"input0": {
"name": "EAX",
"value": null,
"address": null,
"size": 4,
"is_virtual": false
},
"input1": null,
"input2": null
}
}
}
"#,
)
.unwrap(),
def_4_t: serde_json::from_str(
r#"
{
"tid": {
"id": "instr_001053f8_4",
"address": "001053f8"
},
"term": {
"lhs": {
"name": "EAX",
"value": null,
"address": null,
"size": 4,
"is_virtual": false
},
"rhs": {
"mnemonic": "PIECE",
"input0": {
"name": null,
"value": "00000000",
"address": null,
"size": 2,
"is_virtual": false
},
"input1": {
"name": "AX",
"value": null,
"address": null,
"size": 2,
"is_virtual": false
},
"input2": null
}
}
}
"#,
)
.unwrap(),
def_5_t: serde_json::from_str(
r#"
{
"tid": {
"id": "instr_001053f8_5",
"address": "001053f8"
},
"term": {
"lhs": {
"name": "AX",
"value": null,
"address": null,
"size": 2,
"is_virtual": false
},
"rhs": {
"mnemonic": "SUBPIECE",
"input0": {
"name": "EDI",
"value": null,
"address": null,
"size": 4,
"is_virtual": false
},
"input1": {
"name": null,
"value": "00000001",
"address": null,
"size": 4,
"is_virtual": false
},
"input2": null
}
}
}
"#,
)
.unwrap(),
jmp_t: serde_json::from_str(
r#"
{
"tid": {
"id": "instr_00102014_2",
"address": "00102014"
},
"term": {
"type_": "CALL",
"mnemonic": "CALLIND",
"call": {
"target": {
"Indirect": {
"name": "EAX",
"size": 4,
"is_virtual": false
}
},
"return": {
"Direct": {
"id": "blk_00102016",
"address": "00102016"
}
}
}
}
}
"#,
)
.unwrap(),
}
}
}
#[test]
fn def_deserialization() {
let def: Def = serde_json::from_str(
r#"
{
"lhs": {
"name": "CF",
"size": 1,
"is_virtual": false
},
"rhs": {
"mnemonic": "INT_CARRY",
"input0": {
"name": "RDX",
"size": 8,
"is_virtual": false
},
"input1": {
"name": "RDI",
"size": 8,
"is_virtual": false
}
}
}
"#,
)
.unwrap();
let _: IrDef = def.into();
let def: Def = serde_json::from_str(
r#"
{
"lhs": {
"address": "004053e8",
"size": 4,
"is_virtual": false
},
"rhs": {
"mnemonic": "INT_XOR",
"input0": {
"name": "$load_temp0",
"size": 4,
"is_virtual": true
},
"input1": {
"name": "$U4780",
"size": 4,
"is_virtual": true
}
}
}
"#,
)
.unwrap();
let _: IrDef = def.into();
}
#[test]
fn label_deserialization() {
let _: Label = serde_json::from_str(
r#"
{
"Direct": {
"id": "blk_00103901",
"address": "00103901"
}
}
"#,
)
.unwrap();
let _: Label = serde_json::from_str(
r#"
{
"Indirect": {
"name": "00109ef0",
"size": 8,
"is_virtual": false
}
}
"#,
)
.unwrap();
}
#[test]
fn jmp_deserialization() {
let setup = Setup::new();
let jmp_term: Term<Jmp> = setup.jmp_t.clone();
let _: IrJmp = jmp_term.term.into();
}
#[test]
fn blk_deserialization() {
let setup = Setup::new();
let block_term: Term<Blk> = setup.blk_t.clone();
let _: IrBlk = block_term.term.into();
}
#[test]
fn arg_deserialization() {
let _: Arg = serde_json::from_str(
r#"
{
"var": {
"name": "RDI",
"size": 8,
"is_virtual": false
},
"intent": "INPUT"
}
"#,
)
.unwrap();
let _: Arg = serde_json::from_str(
r#"
{
"location": {
"mnemonic": "LOAD",
"input0": {
"address": "0x4",
"size": 4,
"is_virtual": false
}
},
"intent": "INPUT"
}
"#,
)
.unwrap();
}
#[test]
fn sub_deserialization() {
let setup = Setup::new();
let sub_term: Term<Sub> = setup.sub_t.clone();
let _: IrSub = sub_term.term.into();
}
#[test]
fn extern_symbol_deserialization() {
let symbol: ExternSymbol = serde_json::from_str(
r#"
{
"tid": {
"id": "sub_08048410",
"address": "08048410"
},
"addresses": [
"08048410"
],
"name": "atoi",
"calling_convention": "__cdecl",
"arguments": [
{
"location": {
"mnemonic": "LOAD",
"input0": {
"address": "0x4",
"size": 4,
"is_virtual": false
}
},
"intent": "INPUT"
},
{
"var": {
"name": "EAX",
"size": 4,
"is_virtual": false
},
"intent": "OUTPUT"
}
],
"no_return": false
}
"#,
)
.unwrap();
let _: IrExternSymbol = symbol.into();
}
#[test]
fn program_deserialization() {
let program_term: Term<Program> = serde_json::from_str(
r#"
{
"tid": {
"id": "prog_00101000",
"address": "00101000"
},
"term": {
"subs": [],
"extern_symbols": [],
"entry_points":[]
}
}
"#,
)
.unwrap();
let _: IrProgram = program_term.term.into();
}
#[test]
fn project_deserialization() {
let setup = Setup::new();
let project: Project = setup.project.clone();
let _: IrProject = project.into();
}
#[test]
fn add_load_defs_for_implicit_ram_access() {
let mut blk: Blk = Blk {
defs: Vec::new(),
jmps: Vec::new(),
};
blk.defs.push(
serde_json::from_str(
r#"
{
"tid": {
"id": "instr_001053f8_0",
"address": "001053f8"
},
"term": {
"lhs": {
"name": "RDI",
"value": null,
"address": null,
"size": 8,
"is_virtual": false
},
"rhs": {
"mnemonic": "COPY",
"input0": {
"name": null,
"value": null,
"address": "0010a018",
"size": 8,
"is_virtual": false
},
"input1": null,
"input2": null
}
}
}
"#,
)
.unwrap(),
);
blk.add_load_defs_for_implicit_ram_access();
assert_eq!(
blk.defs[0]
.term
.lhs
.as_ref()
.unwrap()
.name
.as_ref()
.unwrap(),
"$load_temp0"
);
assert_eq!(
blk.defs[1]
.term
.rhs
.input0
.as_ref()
.unwrap()
.name
.as_ref()
.unwrap(),
"$load_temp0"
);
assert_eq!(blk.defs.len(), 2);
}
#[test]
fn from_project_to_ir_project() {
let setup = Setup::new();
let mut mock_project: Project = setup.project.clone();
let mut blk = setup.blk_t;
blk.term.defs.push(setup.def_0_t);
blk.term.defs.push(setup.def_1_t);
blk.term.defs.push(setup.def_2_t);
blk.term.defs.push(setup.def_3_t);
blk.term.defs.push(setup.def_4_t);
blk.term.defs.push(setup.def_5_t);
blk.term.jmps.push(setup.jmp_t);
let mut sub = setup.sub_t;
sub.term.blocks.push(blk);
mock_project.program.term.subs.push(sub.clone());
let ir_program = IrProject::from(mock_project).program.term;
let ir_rdi_var = IrVariable {
name: String::from("RDI"),
size: ByteSize::new(8),
is_temp: false,
};
let ir_rax_var = IrVariable {
name: String::from("RAX"),
size: ByteSize::new(8),
is_temp: false,
};
// From: EDI = LOAD EDI
// To: RDI = PIECE(SUBPIECE(RDI, 4, 4), (LOAD SUBPIECE(RDI, 0, 4)))
let expected_def_0 = IrDef::Load {
var: ir_rdi_var.clone(),
address: IrExpression::BinOp {
op: BinOpType::Piece,
lhs: Box::new(IrExpression::Subpiece {
low_byte: ByteSize::new(4),
size: ByteSize::new(4),
arg: Box::new(IrExpression::Var(ir_rdi_var.clone())),
}),
rhs: Box::new(IrExpression::Subpiece {
low_byte: ByteSize::new(0),
size: ByteSize::new(4),
arg: Box::new(IrExpression::Var(ir_rdi_var.clone())),
}),
},
};
// From: AH = AH INT_XOR AH
// To: RAX = PIECE(PIECE(SUBPIECE(RAX, 2, 6), (SUBPIECE(RAX, 1, 1) INT_XOR SUBPIECE(RAX, 1, 1))), SUBPIECE(RAX, 0, 1))
let expected_def_1 = IrDef::Assign {
var: ir_rax_var.clone(),
value: IrExpression::BinOp {
op: BinOpType::Piece,
lhs: Box::new(IrExpression::BinOp {
op: BinOpType::Piece,
lhs: Box::new(IrExpression::Subpiece {
low_byte: ByteSize::new(2),
size: ByteSize::new(6),
arg: Box::new(IrExpression::Var(ir_rax_var.clone())),
}),
rhs: Box::new(IrExpression::BinOp {
op: BinOpType::IntXOr,
lhs: Box::new(IrExpression::Subpiece {
low_byte: ByteSize::new(1),
size: ByteSize::new(1),
arg: Box::new(IrExpression::Var(ir_rax_var.clone())),
}),
rhs: Box::new(IrExpression::Subpiece {
low_byte: ByteSize::new(1),
size: ByteSize::new(1),
arg: Box::new(IrExpression::Var(ir_rax_var.clone())),
}),
}),
}),
rhs: Box::new(IrExpression::Subpiece {
low_byte: ByteSize::new(0),
size: ByteSize::new(1),
arg: Box::new(IrExpression::Var(ir_rax_var.clone())),
}),
},
};
// From: EAX = COPY EDI && RAX = INT_ZEXT EAX
// To: RAX = INT_ZEXT SUBPIECE(RDI, 0, 4)
let expected_def_3 = IrDef::Assign {
var: ir_rax_var.clone(),
value: IrExpression::Cast {
op: CastOpType::IntZExt,
size: ByteSize::new(8),
arg: Box::new(IrExpression::Subpiece {
low_byte: ByteSize::new(0),
size: ByteSize::new(4),
arg: Box::new(IrExpression::Var(ir_rdi_var.clone())),
}),
},
};
// From: EAX = PIECE(0:2, AX)
// To: RAX = PIECE(SUBPIECE(RAX, 4, 4), PIECE(0:2, SUBPIECE(RAX, 0, 2)))
let expected_def_4 = IrDef::Assign {
var: ir_rax_var.clone(),
value: IrExpression::BinOp {
op: BinOpType::Piece,
lhs: Box::new(IrExpression::Subpiece {
low_byte: ByteSize::new(4),
size: ByteSize::new(4),
arg: Box::new(IrExpression::Var(ir_rax_var.clone())),
}),
rhs: Box::new(IrExpression::BinOp {
op: BinOpType::Piece,
lhs: Box::new(IrExpression::Const(Bitvector::zero(
ByteSize::new(2).into(),
))),
rhs: Box::new(IrExpression::Subpiece {
low_byte: ByteSize::new(0),
size: ByteSize::new(2),
arg: Box::new(IrExpression::Var(ir_rax_var.clone())),
}),
}),
},
};
// From: AX = SUBPIECE(EDI, 1, 2)
// To: RAX = PIECE(SUBPIECE(RAX, 2, 6), SUBPIECE(RDI, 1, 2))
let expected_def_5 = IrDef::Assign {
var: ir_rax_var.clone(),
value: IrExpression::BinOp {
op: BinOpType::Piece,
lhs: Box::new(IrExpression::Subpiece {
low_byte: ByteSize::new(2),
size: ByteSize::new(6),
arg: Box::new(IrExpression::Var(ir_rax_var.clone())),
}),
rhs: Box::new(IrExpression::Subpiece {
low_byte: ByteSize::new(1),
size: ByteSize::new(2),
arg: Box::new(IrExpression::Var(ir_rdi_var.clone())),
}),
},
};
let mut target_tid = Tid::new("blk_00102016");
target_tid.address = String::from("00102016");
// From: CALLIND EAX
// To: CALLIND SUBPIECE(RAX, 0, 4)
let expected_jmp = IrJmp::CallInd {
target: IrExpression::Subpiece {
low_byte: ByteSize::new(0),
size: ByteSize::new(4),
arg: Box::new(IrExpression::Var(ir_rax_var.clone())),
},
return_: Some(target_tid.clone()),
};
// Checks whether the zero extension was correctly removed; leaving only 5 definitions behind.
assert_eq!(ir_program.subs[0].term.blocks[0].term.defs.len(), 5);
// Checks if the other definitions and the jump were correctly casted.
assert_eq!(
ir_program.subs[0].term.blocks[0].term.defs[0].term,
expected_def_0
);
assert_eq!(
ir_program.subs[0].term.blocks[0].term.defs[1].term,
expected_def_1
);
assert_eq!(
ir_program.subs[0].term.blocks[0].term.defs[2].term,
expected_def_3
);
assert_eq!(
ir_program.subs[0].term.blocks[0].term.defs[3].term,
expected_def_4
);
assert_eq!(
ir_program.subs[0].term.blocks[0].term.defs[4].term,
expected_def_5
);
assert_eq!(
ir_program.subs[0].term.blocks[0].term.jmps[0].term,
expected_jmp
);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment