use std::ffi::c_int;
use idakit_sys as sys;
use serde::{Deserialize, Serialize};
use crate::Database;
use crate::address::Address;
use crate::bitness::Bitness;
use crate::decompiler::DecompiledFunction;
use crate::decompiler::ctree::Ctree;
use crate::error::{Error, Result};
use crate::ffi::nul_checked;
use crate::flowchart::{FlowChart, flowchart_flags};
use crate::instruction::Instructions;
use crate::location::{LocationMut, PendingInvalidation};
use crate::stack::StackFrame;
use crate::types::{Type, TypeExpr, TypeInfo, walk_type};
use crate::xref::Xrefs;
mod signature;
pub use signature::CallingConvention;
use signature::sig_result;
impl Database {
#[inline]
#[must_use]
#[doc(alias("get_func"))]
pub fn function(&self, address: Address) -> Function<'_> {
Function::new(address, self)
}
#[inline]
#[must_use]
#[doc(alias("get_func"))]
pub fn function_at(&self, address: Address) -> Option<Function<'_>> {
let entry = Address::try_new(self.func_start(address))?;
Some(Function::new(entry, self))
}
#[inline]
#[must_use]
#[doc(alias("get_func_qty"))]
pub fn functions(&self) -> Functions<'_> {
Functions::new(self)
}
#[inline]
#[must_use]
#[doc(alias("get_func"))]
pub fn function_mut(&mut self, address: Address) -> Option<FunctionEdit<'_>> {
let entry = Address::try_new(self.func_start(address))?;
Some(FunctionEdit {
inner: self.at_mut(entry),
})
}
pub fn with_function_mut<R>(
&mut self,
address: Address,
f: impl FnOnce(&mut FunctionEdit<'_>) -> R,
) -> Option<R> {
let mut cursor = self.function_mut(address)?;
Some(f(&mut cursor))
}
}
#[derive(Clone, Copy)]
#[doc(alias("func_t"))]
pub struct Function<'db> {
address: Address,
db: &'db Database,
}
impl<'db> Function<'db> {
#[inline]
pub(crate) fn new(address: Address, db: &'db Database) -> Self {
Self { address, db }
}
#[inline]
#[must_use]
pub const fn address(&self) -> Address {
self.address
}
#[must_use]
#[doc(alias("get_func_name"))]
pub fn name(&self) -> FunctionName {
let text = self.db.func_name(self.address).unwrap_or_default();
FunctionName::from_flags(self.db.get_flags(self.address), text)
}
#[must_use]
#[doc(alias("print_type"))]
pub fn prototype(&self) -> Option<String> {
self.db.func_type(self.address)
}
#[doc(alias("get_tinfo"))]
pub fn prototype_type(&self) -> Result<Option<Type>> {
crate::claim::ensure_kernel_thread();
walk_type(|sink| sys::walk_func_type(self.address.get(), sink)).map_err(|source| {
Error::Extract {
address: self.address.get(),
source,
}
})
}
#[must_use]
pub fn chunks(&self) -> FunctionChunks {
FunctionChunks::new(self.address, self.db)
}
#[must_use]
#[doc(alias("calc_func_size"))]
pub fn total_size(&self) -> u64 {
self.chunks()
.map(|chunk| chunk.start.distance_to(chunk.end))
.sum()
}
#[must_use]
#[doc(alias("get_func_cmt"))]
pub fn comment(&self, repeatable: bool) -> Option<String> {
self.db.func_cmt(self.address, repeatable)
}
#[must_use]
#[doc(alias("func_does_return"))]
pub fn does_return(&self) -> bool {
self.db.func_does_return(self.address)
}
#[must_use]
#[doc(alias("get_func_bitness"))]
pub fn bitness(&self) -> Option<Bitness> {
Bitness::try_from_bits(self.db.func_bitness(self.address).max(0) as u8)
}
#[must_use]
pub fn instructions(&self) -> Instructions<'db> {
Instructions::new(self.db, self.address)
}
#[must_use]
#[doc(alias("end_ea"))]
pub fn end(&self) -> Option<Address> {
Address::try_new(self.db.func_end(self.address))
}
#[must_use]
pub fn size(&self) -> u64 {
self.end().map_or(0, |end| self.address.distance_to(end))
}
#[must_use]
#[doc(alias("FUNC_LIB"))]
pub fn is_lib(&self) -> bool {
sys::FuncFlags::from_bits_retain(self.db.func_flags(self.address))
.contains(sys::FuncFlags::LIB)
}
#[must_use]
#[doc(alias("FUNC_THUNK"))]
pub fn is_thunk(&self) -> bool {
sys::FuncFlags::from_bits_retain(self.db.func_flags(self.address))
.contains(sys::FuncFlags::THUNK)
}
#[must_use]
#[doc(alias("FUNC_NORET"))]
pub fn is_noreturn(&self) -> bool {
sys::FuncFlags::from_bits_retain(self.db.func_flags(self.address))
.contains(sys::FuncFlags::NORET)
}
#[must_use]
#[doc(alias("xrefblk_t", "first_to"))]
pub fn xrefs_to(&self) -> Xrefs {
self.db.xrefs_to(self.address)
}
#[must_use]
#[doc(alias("xrefblk_t", "first_from"))]
pub fn xrefs_from(&self) -> Xrefs {
self.db.xrefs_from(self.address)
}
#[doc(alias("decompile_func"))]
pub fn decompile(&self) -> Result<DecompiledFunction<'db>> {
self.db.decompile(self.address)
}
pub fn ctree(&self) -> Result<Ctree> {
let cfunc = self.decompile()?;
cfunc.ctree().map_err(|source| Error::Extract {
address: self.address.get(),
source,
})
}
#[doc(alias("get_func_frame"))]
pub fn frame(&self) -> Result<Option<StackFrame>> {
self.db.frame(self.address)
}
#[must_use]
pub fn snapshot(&self) -> FunctionSnapshot {
FunctionSnapshot {
address: self.address,
name: self.name(),
prototype: self.prototype(),
}
}
}
#[bon::bon]
impl<'db> Function<'db> {
#[doc(alias("qflow_chart_t"))]
pub fn flowchart(&self) -> Result<FlowChart> {
self.db.flowchart(self.address)
}
#[builder]
#[doc(alias("qflow_chart_t"))]
pub fn flowchart_with(
&self,
#[builder(default = false)] call_ends: bool,
#[builder(default = true)] externals: bool,
#[builder(default = true)] predecessors: bool,
) -> Result<FlowChart> {
self.db.build_flowchart(
self.address,
flowchart_flags(call_ends, externals, predecessors),
)
}
}
pub struct FunctionEdit<'db> {
inner: LocationMut<'db>,
}
impl FunctionEdit<'_> {
#[inline]
#[must_use]
pub const fn address(&self) -> Address {
self.inner.address()
}
#[must_use]
#[doc(alias("get_func_name"))]
pub fn name(&self) -> FunctionName {
self.inner.db().function(self.inner.address()).name()
}
#[must_use]
#[doc(alias("print_type"))]
pub fn prototype(&self) -> Option<String> {
self.inner.db().function(self.inner.address()).prototype()
}
#[must_use]
#[doc(alias("end_ea"))]
pub fn end(&self) -> Option<Address> {
self.inner.db().function(self.inner.address()).end()
}
#[must_use]
pub fn auto_invalidate(mut self, on: bool) -> Self {
self.inner = self.inner.auto_invalidate(on);
self
}
#[doc(alias("set_name"))]
pub fn rename(&mut self, name: impl AsRef<str>) -> Result<()> {
self.inner.rename(name)
}
#[doc(alias("apply_tinfo"))]
pub fn set_type(&mut self, ty: impl Into<TypeExpr>) -> Result<()> {
self.inner.set_type(ty)
}
#[doc(alias("apply_tinfo"))]
pub fn apply_type(&mut self, ty: &TypeInfo) -> Result<()> {
self.inner.apply_type(ty)
}
#[doc(alias("del_tinfo"))]
pub fn clear_type(&mut self) -> Result<()> {
self.inner.clear_type()
}
#[doc(alias("get_func_details", "create_func"))]
pub fn set_return_type(&mut self, ret: impl Into<TypeExpr>) -> Result<()> {
let entry = self.inner.address();
let recipe = ret.into().checked_serialize()?;
let result = self.inner.db_mut().func_set_rettype(entry, &recipe);
let out = sig_result(result.code, entry, None, &result.reason);
self.inner.queued(out, PendingInvalidation::Dependents)
}
#[doc(alias("get_func_details", "create_func"))]
pub fn set_arg_type(&mut self, index: usize, ty: impl Into<TypeExpr>) -> Result<()> {
let entry = self.inner.address();
let recipe = ty.into().checked_serialize()?;
let result = self.inner.db_mut().func_set_argtype(entry, index, &recipe);
let out = sig_result(
result.code,
entry,
Some((index, result.arity)),
&result.reason,
);
self.inner.queued(out, PendingInvalidation::Dependents)
}
#[doc(alias("get_func_details", "create_func"))]
pub fn rename_arg(&mut self, index: usize, name: impl AsRef<str>) -> Result<()> {
let entry = self.inner.address();
let name = nul_checked(name.as_ref(), "name")?;
let result = self.inner.db_mut().func_rename_arg(entry, index, name);
let out = sig_result(
result.code,
entry,
Some((index, result.arity)),
&result.reason,
);
self.inner.queued(out, PendingInvalidation::SelfOnly)
}
#[doc(alias("set_cc"))]
pub fn set_calling_convention(&mut self, cc: CallingConvention) -> Result<()> {
let entry = self.inner.address();
let result = self
.inner
.db_mut()
.func_set_cc(entry, c_int::from(u8::from(cc)));
let out = sig_result(result.code, entry, None, &result.reason);
self.inner.queued(out, PendingInvalidation::Dependents)
}
#[doc(alias("get_func_details", "create_func"))]
pub fn prepend_this(&mut self, this: impl Into<TypeExpr>) -> Result<()> {
let entry = self.inner.address();
let recipe = this.into().checked_serialize()?;
let result = self.inner.db_mut().func_prepend_this(entry, &recipe);
let out = sig_result(result.code, entry, None, &result.reason);
self.inner.queued(out, PendingInvalidation::Dependents)
}
}
impl std::fmt::Debug for FunctionEdit<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FunctionEdit")
.field("address", &self.address())
.finish_non_exhaustive()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct FunctionSnapshot {
pub address: Address,
pub name: FunctionName,
pub prototype: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[doc(alias("FF_NAME", "FF_LABL"))]
pub enum FunctionName {
User(String),
Auto(String),
Dummy(String),
}
impl FunctionName {
#[inline]
#[must_use]
pub fn as_str(&self) -> &str {
match self {
Self::User(s) | Self::Auto(s) | Self::Dummy(s) => s,
}
}
#[inline]
#[must_use]
pub fn is_user(&self) -> bool {
matches!(self, Self::User(_))
}
#[inline]
#[must_use]
pub fn is_auto(&self) -> bool {
matches!(self, Self::Auto(_))
}
#[inline]
#[must_use]
pub fn is_dummy(&self) -> bool {
matches!(self, Self::Dummy(_))
}
fn from_flags(flags: u64, text: String) -> Self {
let named = flags & sys::FF_NAME != 0;
let labeled = flags & sys::FF_LABL != 0;
match (named, labeled) {
(true, false) => Self::User(text),
(true, true) => Self::Auto(text),
_ => Self::Dummy(text),
}
}
}
impl From<FunctionName> for String {
#[inline]
fn from(name: FunctionName) -> Self {
match name {
FunctionName::User(s) | FunctionName::Auto(s) | FunctionName::Dummy(s) => s,
}
}
}
impl std::ops::Deref for FunctionName {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.as_str()
}
}
impl std::fmt::Display for FunctionName {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl std::fmt::Debug for Function<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Function")
.field("address", &self.address)
.field("name", &self.name())
.finish()
}
}
key_identity!(Function, address, ord);
#[doc(alias("getn_func"))]
pub struct Functions<'db> {
db: &'db Database,
next: usize,
count: usize,
}
impl<'db> Functions<'db> {
#[inline]
pub(crate) fn new(db: &'db Database) -> Self {
Self {
db,
next: 0,
count: db.func_qty(),
}
}
}
impl<'db> Iterator for Functions<'db> {
type Item = Function<'db>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
while self.next < self.count {
let raw = self.db.func_ea(self.next);
self.next += 1;
if let Some(address) = Address::try_new(raw) {
return Some(Function::new(address, self.db));
}
}
None
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(0, Some(self.count - self.next))
}
}
impl std::fmt::Debug for Functions<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Functions")
.field("next", &self.next)
.field("count", &self.count)
.finish_non_exhaustive()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[doc(alias("func_tail_iterator_t"))]
pub struct FunctionChunk {
pub start: Address,
pub end: Address,
}
#[doc(alias("func_tail_iterator_t"))]
pub struct FunctionChunks {
chunks: std::vec::IntoIter<FunctionChunk>,
}
impl FunctionChunks {
#[inline]
pub(crate) fn new(address: Address, db: &Database) -> Self {
Self::from_chunks(
db.range_all_chunks(address)
.into_iter()
.filter_map(|r| {
Some(FunctionChunk {
start: Address::try_new(r.start)?,
end: Address::try_new(r.end)?,
})
})
.collect(),
)
}
#[inline]
pub(crate) fn from_chunks(chunks: Vec<FunctionChunk>) -> Self {
Self {
chunks: chunks.into_iter(),
}
}
}
impl Iterator for FunctionChunks {
type Item = FunctionChunk;
#[inline]
fn next(&mut self) -> Option<FunctionChunk> {
self.chunks.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.chunks.size_hint()
}
}
impl ExactSizeIterator for FunctionChunks {}
impl std::fmt::Debug for FunctionChunks {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FunctionChunks")
.field("remaining", &self.chunks.as_slice())
.finish()
}
}
#[cfg(test)]
mod tests {
use assert2::assert;
use super::*;
const fn assert_send<T: Send>() {}
const _: () = assert_send::<FunctionSnapshot>();
const _: () = assert_send::<FunctionName>();
#[test]
fn from_flags_classifies_by_the_two_name_bits() {
assert!(
FunctionName::from_flags(sys::FF_NAME, "s".into()) == FunctionName::User("s".into())
);
assert!(
FunctionName::from_flags(sys::FF_NAME | sys::FF_LABL, "s".into())
== FunctionName::Auto("s".into())
);
assert!(
FunctionName::from_flags(sys::FF_LABL, "s".into()) == FunctionName::Dummy("s".into())
);
assert!(FunctionName::from_flags(0, "s".into()) == FunctionName::Dummy("s".into()));
}
#[test]
fn from_flags_ignores_unrelated_bits() {
let noise = 0xFFFF_FFFF_FFFF_3FFFu64; assert!(
FunctionName::from_flags(noise | sys::FF_NAME, "x".into())
== FunctionName::User("x".into())
);
assert!(
FunctionName::from_flags(noise | sys::FF_LABL, "x".into())
== FunctionName::Dummy("x".into())
);
}
#[test]
fn accessors_project_text_and_kind() {
let u = FunctionName::User("main".into());
assert!(u.as_str() == "main");
assert!(&*u == "main");
assert!(format!("{u}") == "main");
assert!(String::from(u.clone()) == "main");
assert!(u.is_user() && !u.is_auto() && !u.is_dummy());
assert!(FunctionName::Dummy("sub_1000".into()).is_dummy());
assert!(FunctionName::Auto("nullsub_0".into()).is_auto());
}
#[test]
fn from_flags_matches_ida_predicates() {
for extra in [0u64, 0x1234_5678, u64::MAX] {
let extra = extra & !(sys::FF_NAME | sys::FF_LABL);
for &bits in &[0, sys::FF_NAME, sys::FF_LABL, sys::FF_NAME | sys::FF_LABL] {
let flags = extra | bits;
let ours = FunctionName::from_flags(flags, String::new());
let (user, auto, dummy) = (
sys::has_user_name(flags),
sys::has_auto_name(flags),
sys::has_dummy_name(flags),
);
assert!(ours.is_user() == user);
assert!(ours.is_auto() == auto);
assert!(ours.is_dummy() == (!user && !auto));
if dummy {
assert!(ours.is_dummy());
}
}
}
}
#[test]
fn function_chunk_ord_sorts_by_start_then_end() {
let low = FunctionChunk {
start: Address::try_new(0x1000).unwrap(),
end: Address::try_new(0x1010).unwrap(),
};
let high = FunctionChunk {
start: Address::try_new(0x2000).unwrap(),
end: Address::try_new(0x2010).unwrap(),
};
let mut chunks = vec![high, low];
chunks.sort();
assert!(chunks == vec![low, high]);
}
#[test]
fn function_name_serde_round_trips() {
let name = FunctionName::User("main".into());
let json = serde_json::to_string(&name).unwrap();
let back: FunctionName = serde_json::from_str(&json).unwrap();
assert!(back == name);
}
#[test]
fn function_snapshot_serde_round_trips() {
let snapshot = FunctionSnapshot {
address: Address::try_new(0x0040_1000).unwrap(),
name: FunctionName::Auto("nullsub_0".into()),
prototype: Some("void nullsub_0(void)".into()),
};
let json = serde_json::to_string(&snapshot).unwrap();
let back: FunctionSnapshot = serde_json::from_str(&json).unwrap();
assert!(back == snapshot);
}
}