Unverified Commit 74976f0d by Enkelmann Committed by GitHub

implement FunctionSignature analysis (#267)

parent e5631994
......@@ -8,6 +8,8 @@ on:
env:
CARGO_TERM_COLOR: always
GHIDRA_RELEASE_TAG: Ghidra_10.1_build
GHIDRA_VERSION: ghidra_10.1_PUBLIC_20211210
jobs:
......@@ -28,10 +30,10 @@ jobs:
architecture: x64
- name: Install Ghidra
run: |
curl -fSL https://www.ghidra-sre.org/ghidra_9.2.1_PUBLIC_20201215.zip -o ghidra.zip
unzip -q ghidra.zip
mv ghidra_9.2.1_PUBLIC /opt/ghidra
rm ghidra.zip
wget https://github.com/NationalSecurityAgency/ghidra/releases/download/${GHIDRA_RELEASE_TAG}/${GHIDRA_VERSION}.zip
unzip -d ghidra ${GHIDRA_VERSION}.zip
mv ghidra/ghidra_* /opt/ghidra
rm ${GHIDRA_VERSION}.zip
- uses: actions-rs/toolchain@v1
with:
profile: minimal
......
......@@ -121,6 +121,12 @@ impl<T: RegisterDomain> DataDomain<T> {
self.contains_top_values = true;
}
/// Indicate that the domain does not contain any `Top` values
/// in addition to the contained absolute and relative values.
pub fn unset_contains_top_flag(&mut self) {
self.contains_top_values = false;
}
/// Return a new value representing a variable plus an offset,
/// where the variable is represented by the given abstract ID.
pub fn from_target(id: AbstractIdentifier, offset: T) -> Self {
......
......@@ -43,6 +43,15 @@ impl AbstractIdentifier {
AbstractIdentifier(Arc::new(AbstractIdentifierData { time, location }))
}
/// Create a new abstract identifier where the abstract location is a register.
/// Panics if the register is a temporary register.
pub fn new_from_var(time: Tid, variable: &Variable) -> AbstractIdentifier {
AbstractIdentifier(Arc::new(AbstractIdentifierData {
time,
location: AbstractLocation::from_var(variable).unwrap(),
}))
}
/// Get the register associated to the abstract location.
/// Panics if the abstract location is a memory location and not a register.
pub fn unwrap_register(&self) -> &Variable {
......@@ -56,6 +65,11 @@ impl AbstractIdentifier {
pub fn get_tid(&self) -> &Tid {
&self.time
}
/// Get the location component of the abstract ID
pub fn get_location(&self) -> &AbstractLocation {
&self.location
}
}
impl std::fmt::Display for AbstractIdentifier {
......
use crate::{abstract_domain::AbstractDomain, prelude::*};
use std::fmt::Display;
/// Access flags to track different kind of access/usage patterns of a variable.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub struct AccessPattern {
/// The variable was used in the computation of a pointer that was dereferenced for reading a value.
dereferenced: bool,
/// The variable was accessed to compute some nontrivial value
/// or the value was stored in some location.
read: bool,
/// The variable was used in the computation of a pointer that was dereferenced for writing a value.
mutably_dereferenced: bool,
}
impl AccessPattern {
/// Generate a new `AccessPattern` object with none of the access flags set.
pub fn new() -> Self {
Self {
dereferenced: false,
read: false,
mutably_dereferenced: false,
}
}
/// Set the access flag for immutable pointer dereference.
pub fn set_dereference_flag(&mut self) {
self.dereferenced = true;
}
/// Set the access flag for read access.
pub fn set_read_flag(&mut self) {
self.read = true;
}
/// Set the access flag for pointer dereference (with write access to the target of the pointer).
pub fn set_mutably_dereferenced_flag(&mut self) {
self.mutably_dereferenced = true;
}
/// Set all access flags to indicate that any kind of access to the variable may have occured.
pub fn set_unknown_access_flags(&mut self) {
self.read = true;
self.dereferenced = true;
self.mutably_dereferenced = true;
}
/// Returns true if any of the access flags is set.
pub fn is_accessed(&self) -> bool {
self.read || self.dereferenced || self.mutably_dereferenced
}
/// Returns true if the dereferenced or mutably dereferenced access flag is set.
pub fn is_dereferenced(&self) -> bool {
self.dereferenced || self.mutably_dereferenced
}
/// Returns true if the mutably dereferenced access flag is set.
pub fn is_mutably_dereferenced(&self) -> bool {
self.mutably_dereferenced
}
}
impl Default for AccessPattern {
fn default() -> Self {
Self::new()
}
}
impl AbstractDomain for AccessPattern {
/// An access flag in the merged `AccessPattern` object is set
/// if it is set in at least one of the input objects.
fn merge(&self, other: &Self) -> Self {
AccessPattern {
dereferenced: self.dereferenced || other.dereferenced,
read: self.read || other.read,
mutably_dereferenced: self.mutably_dereferenced || other.mutably_dereferenced,
}
}
/// Returns true if all of the access flags are set.
fn is_top(&self) -> bool {
self.read && self.dereferenced && self.mutably_dereferenced
}
}
impl Display for AccessPattern {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.read {
write!(f, "r")?;
} else {
write!(f, "-")?;
}
if self.dereferenced {
write!(f, "d")?;
} else {
write!(f, "-")?;
}
if self.mutably_dereferenced {
write!(f, "w")?;
} else {
write!(f, "-")?;
}
Ok(())
}
}
use super::*;
use std::collections::HashSet;
#[test]
fn test_compute_return_values_of_call() {
let project = Project::mock_empty();
let cconv = CallingConvention::mock();
let graph = crate::analysis::graph::get_program_cfg(&project.program, HashSet::new());
let context = Context::new(&project, &graph);
let mut caller_state = State::mock_x64("caller");
let mut callee_state = State::mock_x64("callee");
let call = Term {
tid: Tid::new("call_tid"),
term: Jmp::Call {
target: Tid::new("callee"),
return_: Some(Tid::new("return_tid")),
},
};
// Test returning a value of unknown origin (since RAX does not contain a reference to the input register).
let return_values =
context.compute_return_values_of_call(&mut caller_state, &callee_state, &cconv, &call);
let expected_val = DataDomain::from_target(
AbstractIdentifier::new_from_var(Tid::new("call_tid"), &Variable::mock("RAX", 8)),
Bitvector::from_i64(0).into(),
);
assert_eq!(return_values.iter().len(), 1);
assert_eq!(return_values[0], (&Variable::mock("RAX", 8), expected_val));
// Test returning a known value.
let param_ref = DataDomain::from_target(
AbstractIdentifier::new_from_var(Tid::new("callee"), &Variable::mock("RDI", 8)),
Bitvector::from_i64(0).into(),
);
callee_state.set_register(&Variable::mock("RAX", 8), param_ref);
let expected_val = DataDomain::from_target(
AbstractIdentifier::new_from_var(Tid::new("caller"), &Variable::mock("RDI", 8)),
Bitvector::from_i64(0).into(),
);
let return_values =
context.compute_return_values_of_call(&mut caller_state, &callee_state, &cconv, &call);
assert_eq!(return_values.iter().len(), 1);
assert_eq!(return_values[0], (&Variable::mock("RAX", 8), expected_val));
}
//! A fixpoint algorithm computing parameters of functions and their access patterns.
//!
//! The fixpoint algorithm tracks the values of registers and the stack,
//! although only stack accesses with known, constant offset are processed.
//! Accesses to potential function parameters are collected together with the type of the access
//! (is the value read, dereferenced for read access or dereferenced for write access).
//!
//! Known limitations of the analysis:
//! * The analysis is an overapproximation in the sense that it may generate more input parameters
//! than actually exist in some cases.
//! * Only registers that are potential parameter registers in the standard calling convention
//! of the CPU architecture are considered as potential parameter registers.
//! For functions that use other registers
//! than those in the standard calling convention for parameter passing
//! the results of this analysis will be wrong.
//! * Parameters that are used as input values for variadic functions (e.g. sprintf) may be missed
//! since detection of variadic function parameters is not yet implemented for this analysis.
//! * If only a part (e.g. a single byte) of a stack parameter is accessed instead of the whole parameter
//! then a duplicate stack parameter may be generated.
//! A proper sanitation for this case is not yet implemented,
//! although error messages are generated if such a case is detected.
//! * For floating point parameter registers the base register is detected as a parameter,
//! although only a smaller sub-register is the actual parameter in many cases.
//! Also, if a function uses sub-registers of floating point registers as local variables,
//! the registers may be incorrectly flagged as input parameters.
use crate::abstract_domain::AbstractDomain;
use crate::analysis::fixpoint::Computation;
use crate::analysis::forward_interprocedural_fixpoint::create_computation;
use crate::analysis::forward_interprocedural_fixpoint::GeneralizedContext;
use crate::analysis::graph::*;
use crate::analysis::interprocedural_fixpoint_generic::NodeValue;
use crate::intermediate_representation::*;
use crate::prelude::*;
use crate::utils::log::LogMessage;
use std::collections::BTreeMap;
use std::collections::HashMap;
mod context;
use context::*;
mod state;
use state::State;
mod access_pattern;
pub use access_pattern::AccessPattern;
/// Generate the computation object for the fixpoint computation
/// and set the node values for all function entry nodes.
fn generate_fixpoint_computation<'a>(
project: &'a Project,
graph: &'a Graph,
) -> Computation<GeneralizedContext<'a, Context<'a>>> {
let context = Context::new(project, graph);
let mut computation = create_computation(context, None);
// Set the node values for all function entry nodes.
for node in graph.node_indices() {
if let Node::BlkStart(block, sub) = graph[node] {
if let Some(entry_block) = sub.term.blocks.get(0) {
if entry_block.tid == block.tid {
// The node of a function entry point
computation.set_node_value(
node,
NodeValue::Value(State::new(
&sub.tid,
&project.stack_pointer_register,
project.get_standard_calling_convention().unwrap(),
)),
)
}
}
}
}
computation
}
/// Extract the function signatures from the computed fixpoint.
///
/// This function needs to merge the signatures at all nodes corresponding to a function
/// to ensure that parameter accesses on non-returning execution paths of a function
/// are also recognized in the function signature.
fn extract_fn_signatures_from_fixpoint<'a>(
project: &'a Project,
graph: &'a Graph,
fixpoint: Computation<GeneralizedContext<'a, Context<'a>>>,
) -> BTreeMap<Tid, FunctionSignature> {
let mut fn_sig_map: BTreeMap<Tid, FunctionSignature> = project
.program
.term
.subs
.keys()
.map(|tid| (tid.clone(), FunctionSignature::new()))
.collect();
for node in graph.node_indices() {
match fixpoint.get_node_value(node) {
None => (),
Some(NodeValue::Value(state)) => {
let fn_sig = fn_sig_map
.get_mut(state.get_current_function_tid())
.unwrap();
fn_sig.merge_with_fn_sig_of_state(state);
}
Some(NodeValue::CallFlowCombinator {
call_stub,
interprocedural_flow,
}) => {
if let Some(state) = call_stub {
let fn_sig = fn_sig_map
.get_mut(state.get_current_function_tid())
.unwrap();
fn_sig.merge_with_fn_sig_of_state(state);
}
if let Some(state) = interprocedural_flow {
let fn_sig = fn_sig_map
.get_mut(state.get_current_function_tid())
.unwrap();
fn_sig.merge_with_fn_sig_of_state(state);
}
}
}
}
fn_sig_map
}
/// Compute the function signatures for all functions in the project.
///
/// Returns a map from the function TIDs to their signatures,
/// and a list of log messages recorded during the computation of the signatures.
///
/// For more information on the used algorithm see the module-level documentation.
pub fn compute_function_signatures<'a>(
project: &'a Project,
graph: &'a Graph,
) -> (Vec<LogMessage>, BTreeMap<Tid, FunctionSignature>) {
let mut computation = generate_fixpoint_computation(project, graph);
computation.compute_with_max_steps(100);
let mut fn_sig_map = extract_fn_signatures_from_fixpoint(project, graph, computation);
// Sanitize the parameters
let mut logs = Vec::new();
for (fn_tid, fn_sig) in fn_sig_map.iter_mut() {
if fn_sig.sanitize(project).is_err() {
logs.push(
LogMessage::new_error("Function parameters are not properly sanitized")
.location(fn_tid.clone())
.source("Function Signature Analysis"),
);
}
}
(logs, fn_sig_map)
}
/// The signature of a function.
/// Currently only contains information on the parameters of a function and their access patterns.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct FunctionSignature {
/// The parameters of the function together with their access patterns.
pub parameters: HashMap<Arg, AccessPattern>,
}
impl FunctionSignature {
/// Generate an empty function signature.
pub fn new() -> Self {
Self {
parameters: HashMap::new(),
}
}
/// Merge the parameter list of `self` with the given parameter list.
fn merge_parameter_list(&mut self, params: &[(Arg, AccessPattern)]) {
for (arg, sig_new) in params {
if let Some(sig_self) = self.parameters.get_mut(arg) {
*sig_self = sig_self.merge(sig_new);
} else {
self.parameters.insert(arg.clone(), *sig_new);
}
}
}
/// Merge the function signature with the signature extracted from the given state.
fn merge_with_fn_sig_of_state(&mut self, state: &State) {
let params = state.get_params_of_current_function();
self.merge_parameter_list(&params);
}
/// Sanitize the function signature:
/// * Remove the return address from the list of stack parameters for x86-based architectures.
/// * Check for unaligned stack parameters or stack parameters that are not pointer-sized
/// and return an error message if one is found.
/// This may indicate an error in the analysis
/// as no proper sanitation pass is implemented for such cases yet.
fn sanitize(&mut self, project: &Project) -> Result<(), Error> {
match project.cpu_architecture.as_str() {
"x86" | "x86_32" | "x86_64" => {
let return_addr_expr = Expression::Var(project.stack_pointer_register.clone());
let return_addr_arg = Arg::Stack {
address: return_addr_expr,
size: project.stack_pointer_register.size,
data_type: None,
};
self.parameters.remove(&return_addr_arg);
}
_ => (),
}
self.check_for_unaligned_stack_params(&project.stack_pointer_register)
}
/// Return an error if an unaligned stack parameter
/// or a stack parameter of different size than the generic pointer size is found.
fn check_for_unaligned_stack_params(&self, stack_register: &Variable) -> Result<(), Error> {
for arg in self.parameters.keys() {
if let Arg::Stack { size, .. } = arg {
if *size != stack_register.size {
return Err(anyhow!("Unexpected stack parameter size"));
}
if let Ok(offset) = arg.eval_stack_offset(stack_register) {
if offset.try_to_u64()? % u64::from(stack_register.size) != 0 {
return Err(anyhow!("Unexpected stack parameter alignment"));
}
}
}
}
Ok(())
}
}
impl Default for FunctionSignature {
fn default() -> Self {
Self::new()
}
}
use super::*;
impl State {
/// Generate a mock state for an ARM-32 state.
pub fn mock() -> State {
State::new(
&Tid::new("mock_fn"),
&Variable::mock("sp", 4),
&CallingConvention::mock_standard_arm_32(),
)
}
/// Generate a mock state for an x64 state.
pub fn mock_x64(tid_name: &str) -> State {
State::new(
&Tid::new(tid_name),
&Variable::mock("RSP", 8),
&CallingConvention::mock(),
)
}
}
/// Mock an abstract ID representing the stack.
fn mock_stack_id() -> AbstractIdentifier {
AbstractIdentifier::new_from_var(Tid::new("mock_fn"), &Variable::mock("sp", 4))
}
/// Mock an abstract ID of a stack parameter
fn mock_stack_param_id(offset: i64, size: u64) -> AbstractIdentifier {
AbstractIdentifier::new(
Tid::new("mock_fn"),
AbstractLocation::from_stack_position(
mock_stack_id().unwrap_register(),
offset,
ByteSize::new(size),
),
)
}
#[test]
fn test_new() {
let state = State::mock();
// Test the generated stack
assert_eq!(&state.stack_id, &mock_stack_id());
assert_eq!(state.stack.iter().len(), 0);
// Assert that the register values are as expected
assert_eq!(state.register.len(), 9); // 8 parameters plus stack pointer
assert_eq!(
state.get_register(&Variable::mock("sp", 4)),
DataDomain::from_target(
mock_stack_id(),
Bitvector::zero(ByteSize::new(4).into()).into()
)
);
// Check the generated tracked IDs
assert_eq!(state.tracked_ids.len(), 8);
for (id, access_pattern) in state.tracked_ids.iter() {
assert_eq!(
state.get_register(id.unwrap_register()),
DataDomain::from_target(id.clone(), Bitvector::zero(ByteSize::new(4).into()).into())
);
assert_eq!(access_pattern, &AccessPattern::new());
}
}
#[test]
fn test_store_and_load_from_stack() {
let mut state = State::mock();
let address = DataDomain::from_target(mock_stack_id(), Bitvector::from_i32(-4).into());
let value: DataDomain<BitvectorDomain> = Bitvector::from_i32(0).into();
// write and load a value to the current stack frame
state.write_value(address.clone(), value.clone());
assert_eq!(state.stack.iter().len(), 1);
assert_eq!(
state.stack.get(Bitvector::from_i32(-4), ByteSize::new(4)),
value.clone()
);
assert_eq!(state.load_value(address, ByteSize::new(4)), value);
// Load a parameter register and check that the parameter gets generated
let address = DataDomain::from_target(mock_stack_id(), Bitvector::from_i32(4).into());
let stack_param_id = mock_stack_param_id(4, 4);
let stack_param =
DataDomain::from_target(stack_param_id.clone(), Bitvector::from_i32(0).into());
assert_eq!(state.tracked_ids.iter().len(), 8);
assert_eq!(
state.load_value(address.clone(), ByteSize::new(4)),
stack_param
);
assert_eq!(state.tracked_ids.iter().len(), 9);
assert_eq!(
state
.tracked_ids
.get(&stack_param_id)
.unwrap()
.is_accessed(),
false
); // The load method does not set access flags.
}
#[test]
fn test_load_unsized_from_stack() {
let mut state = State::mock();
// Load an existing stack param (generated by a sized load at the same address)
let address = DataDomain::from_target(mock_stack_id(), Bitvector::from_i32(0).into());
let stack_param_id = mock_stack_param_id(0, 4);
let stack_param =
DataDomain::from_target(stack_param_id.clone(), Bitvector::from_i32(0).into());
state.load_value(address, ByteSize::new(4));
let unsized_load = state.load_unsized_value_from_stack(Bitvector::from_i32(0));
assert_eq!(unsized_load, stack_param);
assert!(state.tracked_ids.get(&stack_param_id).is_some());
// Load a non-existing stack param
let stack_param_id = mock_stack_param_id(4, 1);
let stack_param = DataDomain::from_target(stack_param_id.clone(), Bitvector::from_i8(0).into());
let unsized_load = state.load_unsized_value_from_stack(Bitvector::from_i32(4));
assert_eq!(unsized_load, stack_param);
assert!(state.tracked_ids.get(&stack_param_id).is_some());
// Unsized load from the current stack frame
let unsized_load = state.load_unsized_value_from_stack(Bitvector::from_i32(-4));
assert_eq!(unsized_load, DataDomain::new_top(ByteSize::new(1)));
}
#[test]
fn test_eval() {
let mut state = State::mock();
// Test the eval method
let expr = Expression::Var(Variable::mock("sp", 4)).plus_const(42);
assert_eq!(
state.eval(&expr),
DataDomain::from_target(mock_stack_id(), Bitvector::from_i32(42).into())
);
// Test the eval_parameter_arg method
let arg = Arg::from_var(Variable::mock("sp", 4), None);
assert_eq!(
state.eval_parameter_arg(&arg),
DataDomain::from_target(mock_stack_id(), Bitvector::from_i32(0).into())
);
}
#[test]
fn test_extern_symbol_handling() {
let mut state = State::mock();
let extern_symbol = ExternSymbol::mock_arm32();
let cconv = CallingConvention::mock_arm32();
let call = Term {
tid: Tid::new("call_tid"),
term: Jmp::Call {
target: extern_symbol.tid.clone(),
return_: Some(Tid::new("return_tid")),
},
};
let param_id = AbstractIdentifier::new_from_var(Tid::new("mock_fn"), &Variable::mock("r0", 4));
let return_val_id =
AbstractIdentifier::new_from_var(Tid::new("call_tid"), &Variable::mock("r0", 4));
// Test extern symbol handling.
state.handle_extern_symbol(&call, &extern_symbol, &cconv);
assert_eq!(
state
.tracked_ids
.get(&param_id)
.unwrap()
.is_mutably_dereferenced(),
true
);
let return_val = state.get_register(&Variable::mock("r0", 4));
assert_eq!(return_val.get_relative_values().iter().len(), 2);
assert_eq!(
return_val.get_relative_values().get(&param_id).unwrap(),
&BitvectorDomain::new_top(ByteSize::new(4))
);
assert_eq!(
return_val.get_relative_values().get(&param_id).unwrap(),
&BitvectorDomain::new_top(ByteSize::new(4))
);
assert_eq!(
return_val
.get_relative_values()
.get(&return_val_id)
.unwrap(),
&Bitvector::from_i32(0).into()
);
}
......@@ -5,6 +5,7 @@ pub mod backward_interprocedural_fixpoint;
pub mod dead_variable_elimination;
pub mod fixpoint;
pub mod forward_interprocedural_fixpoint;
pub mod function_signature;
pub mod graph;
pub mod interprocedural_fixpoint_generic;
pub mod pointer_inference;
......
......@@ -446,7 +446,11 @@ mod tests {
let (mut state, _pi_state) = State::mock_with_pi_state();
assert_eq!(
context.check_parameters_for_taint(&state, &ExternSymbol::mock(), NodeIndex::new(0)),
context.check_parameters_for_taint(
&state,
&ExternSymbol::mock_x64(),
NodeIndex::new(0)
),
false
);
......@@ -455,7 +459,11 @@ mod tests {
Taint::Tainted(ByteSize::new(8)),
);
assert_eq!(
context.check_parameters_for_taint(&state, &ExternSymbol::mock(), NodeIndex::new(0)),
context.check_parameters_for_taint(
&state,
&ExternSymbol::mock_x64(),
NodeIndex::new(0)
),
true
);
}
......
......@@ -52,6 +52,46 @@ impl Arg {
Arg::Stack { data_type, .. } => data_type.clone(),
}
}
/// If the argument is a stack argument,
/// return its offset relative to the current stack register value.
/// Return an error for register arguments or if the offset could not be computed.
pub fn eval_stack_offset(&self, stack_register: &Variable) -> Result<Bitvector, Error> {
let expression = match self {
Arg::Register { .. } => return Err(anyhow!("The argument is not a stack argument.")),
Arg::Stack { address, .. } => address,
};
Self::eval_stack_offset_expression(expression, stack_register)
}
/// If the given expression computes a constant offset to the given stack register,
/// then return the offset.
/// Else return an error.
fn eval_stack_offset_expression(
expression: &Expression,
stack_register: &Variable,
) -> Result<Bitvector, Error> {
match expression {
Expression::Var(var) => {
if var == stack_register {
Ok(Bitvector::zero(var.size.into()))
} else {
Err(anyhow!("Input register is not the stack register"))
}
}
Expression::Const(bitvec) => Ok(bitvec.clone()),
Expression::BinOp { op, lhs, rhs } => {
let lhs = Self::eval_stack_offset_expression(lhs, stack_register)?;
let rhs = Self::eval_stack_offset_expression(rhs, stack_register)?;
lhs.bin_op(*op, &rhs)
}
Expression::UnOp { op, arg } => {
let arg = Self::eval_stack_offset_expression(arg, stack_register)?;
arg.un_op(*op)
}
_ => Err(anyhow!("Expression type not supported for argument values")),
}
}
}
/// An extern symbol represents a funtion that is dynamically linked from another binary.
......@@ -131,6 +171,30 @@ pub struct CallingConvention {
pub callee_saved_register: Vec<Variable>,
}
impl CallingConvention {
/// Return a list of all parameter registers of the calling convention.
/// For parameters, where only a part of a register is the actual parameter,
/// the parameter register is approximated by the (larger) base register.
pub fn get_all_parameter_register(&self) -> Vec<&Variable> {
let mut register_list: Vec<&Variable> = self.integer_parameter_register.iter().collect();
for float_param_expr in self.float_parameter_register.iter() {
register_list.append(&mut float_param_expr.input_vars());
}
register_list
}
/// Return a list of all return registers of the calling convention.
/// For return register, where only a part of a register is the actual return register,
/// the return register is approximated by the (larger) base register.
pub fn get_all_return_register(&self) -> Vec<&Variable> {
let mut register_list: Vec<&Variable> = self.integer_return_register.iter().collect();
for float_param_expr in self.float_return_register.iter() {
register_list.append(&mut float_param_expr.input_vars());
}
register_list
}
}
#[cfg(test)]
mod tests {
use super::*;
......@@ -159,6 +223,17 @@ mod tests {
}
}
pub fn mock_arm32() -> CallingConvention {
CallingConvention {
name: "__stdcall".to_string(), // so that the mock is useable as standard calling convention in tests
integer_parameter_register: vec![Variable::mock("r0", 4)],
float_parameter_register: vec![Expression::Var(Variable::mock("d0", 8))],
integer_return_register: vec![Variable::mock("r0", 4)],
float_return_register: vec![],
callee_saved_register: vec![Variable::mock("r4", 4)],
}
}
pub fn mock_with_parameter_registers(
integer_parameter_register: Vec<Variable>,
float_parameter_register: Vec<Variable>,
......@@ -209,7 +284,7 @@ mod tests {
}
impl ExternSymbol {
pub fn mock() -> ExternSymbol {
pub fn mock_x64() -> ExternSymbol {
ExternSymbol {
tid: Tid::new("mock_symbol"),
addresses: vec!["UNKNOWN".to_string()],
......@@ -222,6 +297,20 @@ mod tests {
}
}
pub fn mock_arm32() -> ExternSymbol {
// There is also the mock_standard_arm32() method. Only on of the two should exist!
ExternSymbol {
tid: Tid::new("mock_symbol"),
addresses: vec!["UNKNOWN".to_string()],
name: "mock_symbol".to_string(),
calling_convention: Some("__stdcall".to_string()),
parameters: vec![Arg::mock_register("r0", 4)],
return_values: vec![Arg::mock_register("r0", 4)],
no_return: false,
has_var_args: false,
}
}
pub fn mock_string() -> Self {
ExternSymbol {
tid: Tid::new("sprintf"),
......
use super::ByteSize;
use crate::prelude::*;
use std::fmt::Display;
/// A variable represents a register with a known size and name.
///
......@@ -19,6 +20,16 @@ pub struct Variable {
pub is_temp: bool,
}
impl Display for Variable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.name, self.size.as_bit_length())?;
if self.is_temp {
write!(f, "(temp)")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
......
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