use crate::backend::Backend;
use crate::demangling;
use crate::error::*;
use crate::hooks;
use crate::return_value::*;
use crate::state::State;
use either::Either;
use llvm_ir::function::{CallingConvention, FunctionAttribute, ParameterAttribute};
use llvm_ir::types::Typed;
use llvm_ir::{instruction::InlineAssembly, Name, Operand, Type};
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::rc::Rc;
#[derive(Clone)]
pub struct FunctionHooks<'p, B: Backend + 'p> {
hooks: HashMap<String, FunctionHook<'p, B>>,
cpp_demangled_hooks: HashMap<String, FunctionHook<'p, B>>,
rust_demangled_hooks: HashMap<String, FunctionHook<'p, B>>,
inline_asm_hook: Option<FunctionHook<'p, B>>,
default_hook: Option<FunctionHook<'p, B>>,
cur_id: usize,
}
pub type Argument = (Operand, Vec<ParameterAttribute>);
pub trait IsCall: Typed {
fn get_called_func(&self) -> &Either<InlineAssembly, Operand>;
fn get_arguments(&self) -> &Vec<Argument>;
fn get_return_attrs(&self) -> &Vec<ParameterAttribute>;
fn get_fn_attrs(&self) -> &Vec<FunctionAttribute>;
fn get_calling_convention(&self) -> CallingConvention;
}
impl IsCall for llvm_ir::instruction::Call {
fn get_called_func(&self) -> &Either<InlineAssembly, Operand> {
&self.function
}
fn get_arguments(&self) -> &Vec<Argument> {
&self.arguments
}
fn get_return_attrs(&self) -> &Vec<ParameterAttribute> {
&self.return_attributes
}
fn get_fn_attrs(&self) -> &Vec<FunctionAttribute> {
&self.function_attributes
}
fn get_calling_convention(&self) -> CallingConvention {
self.calling_convention
}
}
impl IsCall for llvm_ir::terminator::Invoke {
fn get_called_func(&self) -> &Either<InlineAssembly, Operand> {
&self.function
}
fn get_arguments(&self) -> &Vec<Argument> {
&self.arguments
}
fn get_return_attrs(&self) -> &Vec<ParameterAttribute> {
&self.return_attributes
}
fn get_fn_attrs(&self) -> &Vec<FunctionAttribute> {
&self.function_attributes
}
fn get_calling_convention(&self) -> CallingConvention {
self.calling_convention
}
}
impl<'p, B: Backend + 'p> FunctionHooks<'p, B> {
pub fn new() -> Self {
Self {
hooks: HashMap::new(),
cpp_demangled_hooks: HashMap::new(),
rust_demangled_hooks: HashMap::new(),
inline_asm_hook: None,
default_hook: None,
cur_id: 0,
}
}
pub fn add<H>(&mut self, hooked_function: impl Into<String>, hook: &'p H)
where
H: Fn(&mut State<'p, B>, &'p dyn IsCall) -> Result<ReturnValue<B::BV>>,
{
self.hooks
.insert(hooked_function.into(), FunctionHook::new(self.cur_id, hook));
self.cur_id += 1;
}
pub fn add_cpp_demangled<H>(&mut self, hooked_function: impl Into<String>, hook: &'p H)
where
H: Fn(&mut State<'p, B>, &'p dyn IsCall) -> Result<ReturnValue<B::BV>>,
{
self.cpp_demangled_hooks
.insert(hooked_function.into(), FunctionHook::new(self.cur_id, hook));
self.cur_id += 1;
}
pub fn add_rust_demangled<H>(&mut self, hooked_function: impl Into<String>, hook: &'p H)
where
H: Fn(&mut State<'p, B>, &'p dyn IsCall) -> Result<ReturnValue<B::BV>>,
{
self.rust_demangled_hooks
.insert(hooked_function.into(), FunctionHook::new(self.cur_id, hook));
self.cur_id += 1;
}
pub fn add_inline_asm_hook<H>(&mut self, hook: &'p H) -> bool
where
H: Fn(&mut State<'p, B>, &'p dyn IsCall) -> Result<ReturnValue<B::BV>>,
{
match &mut self.inline_asm_hook {
h @ Some(_) => {
*h = Some(FunctionHook::new(self.cur_id, hook));
self.cur_id += 1;
true
},
h @ None => {
*h = Some(FunctionHook::new(self.cur_id, hook));
self.cur_id += 1;
false
},
}
}
pub fn add_default_hook<H>(&mut self, hook: &'p H) -> bool
where
H: Fn(&mut State<'p, B>, &'p dyn IsCall) -> Result<ReturnValue<B::BV>>,
{
match &mut self.default_hook {
h @ Some(_) => {
*h = Some(FunctionHook::new(self.cur_id, hook));
self.cur_id += 1;
true
},
h @ None => {
*h = Some(FunctionHook::new(self.cur_id, hook));
self.cur_id += 1;
false
},
}
}
pub fn remove(&mut self, hooked_function: &str) {
self.hooks.remove(hooked_function);
}
pub fn remove_cpp_demangled(&mut self, hooked_function: &str) {
self.cpp_demangled_hooks.remove(hooked_function);
}
pub fn remove_rust_demangled(&mut self, hooked_function: &str) {
self.rust_demangled_hooks.remove(hooked_function);
}
pub fn remove_inline_asm_hook(&mut self) {
self.inline_asm_hook = None;
}
pub fn remove_default_hook(&mut self) {
self.default_hook = None;
}
pub(crate) fn get_all_hooks(&self) -> impl Iterator<Item = (&String, &FunctionHook<'p, B>)> {
self.hooks
.iter()
.chain(self.cpp_demangled_hooks.iter())
.chain(self.rust_demangled_hooks.iter())
}
pub(crate) fn get_hook_for(&self, funcname: &str) -> Option<&FunctionHook<'p, B>> {
self.hooks
.get(funcname)
.or_else(|| {
demangling::try_rust_demangle(funcname)
.and_then(|demangled| self.rust_demangled_hooks.get(&demangled))
})
.or_else(|| {
demangling::try_cpp_demangle(funcname)
.and_then(|demangled| self.cpp_demangled_hooks.get(&demangled))
})
}
pub(crate) fn get_inline_asm_hook(&self) -> Option<&FunctionHook<'p, B>> {
self.inline_asm_hook.as_ref()
}
pub(crate) fn get_default_hook(&self) -> Option<&FunctionHook<'p, B>> {
self.default_hook.as_ref()
}
pub fn is_hooked(&self, funcname: &str) -> bool {
self.get_hook_for(funcname).is_some()
}
pub fn has_inline_asm_hook(&self) -> bool {
self.inline_asm_hook.is_some()
}
pub fn has_default_hook(&self) -> bool {
self.default_hook.is_some()
}
}
impl<'p, B: Backend + 'p> Default for FunctionHooks<'p, B> {
fn default() -> Self {
let mut fhooks = Self::new();
fhooks.add("malloc", &hooks::allocation::malloc_hook);
fhooks.add("calloc", &hooks::allocation::calloc_hook);
fhooks.add("realloc", &hooks::allocation::realloc_hook);
fhooks.add("free", &hooks::allocation::free_hook);
fhooks.add(
"__cxa_allocate_exception",
&hooks::exceptions::cxa_allocate_exception,
);
fhooks.add("__cxa_throw", &hooks::exceptions::cxa_throw);
fhooks.add("__cxa_begin_catch", &hooks::exceptions::cxa_begin_catch);
fhooks.add("__cxa_end_catch", &hooks::exceptions::cxa_end_catch);
fhooks.add("llvm.eh.typeid.for", &hooks::exceptions::llvm_eh_typeid_for);
fhooks.add("exit", &abort_hook);
fhooks.add_rust_demangled("std::panicking::begin_panic", &abort_hook);
fhooks.add_rust_demangled("std::panicking::begin_panic_fmt", &abort_hook);
fhooks.add_rust_demangled("std::panicking::begin_panic_handler", &abort_hook);
fhooks.add_rust_demangled("core::panicking::panic", &abort_hook);
fhooks.add_rust_demangled("core::panicking::panic_bounds_check", &abort_hook);
fhooks.add_rust_demangled("core::result::unwrap_failed", &abort_hook);
fhooks.add_rust_demangled("core::slice::slice_index_len_fail", &abort_hook);
fhooks.add_rust_demangled("core::slice::slice_index_order_fail", &abort_hook);
fhooks.add_rust_demangled("core::slice::slice_index_overflow_fail", &abort_hook);
fhooks
}
}
pub(crate) struct FunctionHook<'p, B: Backend> {
#[allow(clippy::type_complexity)]
hook: Rc<dyn Fn(&mut State<'p, B>, &'p dyn IsCall) -> Result<ReturnValue<B::BV>> + 'p>,
id: usize,
}
impl<'p, B: Backend> Clone for FunctionHook<'p, B> {
fn clone(&self) -> Self {
Self {
hook: self.hook.clone(),
id: self.id,
}
}
}
impl<'p, B: Backend> PartialEq for FunctionHook<'p, B> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl<'p, B: Backend> Eq for FunctionHook<'p, B> {}
impl<'p, B: Backend> Hash for FunctionHook<'p, B> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl<'p, B: Backend> FunctionHook<'p, B> {
pub fn new(
id: usize,
f: &'p dyn Fn(&mut State<'p, B>, &'p dyn IsCall) -> Result<ReturnValue<B::BV>>,
) -> Self {
Self {
hook: Rc::new(f),
id,
}
}
pub fn call_hook(
&self,
state: &mut State<'p, B>,
call: &'p dyn IsCall,
) -> Result<ReturnValue<B::BV>> {
(self.hook)(state, call)
}
}
pub fn generic_stub_hook<B: Backend>(
state: &mut State<B>,
call: &dyn IsCall,
) -> Result<ReturnValue<B::BV>> {
match state.type_of(call).as_ref() {
Type::VoidType => Ok(ReturnValue::ReturnVoid),
ty => {
let width = state.size_in_bits(ty).ok_or_else(|| {
Error::OtherError("Call return type is an opaque named struct".into())
})?;
assert_ne!(width, 0, "Call return type has size 0 bits but isn't void type"); let bv = state.new_bv_with_name(Name::from("generic_stub_hook_retval"), width)?;
Ok(ReturnValue::Return(bv))
},
}
}
pub fn abort_hook<B: Backend>(
_state: &mut State<B>,
_call: &dyn IsCall,
) -> Result<ReturnValue<B::BV>> {
Ok(ReturnValue::Abort)
}