Unverified Commit 0662acda by van den Bosch Committed by GitHub

Test reorganization (#285)

parent 19eac24e
...@@ -4,7 +4,7 @@ use std::collections::HashSet; ...@@ -4,7 +4,7 @@ use std::collections::HashSet;
#[test] #[test]
fn test_compute_return_values_of_call() { fn test_compute_return_values_of_call() {
let project = Project::mock_empty(); let project = Project::mock_empty();
let cconv = CallingConvention::mock(); let cconv = CallingConvention::mock_x64();
let graph = crate::analysis::graph::get_program_cfg(&project.program, HashSet::new()); let graph = crate::analysis::graph::get_program_cfg(&project.program, HashSet::new());
let context = Context::new(&project, &graph); let context = Context::new(&project, &graph);
...@@ -25,7 +25,7 @@ fn test_compute_return_values_of_call() { ...@@ -25,7 +25,7 @@ fn test_compute_return_values_of_call() {
AbstractIdentifier::new_from_var(Tid::new("call_tid"), &Variable::mock("RAX", 8)), AbstractIdentifier::new_from_var(Tid::new("call_tid"), &Variable::mock("RAX", 8)),
Bitvector::from_i64(0).into(), Bitvector::from_i64(0).into(),
); );
assert_eq!(return_values.iter().len(), 1); assert_eq!(return_values.iter().len(), 3);
assert_eq!(return_values[0], (&Variable::mock("RAX", 8), expected_val)); assert_eq!(return_values[0], (&Variable::mock("RAX", 8), expected_val));
// Test returning a known value. // Test returning a known value.
let param_ref = DataDomain::from_target( let param_ref = DataDomain::from_target(
...@@ -39,6 +39,6 @@ fn test_compute_return_values_of_call() { ...@@ -39,6 +39,6 @@ fn test_compute_return_values_of_call() {
); );
let return_values = let return_values =
context.compute_return_values_of_call(&mut caller_state, &callee_state, &cconv, &call); context.compute_return_values_of_call(&mut caller_state, &callee_state, &cconv, &call);
assert_eq!(return_values.iter().len(), 1); assert_eq!(return_values.iter().len(), 3);
assert_eq!(return_values[0], (&Variable::mock("RAX", 8), expected_val)); assert_eq!(return_values[0], (&Variable::mock("RAX", 8), expected_val));
} }
...@@ -15,7 +15,7 @@ impl State { ...@@ -15,7 +15,7 @@ impl State {
State::new( State::new(
&Tid::new(tid_name), &Tid::new(tid_name),
&Variable::mock("RSP", 8), &Variable::mock("RSP", 8),
&CallingConvention::mock(), &CallingConvention::mock_x64(),
) )
} }
} }
......
...@@ -338,7 +338,7 @@ fn merge_callee_stack_to_caller_stack() { ...@@ -338,7 +338,7 @@ fn merge_callee_stack_to_caller_stack() {
fn remove_and_restore_callee_saved_register() { fn remove_and_restore_callee_saved_register() {
let mut state = State::new(&register("RSP"), Tid::new("func_tid")); let mut state = State::new(&register("RSP"), Tid::new("func_tid"));
let value: Data = Bitvector::from_u64(42).into(); let value: Data = Bitvector::from_u64(42).into();
let cconv = CallingConvention::mock(); let cconv = CallingConvention::mock_x64();
state.set_register(&register("RBP"), value.clone()); state.set_register(&register("RBP"), value.clone());
state.set_register(&register("RAX"), value.clone()); state.set_register(&register("RAX"), value.clone());
......
...@@ -211,7 +211,7 @@ pub mod tests { ...@@ -211,7 +211,7 @@ pub mod tests {
project.program.term.entry_points.insert(Tid::new("func")); project.program.term.entry_points.insert(Tid::new("func"));
project project
.calling_conventions .calling_conventions
.insert("__stdcall".to_string(), CallingConvention::mock()); .insert("__stdcall".to_string(), CallingConvention::mock_x64());
project project
} }
......
...@@ -213,45 +213,108 @@ mod tests { ...@@ -213,45 +213,108 @@ mod tests {
} }
} }
} }
/// Wrapper for subpiece to model float register for argument passing
fn create_float_register_subpiece(
name: &str,
reg_size: u64,
low_byte: u64,
size: u64,
) -> Expression {
Expression::subpiece(
Expression::Var(Variable::mock(name, reg_size)),
ByteSize::new(low_byte),
ByteSize::new(size),
)
}
impl CallingConvention { impl CallingConvention {
pub fn mock() -> CallingConvention { /// Creates System V Calling Convention with Advanced Vector Extensions 512
pub fn mock_x64() -> CallingConvention {
CallingConvention { CallingConvention {
name: "__stdcall".to_string(), // so that the mock is useable as standard calling convention in tests name: "__stdcall".to_string(), // so that the mock is useable as standard calling convention in tests
integer_parameter_register: vec![Variable::mock("RDI", 8)], integer_parameter_register: vec![
float_parameter_register: vec![Expression::Var(Variable::mock("XMMO", 16))], Variable::mock("RDI", 8),
integer_return_register: vec![Variable::mock("RAX", 8)], Variable::mock("RSI", 8),
float_return_register: vec![], Variable::mock("RDX", 8),
callee_saved_register: vec![Variable::mock("RBP", 8)], Variable::mock("RCX", 8),
Variable::mock("R8", 8),
Variable::mock("R9", 8),
],
// ABI: first 8 Bytes of ZMM0-ZMM7 for float parameter
// Ghidra: first 8 Bytes of YMM0-YMM7 for float parameter
float_parameter_register: vec![
create_float_register_subpiece("ZMM0", 64, 0, 8),
create_float_register_subpiece("ZMM1", 64, 0, 8),
create_float_register_subpiece("ZMM2", 64, 0, 8),
create_float_register_subpiece("ZMM3", 64, 0, 8),
create_float_register_subpiece("ZMM4", 64, 0, 8),
create_float_register_subpiece("ZMM5", 64, 0, 8),
create_float_register_subpiece("ZMM6", 64, 0, 8),
create_float_register_subpiece("ZMM7", 64, 0, 8),
],
integer_return_register: vec![Variable::mock("RAX", 8), Variable::mock("RDX", 8)],
// ABI: XMM0-XMM1 float return register
// Ghidra: uses XMM0 only
float_return_register: vec![create_float_register_subpiece("ZMM0", 64, 0, 8)],
callee_saved_register: vec![
Variable::mock("RBP", 8),
Variable::mock("RBX", 8),
Variable::mock("RSP", 8),
Variable::mock("R12", 8),
Variable::mock("R13", 8),
Variable::mock("R14", 8),
Variable::mock("R15", 8),
],
} }
} }
/// Following ARM32 ABI with MVE Extention
pub fn mock_arm32() -> CallingConvention { pub fn mock_arm32() -> CallingConvention {
CallingConvention { CallingConvention {
name: "__stdcall".to_string(), // so that the mock is useable as standard calling convention in tests name: "__stdcall".to_string(), // so that the mock is useable as standard calling convention in tests
integer_parameter_register: vec![Variable::mock("r0", 4)], integer_parameter_register: vec![
float_parameter_register: vec![Expression::Var(Variable::mock("d0", 8))], Variable::mock("r0", 4),
integer_return_register: vec![Variable::mock("r0", 4)], Variable::mock("r1", 4),
float_return_register: vec![], Variable::mock("r2", 4),
callee_saved_register: vec![Variable::mock("r4", 4)], Variable::mock("r3", 4),
} ],
} // ABI: q0-q3 used for argument passing
// Ghidra: uses q0-q1 only
pub fn mock_with_parameter_registers( float_parameter_register: vec![
integer_parameter_register: Vec<Variable>, create_float_register_subpiece("q0", 16, 0, 4),
float_parameter_register: Vec<Variable>, create_float_register_subpiece("q0", 16, 4, 4),
) -> CallingConvention { create_float_register_subpiece("q0", 16, 8, 4),
let float_parameter_register = float_parameter_register create_float_register_subpiece("q0", 16, 12, 4),
.into_iter() create_float_register_subpiece("q1", 16, 0, 4),
.map(Expression::Var) create_float_register_subpiece("q1", 16, 4, 4),
.collect(); create_float_register_subpiece("q1", 16, 8, 4),
CallingConvention { create_float_register_subpiece("q1", 16, 12, 4),
name: "__stdcall".to_string(), // so that the mock is useable as standard calling convention in tests ],
integer_parameter_register, // ABI: r0-r1 used as integer return register
float_parameter_register, // Ghidra uses r0 only
integer_return_register: vec![Variable::mock("RAX", 8)], integer_return_register: vec![
float_return_register: vec![], Variable::mock("r0", 4),
callee_saved_register: vec![Variable::mock("RBP", 8)], Variable::mock("r1", 4),
Variable::mock("r2", 4),
Variable::mock("r3", 4),
],
// ABI: whole q0 used as float return
// Ghidra: uses first 8 Bytes of q0 only
float_return_register: vec![create_float_register_subpiece("q0", 16, 0, 4)],
callee_saved_register: vec![
Variable::mock("r4", 4),
Variable::mock("r5", 4),
Variable::mock("r6", 4),
Variable::mock("r7", 4),
Variable::mock("r8", 4),
Variable::mock("r9", 4),
Variable::mock("r10", 4),
Variable::mock("r11", 4),
Variable::mock("r13", 4),
Variable::mock("q4", 16),
Variable::mock("q5", 16),
Variable::mock("q6", 16),
Variable::mock("q7", 16),
],
} }
} }
} }
...@@ -301,7 +364,6 @@ mod tests { ...@@ -301,7 +364,6 @@ mod tests {
} }
pub fn mock_arm32() -> ExternSymbol { pub fn mock_arm32() -> ExternSymbol {
// There is also the mock_standard_arm32() method. Only on of the two should exist!
ExternSymbol { ExternSymbol {
tid: Tid::new("mock_symbol"), tid: Tid::new("mock_symbol"),
addresses: vec!["UNKNOWN".to_string()], addresses: vec!["UNKNOWN".to_string()],
......
...@@ -9,36 +9,32 @@ fn mock_pi_state() -> PointerInferenceState { ...@@ -9,36 +9,32 @@ fn mock_pi_state() -> PointerInferenceState {
} }
#[test] #[test]
/// Tests extraction of format string parameters '/dev/sd%c%d' and 'cat %s'.
fn test_get_variable_parameters() { fn test_get_variable_parameters() {
let mem_image = RuntimeMemoryImage::mock(); let mem_image = RuntimeMemoryImage::mock();
let mut pi_state = mock_pi_state(); let mut pi_state = mock_pi_state();
let sprintf_symbol = ExternSymbol::mock_string(); let sprintf_symbol = ExternSymbol::mock_string();
let mut format_string_index_map: HashMap<String, usize> = HashMap::new(); let mut format_string_index_map: HashMap<String, usize> = HashMap::new();
format_string_index_map.insert("sprintf".to_string(), 0); format_string_index_map.insert("sprintf".to_string(), 1);
let global_address = Bitvector::from_str_radix(16, "5000").unwrap(); let global_address = Bitvector::from_str_radix(16, "5000").unwrap();
pi_state.set_register( pi_state.set_register(
&Variable::mock("RDI", 8 as u64), &Variable::mock("RSI", 8 as u64),
IntervalDomain::new(global_address.clone(), global_address).into(), IntervalDomain::new(global_address.clone(), global_address).into(),
); );
let mut project = Project::mock_empty(); let mut project = Project::mock_empty();
let cconv = CallingConvention::mock_with_parameter_registers( let cconv = CallingConvention::mock_x64();
vec![Variable::mock("RDI", 8)],
vec![Variable::mock("XMM0", 16)],
);
project.calling_conventions = BTreeMap::from_iter([(cconv.name.clone(), cconv)]); project.calling_conventions = BTreeMap::from_iter([(cconv.name.clone(), cconv)]);
let mut output: Vec<Arg> = Vec::new(); let mut output: Vec<Arg> = Vec::new();
output.push(Arg::Stack { output.push(Arg::from_var(
address: Expression::Var(Variable::mock("RSP", 8)).plus_const(8), Variable::mock("RDX", 8),
size: ByteSize::new(4), Some(Datatype::Char),
data_type: Some(Datatype::Char), ));
}); output.push(Arg::from_var(
Variable::mock("RCX", 8),
Some(Datatype::Integer),
));
output.push(Arg::Stack {
address: Expression::Var(Variable::mock("RSP", 8)).plus_const(12),
size: ByteSize::new(4),
data_type: Some(Datatype::Integer),
});
assert_eq!( assert_eq!(
output, output,
get_variable_parameters( get_variable_parameters(
...@@ -51,15 +47,14 @@ fn test_get_variable_parameters() { ...@@ -51,15 +47,14 @@ fn test_get_variable_parameters() {
.unwrap() .unwrap()
); );
output = vec![Arg::Stack { output = vec![Arg::from_var(
address: Expression::Var(Variable::mock("RSP", 8)).plus_const(8), Variable::mock("RDX", 8),
size: ByteSize::new(8), Some(Datatype::Pointer),
data_type: Some(Datatype::Pointer), )];
}];
let global_address = Bitvector::from_str_radix(16, "500c").unwrap(); let global_address = Bitvector::from_str_radix(16, "500c").unwrap();
pi_state.set_register( pi_state.set_register(
&Variable::mock("RDI", 8 as u64), &Variable::mock("RSI", 8 as u64),
IntervalDomain::new(global_address.clone(), global_address).into(), IntervalDomain::new(global_address.clone(), global_address).into(),
); );
...@@ -164,16 +159,9 @@ fn test_parse_format_string_parameters() { ...@@ -164,16 +159,9 @@ fn test_parse_format_string_parameters() {
} }
#[test] #[test]
/// Tests tracking of parameters according to format string
fn test_calculate_parameter_locations() { fn test_calculate_parameter_locations() {
let cconv = CallingConvention::mock_with_parameter_registers( let cconv = CallingConvention::mock_x64();
vec![
Variable::mock("RDI", 8),
Variable::mock("RSI", 8),
Variable::mock("R8", 8),
Variable::mock("R9", 8),
],
vec![Variable::mock("XMM0", 16)],
);
let format_string_index: usize = 1; let format_string_index: usize = 1;
let mut parameters: Vec<(Datatype, ByteSize)> = Vec::new(); let mut parameters: Vec<(Datatype, ByteSize)> = Vec::new();
parameters.push(("d".to_string().into(), ByteSize::new(8))); parameters.push(("d".to_string().into(), ByteSize::new(8)));
...@@ -182,20 +170,24 @@ fn test_calculate_parameter_locations() { ...@@ -182,20 +170,24 @@ fn test_calculate_parameter_locations() {
let mut expected_args = vec![ let mut expected_args = vec![
Arg::Register { Arg::Register {
expr: Expression::Var(Variable::mock("R8", ByteSize::new(8))), expr: Expression::Var(Variable::mock("RDX", ByteSize::new(8))),
data_type: Some(Datatype::Integer), data_type: Some(Datatype::Integer),
}, },
Arg::Register { Arg::Register {
expr: Expression::Var(Variable::mock("XMM0", ByteSize::new(16))), expr: Expression::subpiece(
Expression::Var(Variable::mock("ZMM0", 64)),
ByteSize::new(0),
ByteSize::new(8),
),
data_type: Some(Datatype::Double), data_type: Some(Datatype::Double),
}, },
Arg::Register { Arg::Register {
expr: Expression::Var(Variable::mock("R9", ByteSize::new(8))), expr: Expression::Var(Variable::mock("RCX", ByteSize::new(8))),
data_type: Some(Datatype::Pointer), data_type: Some(Datatype::Pointer),
}, },
]; ];
// Test Case 1: The string parameter is still written in the R9 register since 'f' is contained in the float register. // Test Case 1: The string parameter is still written in the RCX register since 'f' is contained in the float register.
assert_eq!( assert_eq!(
expected_args, expected_args,
calculate_parameter_locations( calculate_parameter_locations(
...@@ -208,13 +200,24 @@ fn test_calculate_parameter_locations() { ...@@ -208,13 +200,24 @@ fn test_calculate_parameter_locations() {
); );
parameters.push(("s".to_string().into(), ByteSize::new(8))); parameters.push(("s".to_string().into(), ByteSize::new(8)));
parameters.push(("s".to_string().into(), ByteSize::new(8)));
parameters.push(("s".to_string().into(), ByteSize::new(8)));
expected_args.push(Arg::Register {
expr: Expression::Var(Variable::mock("R8", ByteSize::new(8))),
data_type: Some(Datatype::Pointer),
});
expected_args.push(Arg::Register {
expr: Expression::Var(Variable::mock("R9", ByteSize::new(8))),
data_type: Some(Datatype::Pointer),
});
expected_args.push(Arg::Stack { expected_args.push(Arg::Stack {
address: Expression::Var(Variable::mock("RSP", 8)).plus_const(8), address: Expression::Var(Variable::mock("RSP", 8)).plus_const(8),
size: ByteSize::new(8), size: ByteSize::new(8),
data_type: Some(Datatype::Pointer), data_type: Some(Datatype::Pointer),
}); });
// Test Case 2: A second string parameter does not fit into the registers anymore and is written into the stack. // Test Case 2: Three further string parameter does not fit into the registers anymore and one is written into the stack.
assert_eq!( assert_eq!(
expected_args, expected_args,
calculate_parameter_locations( calculate_parameter_locations(
......
...@@ -407,7 +407,7 @@ pub mod tests { ...@@ -407,7 +407,7 @@ pub mod tests {
use super::*; use super::*;
impl RuntimeMemoryImage { impl RuntimeMemoryImage {
/// Create a mock runtime memory image for unit tests. /// Creates a mock runtime memory image with: byte series, strings and format strings.
pub fn mock() -> RuntimeMemoryImage { pub fn mock() -> RuntimeMemoryImage {
RuntimeMemoryImage { RuntimeMemoryImage {
memory_segments: vec![ memory_segments: vec![
......
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