Unverified Commit a16e6589 by Enkelmann Committed by GitHub

add global memory tracking to FunctionSignature analysis (#358)

parent 0359ea16
...@@ -86,6 +86,11 @@ impl AbstractIdentifier { ...@@ -86,6 +86,11 @@ impl AbstractIdentifier {
AbstractIdentifier::new(time.clone(), location) AbstractIdentifier::new(time.clone(), location)
} }
/// Create an abstract identifier from an address into global memory.
pub fn from_global_address(time: &Tid, address: &Bitvector) -> AbstractIdentifier {
AbstractIdentifier::new(time.clone(), AbstractLocation::from_global_address(address))
}
/// Create a new abstract identifier /// Create a new abstract identifier
/// by pushing the given path hint to the array of path hints of `self`. /// by pushing the given path hint to the array of path hints of `self`.
/// Returns an error if the path hint is already contained in the path hints of `self`. /// Returns an error if the path hint is already contained in the path hints of `self`.
...@@ -106,11 +111,13 @@ impl AbstractIdentifier { ...@@ -106,11 +111,13 @@ impl AbstractIdentifier {
} }
/// Get the register associated to the abstract location. /// Get the register associated to the abstract location.
/// Panics if the abstract location is a memory location and not a register. /// Panics if the abstract location is not a register but a memory location.
pub fn unwrap_register(&self) -> &Variable { pub fn unwrap_register(&self) -> &Variable {
match &self.location { match &self.location {
AbstractLocation::Register(var) => var, AbstractLocation::Register(var) => var,
AbstractLocation::Pointer(_, _) => panic!("Abstract location is not a register."), AbstractLocation::GlobalAddress { .. }
| AbstractLocation::GlobalPointer(_, _)
| AbstractLocation::Pointer(_, _) => panic!("Abstract location is not a register."),
} }
} }
...@@ -153,18 +160,36 @@ impl std::fmt::Display for AbstractIdentifier { ...@@ -153,18 +160,36 @@ impl std::fmt::Display for AbstractIdentifier {
pub enum AbstractLocation { pub enum AbstractLocation {
/// The location is given by a register. /// The location is given by a register.
Register(Variable), Register(Variable),
/// The value itself is a constant address to global memory.
/// Note that the `size` is the size of the pointer and not the size
/// of the value residing at the specific address in global memory.
GlobalAddress {
/// The address in global memory.
address: u64,
/// The byte size of the address (not the pointed-to value!).
size: ByteSize,
},
/// The location is in memory. /// The location is in memory.
/// One needs to follow the pointer in the given register /// One needs to follow the pointer in the given register
/// and then follow the abstract memory location inside the pointed to memory object /// and then follow the abstract memory location inside the pointed to memory object
/// to find the actual memory location. /// to find the actual memory location.
Pointer(Variable, AbstractMemoryLocation), Pointer(Variable, AbstractMemoryLocation),
/// The location is in memory.
/// One needs to follow the pointer located at the given global address
/// and then follow the abstract memory location inside the pointed to memory object
/// to find the actual memory location.
GlobalPointer(u64, AbstractMemoryLocation),
} }
impl std::fmt::Display for AbstractLocation { impl std::fmt::Display for AbstractLocation {
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
match self { match self {
Self::Register(var) => write!(formatter, "{}", var.name), Self::Register(var) => write!(formatter, "{}", var.name),
Self::GlobalAddress { address, size: _ } => write!(formatter, "0x{:x}", address),
Self::Pointer(var, location) => write!(formatter, "{}->{}", var.name, location), Self::Pointer(var, location) => write!(formatter, "{}->{}", var.name, location),
Self::GlobalPointer(address, location) => {
write!(formatter, "0x{:x}->{}", address, location)
}
} }
} }
} }
...@@ -193,11 +218,23 @@ impl AbstractLocation { ...@@ -193,11 +218,23 @@ impl AbstractLocation {
AbstractLocation::Pointer(stack_register.clone(), stack_pos) AbstractLocation::Pointer(stack_register.clone(), stack_pos)
} }
/// Create an abstract location representing an address pointing to global memory.
pub fn from_global_address(address: &Bitvector) -> AbstractLocation {
let size = address.bytesize();
let address = address
.try_to_u64()
.expect("Global address larger than 64 bits encountered.");
AbstractLocation::GlobalAddress { address, size }
}
/// Get the bytesize of the value represented by the abstract location. /// Get the bytesize of the value represented by the abstract location.
pub fn bytesize(&self) -> ByteSize { pub fn bytesize(&self) -> ByteSize {
match self { match self {
Self::Register(var) => var.size, Self::Register(var) => var.size,
Self::Pointer(_pointer_var, mem_location) => mem_location.bytesize(), Self::GlobalAddress { size, .. } => *size,
Self::Pointer(_, mem_location) | Self::GlobalPointer(_, mem_location) => {
mem_location.bytesize()
}
} }
} }
} }
......
//! Generate call graphs out of a program term.
use std::collections::HashMap;
use crate::intermediate_representation::*;
use petgraph::graph::DiGraph;
/// The graph type of a call graph
pub type CallGraph<'a> = DiGraph<Tid, &'a Term<Jmp>>;
/// Generate a call graph for the given program.
///
/// The nodes of the returned graph correspond to the TIDs of functions in the program.
/// Edges are jump terms of call operations.
///
/// Note that calls to external symbols are not represented in the graph,
/// i.e. there are neither nodes nor edges representing (calls to) external symbols in the graph.
/// Also, there are currently no edges for indirect calls,
/// because a corresponding analysis for resolving indirect calls is not implemented yet.
pub fn get_program_callgraph(program: &Term<Program>) -> CallGraph {
let mut callgraph = CallGraph::new();
let mut tid_to_node_index_map = HashMap::new();
for sub_tid in program.term.subs.keys() {
let node_index = callgraph.add_node(sub_tid.clone());
tid_to_node_index_map.insert(sub_tid.clone(), node_index);
}
for sub in program.term.subs.values() {
let source_index = tid_to_node_index_map.get(&sub.tid).unwrap();
for block in &sub.term.blocks {
for jump in &block.term.jmps {
if let Jmp::Call { target, .. } = &jump.term {
if let Some(target_index) = tid_to_node_index_map.get(target) {
callgraph.add_edge(*source_index, *target_index, jump);
}
}
}
}
}
callgraph
}
#[cfg(test)]
pub mod tests {
use super::*;
#[test]
fn test_get_program_callgraph() {
// Create a program with 2 functions and one call between them
let mut project = Project::mock_x64();
let mut caller = Sub::mock("caller");
let callee = Sub::mock("callee");
let call = Jmp::Call {
target: Tid::new("callee"),
return_: None,
};
let mut call_block = Blk::mock();
call_block.term.jmps.push(Term {
tid: Tid::new("call"),
term: call,
});
caller.term.blocks.push(call_block);
project.program.term.subs.insert(Tid::new("caller"), caller);
project.program.term.subs.insert(Tid::new("callee"), callee);
// Test correctness of the call graph
let callgraph = get_program_callgraph(&project.program);
assert_eq!(callgraph.node_indices().len(), 2);
assert_eq!(callgraph.edge_indices().len(), 1);
let (start, end) = callgraph
.edge_endpoints(callgraph.edge_indices().next().unwrap())
.unwrap();
assert_eq!(callgraph[start], Tid::new("caller"));
assert_eq!(callgraph[end], Tid::new("callee"));
}
}
use crate::abstract_domain::{ use crate::abstract_domain::{
AbstractDomain, AbstractIdentifier, BitvectorDomain, DataDomain, TryToBitvec, AbstractDomain, AbstractIdentifier, AbstractLocation, BitvectorDomain, DataDomain, SizedDomain,
TryToBitvec,
}; };
use crate::utils::arguments; use crate::utils::arguments;
use crate::{ use crate::{
...@@ -95,6 +96,8 @@ impl<'a> Context<'a> { ...@@ -95,6 +96,8 @@ impl<'a> Context<'a> {
for (callee_id, callee_offset) in callee_value.get_relative_values() { for (callee_id, callee_offset) in callee_value.get_relative_values() {
if let Some(param_arg) = callee_state.get_arg_corresponding_to_id(callee_id) { if let Some(param_arg) = callee_state.get_arg_corresponding_to_id(callee_id) {
let param_value = caller_state.eval_parameter_arg(&param_arg); let param_value = caller_state.eval_parameter_arg(&param_arg);
let param_value = caller_state
.substitute_global_mem_address(param_value, &self.project.runtime_memory_image);
if param_value.contains_top() || param_value.get_absolute_value().is_some() { if param_value.contains_top() || param_value.get_absolute_value().is_some() {
return_value.set_contains_top_flag() return_value.set_contains_top_flag()
} }
...@@ -135,7 +138,10 @@ impl<'a> Context<'a> { ...@@ -135,7 +138,10 @@ impl<'a> Context<'a> {
if let Some(param_access_list) = self.param_access_stubs.get(extern_symbol.name.as_str()) { if let Some(param_access_list) = self.param_access_stubs.get(extern_symbol.name.as_str()) {
// Set access flags for parameter access // Set access flags for parameter access
for (param, access_pattern) in extern_symbol.parameters.iter().zip(param_access_list) { for (param, access_pattern) in extern_symbol.parameters.iter().zip(param_access_list) {
for id in state.eval_parameter_arg(param).get_relative_values().keys() { let param_value = state.eval_parameter_arg(param);
let param_value = state
.substitute_global_mem_address(param_value, &self.project.runtime_memory_image);
for id in param_value.get_relative_values().keys() {
state.merge_access_pattern_of_id(id, access_pattern); state.merge_access_pattern_of_id(id, access_pattern);
} }
} }
...@@ -158,7 +164,12 @@ impl<'a> Context<'a> { ...@@ -158,7 +164,12 @@ impl<'a> Context<'a> {
state.clear_non_callee_saved_register(&cconv.callee_saved_register); state.clear_non_callee_saved_register(&cconv.callee_saved_register);
state.set_register(&cconv.integer_return_register[0], return_val); state.set_register(&cconv.integer_return_register[0], return_val);
} else { } else {
state.handle_generic_extern_symbol(call_tid, extern_symbol, cconv); state.handle_generic_extern_symbol(
call_tid,
extern_symbol,
cconv,
&self.project.runtime_memory_image,
);
} }
} }
...@@ -179,10 +190,13 @@ impl<'a> Context<'a> { ...@@ -179,10 +190,13 @@ impl<'a> Context<'a> {
let (format_string_index, variadic_access_pattern) = self let (format_string_index, variadic_access_pattern) = self
.stubbed_variadic_symbols .stubbed_variadic_symbols
.get(extern_symbol.name.as_str())?; .get(extern_symbol.name.as_str())?;
let format_string_address = state let format_string_address =
.eval_parameter_arg(&extern_symbol.parameters[*format_string_index]) state.eval_parameter_arg(&extern_symbol.parameters[*format_string_index]); // TODO: potential problem: What if the address is now an abstract ID? And how do we handle format strings in writeable memory anyway?
.get_if_absolute_value() let format_string_address = state.substitute_global_mem_address(
.map(|value| value.try_to_bitvec().ok())??; format_string_address,
&self.project.runtime_memory_image,
);
let format_string_address = self.get_global_mem_address(&format_string_address)?;
let format_string = arguments::parse_format_string_destination_and_return_content( let format_string = arguments::parse_format_string_destination_and_return_content(
format_string_address, format_string_address,
&self.project.runtime_memory_image, &self.project.runtime_memory_image,
...@@ -207,11 +221,10 @@ impl<'a> Context<'a> { ...@@ -207,11 +221,10 @@ impl<'a> Context<'a> {
self.project, self.project,
); );
for param in format_string_args { for param in format_string_args {
for id in state let param_value = state.eval_parameter_arg(&param);
.eval_parameter_arg(&param) let param_value = state
.get_relative_values() .substitute_global_mem_address(param_value, &self.project.runtime_memory_image);
.keys() for id in param_value.get_relative_values().keys() {
{
state.merge_access_pattern_of_id(id, variadic_access_pattern); state.merge_access_pattern_of_id(id, variadic_access_pattern);
} }
} }
...@@ -238,16 +251,54 @@ impl<'a> Context<'a> { ...@@ -238,16 +251,54 @@ impl<'a> Context<'a> {
extern_symbol.parameters.len(), extern_symbol.parameters.len(),
cconv.integer_parameter_register.len() - 1, cconv.integer_parameter_register.len() - 1,
] { ] {
for id in state let param = state.get_register(&cconv.integer_parameter_register[index]);
.get_register(&cconv.integer_parameter_register[index]) let param =
.get_relative_values() state.substitute_global_mem_address(param, &self.project.runtime_memory_image);
.keys() for id in param.get_relative_values().keys() {
{
state.merge_access_pattern_of_id(id, variadic_access_pattern); state.merge_access_pattern_of_id(id, variadic_access_pattern);
} }
} }
} }
} }
/// If the given data is either an absolute value or a unique relative value, where the corresponding abstract ID denotes a global memory address,
/// then return the resulting global memory address.
/// If the resulting constant value does not denote a global address then `None` is returned.
///
/// If the data may denote more than one value, then also return `None`.
fn get_global_mem_address(&self, data: &DataDomain<BitvectorDomain>) -> Option<Bitvector> {
if let Some((id, offset)) = data.get_if_unique_target() {
// Check if the relative value is a global memory address (in writeable memory)
if let AbstractLocation::GlobalAddress { address, size: _ } = id.get_location() {
if let Ok(offset_bitvec) = offset.try_to_bitvec() {
let mut global_address = Bitvector::from_u64(*address)
.into_truncate(offset.bytesize())
.ok()?;
global_address += &offset_bitvec;
if self
.project
.runtime_memory_image
.is_global_memory_address(&global_address)
{
return Some(global_address);
}
}
}
} else {
// Global addresses in read-only memory are still handled as absolute values.
let global_address = data
.get_if_absolute_value()
.map(|value| value.try_to_bitvec().ok())??;
if self
.project
.runtime_memory_image
.is_global_memory_address(&global_address)
{
return Some(global_address);
}
}
None
}
} }
impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> { impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
...@@ -266,12 +317,21 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> { ...@@ -266,12 +317,21 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
match &def.term { match &def.term {
Def::Assign { var, value } => { Def::Assign { var, value } => {
new_state.set_read_flag_for_input_ids_of_expression(value); new_state.set_read_flag_for_input_ids_of_expression(value);
new_state.set_register(var, state.eval(value)); let value = new_state.substitute_global_mem_address(
state.eval(value),
&self.project.runtime_memory_image,
);
new_state.set_register(var, value);
} }
Def::Load { var, address } => { Def::Load { var, address } => {
new_state.set_deref_flag_for_input_ids_of_expression(address); new_state.set_deref_flag_for_input_ids_of_expression(address);
let address = new_state.substitute_global_mem_address(
state.eval(address),
&self.project.runtime_memory_image,
);
new_state.set_deref_flag_for_contained_ids(&address);
let value = new_state.load_value( let value = new_state.load_value(
new_state.eval(address), address,
var.size, var.size,
Some(&self.project.runtime_memory_image), Some(&self.project.runtime_memory_image),
); );
...@@ -279,17 +339,23 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> { ...@@ -279,17 +339,23 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
} }
Def::Store { address, value } => { Def::Store { address, value } => {
new_state.set_mutable_deref_flag_for_input_ids_of_expression(address); new_state.set_mutable_deref_flag_for_input_ids_of_expression(address);
if state let address = new_state.substitute_global_mem_address(
.get_offset_if_exact_stack_pointer(&state.eval(address)) state.eval(address),
.is_some() &self.project.runtime_memory_image,
{ );
new_state.set_deref_mut_flag_for_contained_ids(&address);
if state.get_offset_if_exact_stack_pointer(&address).is_some() {
// Only flag inputs of non-trivial expressions as accessed to prevent flagging callee-saved registers as parameters. // Only flag inputs of non-trivial expressions as accessed to prevent flagging callee-saved registers as parameters.
// Sometimes parameter registers are callee-saved (for no apparent reason). // Sometimes parameter registers are callee-saved (for no apparent reason).
new_state.set_read_flag_for_input_ids_of_nontrivial_expression(value); new_state.set_read_flag_for_input_ids_of_nontrivial_expression(value);
} else { } else {
new_state.set_read_flag_for_input_ids_of_expression(value); new_state.set_read_flag_for_input_ids_of_expression(value);
} }
new_state.write_value(new_state.eval(address), new_state.eval(value)); let value = new_state.substitute_global_mem_address(
state.eval(value),
&self.project.runtime_memory_image,
);
new_state.write_value(address, value);
} }
} }
Some(new_state) Some(new_state)
...@@ -332,7 +398,11 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> { ...@@ -332,7 +398,11 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
Jmp::CallInd { target, .. } => { Jmp::CallInd { target, .. } => {
new_state.set_read_flag_for_input_ids_of_expression(target); new_state.set_read_flag_for_input_ids_of_expression(target);
if let Some(cconv) = self.project.get_standard_calling_convention() { if let Some(cconv) = self.project.get_standard_calling_convention() {
new_state.handle_unknown_function_stub(call, cconv); new_state.handle_unknown_function_stub(
call,
cconv,
&self.project.runtime_memory_image,
);
return Some(new_state); return Some(new_state);
} }
} }
...@@ -343,7 +413,11 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> { ...@@ -343,7 +413,11 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
return Some(new_state); return Some(new_state);
} }
} else if let Some(cconv) = self.project.get_standard_calling_convention() { } else if let Some(cconv) = self.project.get_standard_calling_convention() {
new_state.handle_unknown_function_stub(call, cconv); new_state.handle_unknown_function_stub(
call,
cconv,
&self.project.runtime_memory_image,
);
return Some(new_state); return Some(new_state);
} }
} }
...@@ -373,7 +447,7 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> { ...@@ -373,7 +447,7 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
let mut new_state = old_state.clone(); let mut new_state = old_state.clone();
// Merge parameter access patterns with the access patterns from the callee. // Merge parameter access patterns with the access patterns from the callee.
let parameters = callee_state.get_params_of_current_function(); let parameters = callee_state.get_params_of_current_function();
new_state.merge_parameter_access(&parameters); new_state.merge_parameter_access(&parameters, &self.project.runtime_memory_image);
// Compute values for return register (but do not add them to `new_state` yet) // Compute values for return register (but do not add them to `new_state` yet)
let return_value_list = self.compute_return_values_of_call( let return_value_list = self.compute_return_values_of_call(
&mut new_state, &mut new_state,
......
...@@ -112,3 +112,26 @@ fn test_call_stub_handling() { ...@@ -112,3 +112,26 @@ fn test_call_stub_handling() {
); );
assert_eq!(params.len(), 5); assert_eq!(params.len(), 5);
} }
#[test]
fn test_get_global_mem_address() {
let project = Project::mock_arm32();
let graph = crate::analysis::graph::get_program_cfg(&project.program, HashSet::new());
let context = Context::new(&project, &graph);
// Check global address from abstract ID
let global_address_id: DataDomain<BitvectorDomain> = DataDomain::from_target(
AbstractIdentifier::from_global_address(&Tid::new("fn_tid"), &Bitvector::from_i32(0x2000)),
Bitvector::from_i32(0x2).into(),
);
let result = context.get_global_mem_address(&global_address_id);
assert_eq!(result, Some(Bitvector::from_i32(0x2002)));
// Check global address from absolute value
let global_address_const = Bitvector::from_i32(0x2003).into();
let result = context.get_global_mem_address(&global_address_const);
assert_eq!(result, Some(Bitvector::from_i32(0x2003)));
// Check global address not returned if it may not be unique
let value = global_address_id.merge(&global_address_const);
let result = context.get_global_mem_address(&value);
assert!(result.is_none());
}
//! This module implements propagation of global variables via two fixpoint algorithms on the call graph.
//! For more details see [`propagate_globals`].
use super::AccessPattern;
use super::FunctionSignature;
use crate::abstract_domain::AbstractDomain;
use crate::abstract_domain::DomainMap;
use crate::abstract_domain::UnionMergeStrategy;
use crate::analysis::callgraph::get_program_callgraph;
use crate::analysis::callgraph::CallGraph;
use crate::analysis::fixpoint::{Computation, Context};
use crate::intermediate_representation::*;
use std::collections::BTreeMap;
use std::collections::HashSet;
/// The context object for propagating known global variables top-down in the call graph.
struct KnownGlobalsContext<'a> {
/// The call graph of the program.
graph: &'a CallGraph<'a>,
}
impl<'a> KnownGlobalsContext<'a> {
/// Create a new context object.
fn new(graph: &'a CallGraph<'a>) -> Self {
KnownGlobalsContext { graph }
}
}
impl<'a> Context for KnownGlobalsContext<'a> {
type EdgeLabel = &'a Term<Jmp>;
type NodeLabel = Tid;
/// The values at nodes are the sets of known addresses of global variables for that function.
type NodeValue = HashSet<u64>;
/// Get the call graph corresponding to the context object.
fn get_graph(&self) -> &CallGraph<'a> {
self.graph
}
/// The merge function returns the union of the two input sets of global addresses.
fn merge(&self, set1: &HashSet<u64>, set2: &HashSet<u64>) -> HashSet<u64> {
let mut result = set1.clone();
for address in set2 {
result.insert(*address);
}
result
}
/// We always propagate all known addresses of global variables along the edges of the call graph.
fn update_edge(
&self,
globals: &HashSet<u64>,
_edge: petgraph::stable_graph::EdgeIndex,
) -> Option<HashSet<u64>> {
Some(globals.clone())
}
}
/// For each function in the call graph,
/// compute the set of global addresses that are known to the function itself
/// or at least one function that calls this function (either directly or indirectly).
///
/// This is computed via a fixpoint algorithm on the call graph of the program,
/// where known addresses of global variables are propagated top-down along the edges of the call graph.
fn propagate_known_globals_top_down(
project: &Project,
fn_sigs: &BTreeMap<Tid, FunctionSignature>,
) -> BTreeMap<Tid, HashSet<u64>> {
let graph = get_program_callgraph(&project.program);
let context = KnownGlobalsContext::new(&graph);
let mut computation = Computation::new(context, None);
// Set the start values of all nodes
for node in graph.node_indices() {
let fn_tid = &graph[node];
let fn_sig = &fn_sigs[fn_tid];
let globals = fn_sig.global_parameters.keys().cloned().collect();
computation.set_node_value(node, globals);
}
// Propagate top-down in the call graph
computation.compute_with_max_steps(100);
// Generate results map
let mut results = BTreeMap::new();
for node in graph.node_indices() {
let fn_tid = &graph[node];
let propagated_globals = computation.get_node_value(node).unwrap();
results.insert(fn_tid.clone(), propagated_globals.clone());
}
results
}
/// The context object for propagating the access patterns of global variables in the call graph.
struct GlobalsPropagationContext<'a> {
/// The reversed (!) call graph of the program.
graph: &'a CallGraph<'a>,
/// A map from TIDs of functions to the set of known addresses of global variables for that function.
known_globals: &'a BTreeMap<Tid, HashSet<u64>>,
}
impl<'a> GlobalsPropagationContext<'a> {
/// Create a new [`GlobalsPropagationContext`] object.
fn new(graph: &'a CallGraph<'a>, known_globals: &'a BTreeMap<Tid, HashSet<u64>>) -> Self {
GlobalsPropagationContext {
graph,
known_globals,
}
}
}
impl<'a> Context for GlobalsPropagationContext<'a> {
type EdgeLabel = &'a Term<Jmp>;
type NodeLabel = Tid;
/// The node values for the fixpoint comutation
/// are maps from addresses of global variables known to the function represented by the node
/// to the corresponding access pattern of the global variable.
type NodeValue = DomainMap<u64, AccessPattern, UnionMergeStrategy>;
/// Get the (reversed!) call graph corresponding to the program
fn get_graph(&self) -> &CallGraph<'a> {
self.graph
}
/// Merge two maps of known globals by merging the corresponding access patterns.
fn merge(&self, globals1: &Self::NodeValue, globals2: &Self::NodeValue) -> Self::NodeValue {
globals1.merge(globals2)
}
/// Propagate the access patterns of global variables along the edges of the reversed call graph.
///
/// Access patterns are propagated from callees to callers,
/// but only for those global variables, that are also known to the caller.
fn update_edge(
&self,
callee_globals: &Self::NodeValue,
edge: petgraph::stable_graph::EdgeIndex,
) -> Option<Self::NodeValue> {
let (_, target_node) = self.graph.edge_endpoints(edge).unwrap();
let target_tid = &self.graph[target_node];
let caller_known_globals = &self.known_globals[target_tid];
let caller_globals: Self::NodeValue = callee_globals
.iter()
.filter_map(|(address, access_pattern)| {
if caller_known_globals.contains(address) && access_pattern.is_accessed() {
Some((*address, *access_pattern))
} else {
None
}
})
.collect();
Some(caller_globals)
}
}
/// Propagate the access patterns of global variables bottom-up in the call graph.
///
/// Only those global variables (and their access patterns) are propagated,
/// that are known to the caller anyway (i.e. some function upwards in the call graph accesses the global variable).
fn propagate_globals_bottom_up(
project: &Project,
known_globals: &BTreeMap<Tid, HashSet<u64>>,
fn_sigs: &mut BTreeMap<Tid, FunctionSignature>,
) {
// To propagate bottom-up, we have to reverse the edges in the callgraph
let mut graph = get_program_callgraph(&project.program);
graph.reverse();
let context = GlobalsPropagationContext::new(&graph, known_globals);
let mut computation = Computation::new(context, None);
// Set start values for all nodes
for node in graph.node_indices() {
let fn_tid = &graph[node];
let fn_sig = &fn_sigs[fn_tid];
let globals = fn_sig
.global_parameters
.iter()
.filter_map(|(address, access_pattern)| {
if access_pattern.is_accessed() {
Some((*address, *access_pattern))
} else {
None
}
})
.collect();
computation.set_node_value(node, globals);
}
// Compute the fixpoint
computation.compute_with_max_steps(100);
if !computation.has_stabilized() {
panic!("Global parameter propagation algorithm did not stabilize.")
}
// Add the propagated globals to the function signatures
for node in graph.node_indices() {
let fn_tid = &graph[node];
let propagated_globals = computation.get_node_value(node).unwrap();
let fn_globals = &mut fn_sigs.get_mut(fn_tid).unwrap().global_parameters;
for (address, propagated_access_pattern) in propagated_globals.iter() {
fn_globals
.entry(*address)
.and_modify(|access_pattern| {
*access_pattern = access_pattern.merge(propagated_access_pattern);
})
.or_insert(*propagated_access_pattern);
}
}
}
/// Propagate the access patterns of global variables along the edges of the call graph of the given project.
///
/// The propagation works as follows:
/// Global variables and their access patterns are only propagated from callees to callers
/// and only if some function upwards in the call-stack also accesses the corresponding variable.
/// As usual, access patterns are merged if the caller also may access a global variable.
///
/// This propagation scheme is optimized for usage with other bottom-up analyses:
/// - If some callee of a function accesses the same global variable as the function itself,
/// then we need to propagate the corresponding access pattern to the function.
/// This ensures that the function knows which callees may modify the value of the global variable.
/// - If two callees of a function access a global variable,
/// then there is no information flow on the value of the global variable between the callees in a proper bottom-up analysis.
/// But if the function itself (or any of its callers) do not access the global variable,
/// then there is no benefit in tracking its value for the function itself.
/// Thus, the global variable should not be propagated to the function in such a case.
pub fn propagate_globals(project: &Project, fn_sigs: &mut BTreeMap<Tid, FunctionSignature>) {
let known_globals = propagate_known_globals_top_down(project, fn_sigs);
propagate_globals_bottom_up(project, &known_globals, fn_sigs);
}
#[cfg(test)]
pub mod tests {
use std::collections::HashMap;
use super::*;
#[test]
fn test_globals_propagation() {
let mut project = Project::mock_arm32();
// Add 3 functions, so that the call graph will look like this:
// main -> callee1 -> callee2
let mut func = Sub::mock("main");
let mut call_blk = Blk::mock_with_tid("main_blk");
let call = Jmp::mock_call("callee1", None);
call_blk.term.jmps.push(call);
func.term.blocks.push(call_blk);
project.program.term.subs.insert(Tid::new("main"), func);
let mut func = Sub::mock("callee1");
let mut call_blk = Blk::mock_with_tid("callee1_blk");
let call = Jmp::mock_call("callee2", None);
call_blk.term.jmps.push(call);
func.term.blocks.push(call_blk);
project.program.term.subs.insert(Tid::new("callee1"), func);
let func = Sub::mock("callee2");
project.program.term.subs.insert(Tid::new("callee2"), func);
// Add one global var that is known to main and callee2
// and another that is only known to callee1
let mut sig_main = FunctionSignature::new();
sig_main
.global_parameters
.insert(1000, AccessPattern::new().with_read_flag());
let mut sig_callee1 = FunctionSignature::new();
sig_callee1
.global_parameters
.insert(2000, AccessPattern::new().with_dereference_flag());
let mut sig_callee2 = FunctionSignature::new();
sig_callee2
.global_parameters
.insert(1000, AccessPattern::new_unknown_access());
let mut fn_sigs = BTreeMap::from([
(Tid::new("main"), sig_main),
(Tid::new("callee1"), sig_callee1),
(Tid::new("callee2"), sig_callee2),
]);
// Propagate globals
propagate_globals(&project, &mut fn_sigs);
// Check propagation results
assert_eq!(
&fn_sigs[&Tid::new("main")].global_parameters,
&HashMap::from([(1000, AccessPattern::new_unknown_access())])
);
assert_eq!(
&fn_sigs[&Tid::new("callee1")].global_parameters,
&HashMap::from([
(1000, AccessPattern::new_unknown_access()),
(2000, AccessPattern::new().with_dereference_flag())
])
);
assert_eq!(
&fn_sigs[&Tid::new("callee2")].global_parameters,
&HashMap::from([(1000, AccessPattern::new_unknown_access())])
);
}
}
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
//! although only stack accesses with known, constant offset are processed. //! although only stack accesses with known, constant offset are processed.
//! Accesses to potential function parameters are collected together with the type of the access //! 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). //! (is the value read, dereferenced for read access or dereferenced for write access).
//! Accesses to constant addresses that may correspond to global variables are also tracked.
//! //!
//! Known limitations of the analysis: //! Known limitations of the analysis:
//! * The analysis is an overapproximation in the sense that it may generate more input parameters //! * The analysis is an overapproximation in the sense that it may generate more input parameters
...@@ -13,8 +14,9 @@ ...@@ -13,8 +14,9 @@
//! For functions that use other registers //! For functions that use other registers
//! than those in the standard calling convention for parameter passing //! than those in the standard calling convention for parameter passing
//! the results of this analysis will be wrong. //! the results of this analysis will be wrong.
//! * Parameters that are used as input values for variadic functions (e.g. sprintf) may be missed //! * Parameters that are used as input values for variadic functions may be missed.
//! since detection of variadic function parameters is not yet implemented for this analysis. //! Some variadic functions are stubbed, i.e. parameter recognition should work for these.
//! But not all variadic functions are stubbed.
//! * If only a part (e.g. a single byte) of a stack parameter is accessed instead of the whole parameter //! * 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. //! then a duplicate stack parameter may be generated.
//! A proper sanitation for this case is not yet implemented, //! A proper sanitation for this case is not yet implemented,
...@@ -42,6 +44,8 @@ mod state; ...@@ -42,6 +44,8 @@ mod state;
use state::State; use state::State;
mod access_pattern; mod access_pattern;
pub use access_pattern::AccessPattern; pub use access_pattern::AccessPattern;
mod global_var_propagation;
use global_var_propagation::propagate_globals;
pub mod stubs; pub mod stubs;
/// Generate the computation object for the fixpoint computation /// Generate the computation object for the fixpoint computation
...@@ -152,6 +156,8 @@ pub fn compute_function_signatures<'a>( ...@@ -152,6 +156,8 @@ pub fn compute_function_signatures<'a>(
); );
} }
} }
// Propagate globals in bottom-up direction in the call graph
propagate_globals(project, &mut fn_sig_map);
(fn_sig_map, logs) (fn_sig_map, logs)
} }
...@@ -162,6 +168,9 @@ pub fn compute_function_signatures<'a>( ...@@ -162,6 +168,9 @@ pub fn compute_function_signatures<'a>(
pub struct FunctionSignature { pub struct FunctionSignature {
/// The parameters of the function together with their access patterns. /// The parameters of the function together with their access patterns.
pub parameters: HashMap<Arg, AccessPattern>, pub parameters: HashMap<Arg, AccessPattern>,
/// Values in writeable global memory accessed by the function.
/// Does not contain indirectly accessed values, e.g. values accessed by callees of this function.
pub global_parameters: HashMap<u64, AccessPattern>,
} }
impl FunctionSignature { impl FunctionSignature {
...@@ -169,6 +178,7 @@ impl FunctionSignature { ...@@ -169,6 +178,7 @@ impl FunctionSignature {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
parameters: HashMap::new(), parameters: HashMap::new(),
global_parameters: HashMap::new(),
} }
} }
...@@ -186,8 +196,12 @@ impl FunctionSignature { ...@@ -186,8 +196,12 @@ impl FunctionSignature {
stack_params_total_size stack_params_total_size
} }
/// Merge the parameter list of `self` with the given parameter list. /// Merge the parameter list and the global parameter list of `self` with the given lists.
fn merge_parameter_list(&mut self, params: &[(Arg, AccessPattern)]) { fn merge_parameter_lists(
&mut self,
params: &[(Arg, AccessPattern)],
global_params: &[(u64, AccessPattern)],
) {
for (arg, sig_new) in params { for (arg, sig_new) in params {
if let Some(sig_self) = self.parameters.get_mut(arg) { if let Some(sig_self) = self.parameters.get_mut(arg) {
*sig_self = sig_self.merge(sig_new); *sig_self = sig_self.merge(sig_new);
...@@ -195,12 +209,20 @@ impl FunctionSignature { ...@@ -195,12 +209,20 @@ impl FunctionSignature {
self.parameters.insert(arg.clone(), *sig_new); self.parameters.insert(arg.clone(), *sig_new);
} }
} }
for (address, sig_new) in global_params {
if let Some(sig_self) = self.global_parameters.get_mut(address) {
*sig_self = sig_self.merge(sig_new);
} else {
self.global_parameters.insert(*address, *sig_new);
}
}
} }
/// Merge the function signature with the signature extracted from the given state. /// Merge the function signature with the signature extracted from the given state.
fn merge_with_fn_sig_of_state(&mut self, state: &State) { fn merge_with_fn_sig_of_state(&mut self, state: &State) {
let params = state.get_params_of_current_function(); let params = state.get_params_of_current_function();
self.merge_parameter_list(&params); let global_params = state.get_global_mem_params_of_current_function();
self.merge_parameter_lists(&params, &global_params);
} }
/// Sanitize the function signature: /// Sanitize the function signature:
...@@ -269,7 +291,10 @@ pub mod tests { ...@@ -269,7 +291,10 @@ pub mod tests {
write_access_pattern, write_access_pattern,
), ),
]); ]);
FunctionSignature { parameters } FunctionSignature {
parameters,
global_parameters: HashMap::new(),
}
} }
} }
} }
...@@ -195,6 +195,14 @@ impl State { ...@@ -195,6 +195,14 @@ impl State {
} }
} }
/// Add an abstract ID to the set of tracked IDs if it is not already tracked.
/// No access flags are set if the ID was not already tracked.
pub fn add_id_to_tracked_ids(&mut self, id: &AbstractIdentifier) {
if self.tracked_ids.get(id).is_none() {
self.tracked_ids.insert(id.clone(), AccessPattern::new());
}
}
/// Get the value located at a positive stack offset. /// Get the value located at a positive stack offset.
/// ///
/// If no corresponding stack parameter ID exists for the value, /// If no corresponding stack parameter ID exists for the value,
...@@ -308,30 +316,75 @@ impl State { ...@@ -308,30 +316,75 @@ impl State {
} }
} }
/// Set the read and dereferenced flag for every ID /// Set the read and dereferenced flag for every tracked ID
/// that may be referenced when computing the value of the expression. /// that may be referenced when computing the value of the expression.
pub fn set_deref_flag_for_input_ids_of_expression(&mut self, expression: &Expression) { pub fn set_deref_flag_for_input_ids_of_expression(&mut self, expression: &Expression) {
for register in expression.input_vars() { for register in expression.input_vars() {
for id in self.get_register(register).referenced_ids() { self.set_deref_flag_for_contained_ids(&self.get_register(register));
}
}
/// Set the read and mutably dereferenced flag for every tracked ID
/// that may be referenced when computing the value of the expression.
pub fn set_mutable_deref_flag_for_input_ids_of_expression(&mut self, expression: &Expression) {
for register in expression.input_vars() {
self.set_deref_mut_flag_for_contained_ids(&self.get_register(register));
}
}
/// Set the read and dereferenced flag for every tracked ID contained in the given value.
pub fn set_deref_flag_for_contained_ids(&mut self, value: &DataDomain<BitvectorDomain>) {
for id in value.referenced_ids() {
if let Some(object) = self.tracked_ids.get_mut(id) { if let Some(object) = self.tracked_ids.get_mut(id) {
object.set_read_flag(); object.set_read_flag();
object.set_dereference_flag(); object.set_dereference_flag();
} }
} }
} }
}
/// Set the read and mutably dereferenced flag for every ID /// Set the read and mutably dereferenced flag for every tracked ID contained in the given value.
/// that may be referenced when computing the value of the expression. pub fn set_deref_mut_flag_for_contained_ids(&mut self, value: &DataDomain<BitvectorDomain>) {
pub fn set_mutable_deref_flag_for_input_ids_of_expression(&mut self, expression: &Expression) { for id in value.referenced_ids() {
for register in expression.input_vars() {
for id in self.get_register(register).referenced_ids() {
if let Some(object) = self.tracked_ids.get_mut(id) { if let Some(object) = self.tracked_ids.get_mut(id) {
object.set_read_flag(); object.set_read_flag();
object.set_mutably_dereferenced_flag(); object.set_mutably_dereferenced_flag();
} }
} }
} }
/// If the absolute value part of the given value might represent an address into writeable global memory
/// then substitute it by a relative value relative to a new global memory ID.
///
/// The generated ID will be also added to the tracked IDs of `self`.
/// However, no access flags will be set for the newly generated ID.
pub fn substitute_global_mem_address(
&mut self,
mut value: DataDomain<BitvectorDomain>,
global_memory: &RuntimeMemoryImage,
) -> DataDomain<BitvectorDomain> {
if value.bytesize() != self.stack_id.bytesize() {
// Only pointer-sized values can represent global addresses.
return value;
} else if let Some(absolute_value) = value.get_absolute_value() {
if let Ok(bitvec) = absolute_value.try_to_bitvec() {
if let Ok(true) = global_memory.is_address_writeable(&bitvec) {
// The absolute value might be a pointer to global memory.
let global_id = AbstractIdentifier::from_global_address(
self.get_current_function_tid(),
&bitvec,
);
// Add the ID to the set of tracked IDs for the state.
self.add_id_to_tracked_ids(&global_id);
// Convert the absolute value to a relative value (relative the new global ID).
value = value.merge(&DataDomain::from_target(
global_id,
Bitvector::zero(value.bytesize().into()).into(),
));
value.set_absolute_value(None);
}
}
}
value
} }
} }
......
...@@ -10,8 +10,9 @@ impl State { ...@@ -10,8 +10,9 @@ impl State {
call_tid: &Tid, call_tid: &Tid,
extern_symbol: &ExternSymbol, extern_symbol: &ExternSymbol,
calling_convention: &CallingConvention, calling_convention: &CallingConvention,
global_memory: &RuntimeMemoryImage,
) { ) {
let input_ids = self.collect_input_ids_of_call(&extern_symbol.parameters); let input_ids = self.collect_input_ids_of_call(&extern_symbol.parameters, global_memory);
self.clear_non_callee_saved_register(&calling_convention.callee_saved_register); self.clear_non_callee_saved_register(&calling_convention.callee_saved_register);
self.generate_return_values_for_call(&input_ids, &extern_symbol.return_values, call_tid); self.generate_return_values_for_call(&input_ids, &extern_symbol.return_values, call_tid);
} }
...@@ -26,6 +27,7 @@ impl State { ...@@ -26,6 +27,7 @@ impl State {
&mut self, &mut self,
call: &Term<Jmp>, call: &Term<Jmp>,
calling_convention: &CallingConvention, calling_convention: &CallingConvention,
global_memory: &RuntimeMemoryImage,
) { ) {
let mut parameters = let mut parameters =
generate_args_from_registers(&calling_convention.integer_parameter_register); generate_args_from_registers(&calling_convention.integer_parameter_register);
...@@ -43,22 +45,24 @@ impl State { ...@@ -43,22 +45,24 @@ impl State {
data_type: None, data_type: None,
}); });
} }
let input_ids = self.collect_input_ids_of_call(&parameters); let input_ids = self.collect_input_ids_of_call(&parameters, global_memory);
self.clear_non_callee_saved_register(&calling_convention.callee_saved_register); self.clear_non_callee_saved_register(&calling_convention.callee_saved_register);
self.generate_return_values_for_call(&input_ids, &return_register, &call.tid); self.generate_return_values_for_call(&input_ids, &return_register, &call.tid);
} }
/// Get all input IDs referenced in the parameters of a call. /// Get all input IDs referenced in the parameters of a call.
/// Marks every input ID as accessed (with access flags for unknown access) /// Marks every input ID as accessed (with access flags for unknown access).
/// and generates stack parameter IDs for the current function if necessary. /// Also generates stack parameter IDs and global memory IDs for the current function if necessary.
fn collect_input_ids_of_call(&mut self, parameters: &[Arg]) -> BTreeSet<AbstractIdentifier> { fn collect_input_ids_of_call(
&mut self,
parameters: &[Arg],
global_memory: &RuntimeMemoryImage,
) -> BTreeSet<AbstractIdentifier> {
let mut input_ids = BTreeSet::new(); let mut input_ids = BTreeSet::new();
for input_param in parameters { for input_param in parameters {
for (id, offset) in self let param = self.eval_parameter_arg(input_param);
.eval_parameter_arg(input_param) let param = self.substitute_global_mem_address(param, global_memory);
.get_relative_values() for (id, offset) in param.get_relative_values() {
.iter()
{
input_ids.insert(id.clone()); input_ids.insert(id.clone());
// If the relative value points to the stack we also have to collect all IDs contained in the pointed-to value. // If the relative value points to the stack we also have to collect all IDs contained in the pointed-to value.
if *id == self.stack_id { if *id == self.stack_id {
...@@ -131,29 +135,55 @@ impl State { ...@@ -131,29 +135,55 @@ impl State {
let mut params = Vec::new(); let mut params = Vec::new();
for (id, access_pattern) in self.tracked_ids.iter() { for (id, access_pattern) in self.tracked_ids.iter() {
if id.get_tid() == self.get_current_function_tid() { if id.get_tid() == self.get_current_function_tid() {
if let Ok(param_arg) = generate_param_arg_from_abstract_id(id) {
if access_pattern.is_accessed() { if access_pattern.is_accessed() {
params.push((generate_arg_from_abstract_id(id), *access_pattern)); params.push((param_arg, *access_pattern));
} else if matches!(id.get_location(), &AbstractLocation::Pointer { .. }) { } else if matches!(id.get_location(), &AbstractLocation::Pointer { .. }) {
// This is a stack parameter. // This is a stack parameter.
// If it was only loaded into a register but otherwise not used, then the read-flag needs to be set. // If it was only loaded into a register but otherwise not used, then the read-flag needs to be set.
let mut access_pattern = *access_pattern; let mut access_pattern = *access_pattern;
access_pattern.set_read_flag(); access_pattern.set_read_flag();
params.push((generate_arg_from_abstract_id(id), access_pattern)); params.push((param_arg, access_pattern));
}
} }
} }
} }
params params
} }
/// Return a list of all potential global memory addresses
/// for which any type of access has been tracked by the current state.
pub fn get_global_mem_params_of_current_function(&self) -> Vec<(u64, AccessPattern)> {
let mut global_params = Vec::new();
for (id, access_pattern) in self.tracked_ids.iter() {
if id.get_tid() == self.get_current_function_tid() && access_pattern.is_accessed() {
match id.get_location() {
AbstractLocation::GlobalPointer(address, _)
| AbstractLocation::GlobalAddress { address, .. } => {
global_params.push((*address, *access_pattern));
}
AbstractLocation::Pointer(_, _) | AbstractLocation::Register(_) => (),
}
}
}
global_params
}
/// Merges the access patterns of callee parameters with those of the caller (represented by `self`). /// Merges the access patterns of callee parameters with those of the caller (represented by `self`).
/// The result represents the access patterns after returning to the caller and is written to `self`. /// The result represents the access patterns after returning to the caller and is written to `self`.
/// ///
/// If a parameter is a pointer to the stack frame of self, it is dereferenced /// If a parameter is a pointer to the stack frame of self, it is dereferenced
/// to set the access patterns of the target. /// to set the access patterns of the target.
/// Note that this may create new stack parameter objects for self. /// Note that this may create new stack parameter objects for self.
pub fn merge_parameter_access(&mut self, params: &[(Arg, AccessPattern)]) { pub fn merge_parameter_access(
&mut self,
params: &[(Arg, AccessPattern)],
global_memory: &RuntimeMemoryImage,
) {
for (parameter, call_access_pattern) in params { for (parameter, call_access_pattern) in params {
for (id, offset) in self.eval_parameter_arg(parameter).get_relative_values() { let param_value = self.eval_parameter_arg(parameter);
let param_value = self.substitute_global_mem_address(param_value, global_memory);
for (id, offset) in param_value.get_relative_values() {
if let Some(object) = self.tracked_ids.get_mut(id) { if let Some(object) = self.tracked_ids.get_mut(id) {
*object = object.merge(call_access_pattern); *object = object.merge(call_access_pattern);
} }
...@@ -188,7 +218,7 @@ impl State { ...@@ -188,7 +218,7 @@ impl State {
/// then return an argument object corresponding to the parameter. /// then return an argument object corresponding to the parameter.
pub fn get_arg_corresponding_to_id(&self, id: &AbstractIdentifier) -> Option<Arg> { pub fn get_arg_corresponding_to_id(&self, id: &AbstractIdentifier) -> Option<Arg> {
if id.get_tid() == self.stack_id.get_tid() { if id.get_tid() == self.stack_id.get_tid() {
Some(generate_arg_from_abstract_id(id)) generate_param_arg_from_abstract_id(id).ok()
} else { } else {
None None
} }
...@@ -205,19 +235,23 @@ fn generate_args_from_registers(registers: &[Variable]) -> Vec<Arg> { ...@@ -205,19 +235,23 @@ fn generate_args_from_registers(registers: &[Variable]) -> Vec<Arg> {
/// Generate an argument representing the location in the given abstract ID. /// Generate an argument representing the location in the given abstract ID.
/// If the location is a pointer, it is assumed that the pointer points to the stack. /// If the location is a pointer, it is assumed that the pointer points to the stack.
/// Panics if the location contains a second level of indirection. /// Returns an error if the location contains a second level of indirection
fn generate_arg_from_abstract_id(id: &AbstractIdentifier) -> Arg { /// or if the location is associated to global memory.
fn generate_param_arg_from_abstract_id(id: &AbstractIdentifier) -> Result<Arg, Error> {
match id.get_location() { match id.get_location() {
AbstractLocation::Register(var) => Arg::from_var(var.clone(), None), AbstractLocation::Register(var) => Ok(Arg::from_var(var.clone(), None)),
AbstractLocation::Pointer(var, mem_location) => match mem_location { AbstractLocation::Pointer(var, mem_location) => match mem_location {
AbstractMemoryLocation::Location { offset, size } => Arg::Stack { AbstractMemoryLocation::Location { offset, size } => Ok(Arg::Stack {
address: Expression::Var(var.clone()).plus_const(*offset), address: Expression::Var(var.clone()).plus_const(*offset),
size: *size, size: *size,
data_type: None, data_type: None,
}, }),
AbstractMemoryLocation::Pointer { .. } => { AbstractMemoryLocation::Pointer { .. } => {
panic!("Memory location is not a stack offset.") Err(anyhow!("Memory location is not a stack offset."))
} }
}, },
AbstractLocation::GlobalAddress { .. } | AbstractLocation::GlobalPointer(_, _) => {
Err(anyhow!("Global values are not parameters."))
}
} }
} }
...@@ -150,7 +150,12 @@ fn test_extern_symbol_handling() { ...@@ -150,7 +150,12 @@ fn test_extern_symbol_handling() {
let return_val_id = let return_val_id =
AbstractIdentifier::from_var(Tid::new("call_tid"), &Variable::mock("r0", 4)); AbstractIdentifier::from_var(Tid::new("call_tid"), &Variable::mock("r0", 4));
// Test extern symbol handling. // Test extern symbol handling.
state.handle_generic_extern_symbol(&call_tid, &extern_symbol, &cconv); state.handle_generic_extern_symbol(
&call_tid,
&extern_symbol,
&cconv,
&RuntimeMemoryImage::mock(),
);
assert_eq!( assert_eq!(
state state
.tracked_ids .tracked_ids
...@@ -177,3 +182,30 @@ fn test_extern_symbol_handling() { ...@@ -177,3 +182,30 @@ fn test_extern_symbol_handling() {
&Bitvector::from_i32(0).into() &Bitvector::from_i32(0).into()
); );
} }
#[test]
fn test_substitute_global_mem_address() {
let mut state = State::mock_arm32();
let global_memory = RuntimeMemoryImage::mock();
// Test that addresses into non-writeable memory do not get substituted.
let global_address: DataDomain<BitvectorDomain> = Bitvector::from_i32(0x1000).into();
let substituted_address =
state.substitute_global_mem_address(global_address.clone(), &global_memory);
assert_eq!(global_address, substituted_address);
// Test substitution for addresses into writeable global memory.
let global_address: DataDomain<BitvectorDomain> = Bitvector::from_i32(0x2000).into();
let substituted_address = state.substitute_global_mem_address(global_address, &global_memory);
let expected_global_id = AbstractIdentifier::from_global_address(
state.get_current_function_tid(),
&Bitvector::from_i32(0x2000),
);
assert_eq!(
state.tracked_ids.get(&expected_global_id),
Some(&AccessPattern::new())
);
assert_eq!(
substituted_address,
DataDomain::from_target(expected_global_id, Bitvector::from_i32(0).into())
);
}
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
//! as well as analyses depending on these modules. //! as well as analyses depending on these modules.
pub mod backward_interprocedural_fixpoint; pub mod backward_interprocedural_fixpoint;
pub mod callgraph;
pub mod dead_variable_elimination; pub mod dead_variable_elimination;
pub mod expression_propagation; pub mod expression_propagation;
pub mod fixpoint; pub mod fixpoint;
......
...@@ -92,3 +92,23 @@ impl fmt::Display for Jmp { ...@@ -92,3 +92,23 @@ impl fmt::Display for Jmp {
} }
} }
} }
#[cfg(test)]
pub mod tests {
use super::*;
impl Jmp {
/// Create a mock call to a TID with the given `target` and `return_`
/// as the names of the target and return TIDs.
pub fn mock_call(target: &str, return_: Option<&str>) -> Term<Jmp> {
let call = Jmp::Call {
target: Tid::new(target.to_string()),
return_: return_.map(|tid_name| Tid::new(tid_name)),
};
Term {
tid: Tid::new(format!("call_{}", target.to_string())),
term: call,
}
}
}
}
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