idalib/
func.rs

1use std::marker::PhantomData;
2use std::mem;
3use std::pin::Pin;
4use std::ptr;
5
6use autocxx::moveit::Emplace;
7use bitflags::bitflags;
8use cxx::UniquePtr;
9
10use crate::ffi::func::*;
11use crate::ffi::xref::has_external_refs;
12use crate::ffi::{range_t, IDAError, BADADDR};
13use crate::idb::IDB;
14use crate::Address;
15
16pub struct Function<'a> {
17    ptr: *mut func_t,
18    _lock: Pin<Box<lock_func>>,
19    _marker: PhantomData<&'a IDB>,
20}
21
22pub struct FunctionCFG<'a> {
23    flow_chart: UniquePtr<qflow_chart_t>,
24    _marker: PhantomData<&'a Function<'a>>,
25}
26
27pub struct BasicBlock<'a> {
28    block: *const qbasic_block_t,
29    kind: fc_block_type_t,
30    _marker: PhantomData<&'a FunctionCFG<'a>>,
31}
32
33impl<'a> BasicBlock<'a> {
34    fn as_range_t(&self) -> *const range_t {
35        self.block.cast()
36    }
37
38    pub(crate) fn from_parts(ptr: *const qbasic_block_t, kind: fc_block_type_t) -> Self {
39        BasicBlock {
40            block: ptr,
41            kind,
42            _marker: PhantomData,
43        }
44    }
45
46    pub fn start_address(&self) -> Address {
47        unsafe { (*self.as_range_t()).start_ea.into() }
48    }
49
50    pub fn end_address(&self) -> Address {
51        unsafe { (*self.as_range_t()).end_ea.into() }
52    }
53
54    pub fn contains_address(&self, addr: Address) -> bool {
55        unsafe { (*self.as_range_t()).contains(addr.into()) }
56    }
57
58    pub fn len(&self) -> usize {
59        unsafe { (*self.as_range_t()).size().0 as _ }
60    }
61
62    pub fn is_empty(&self) -> bool {
63        self.len() == 0
64    }
65
66    pub fn is_normal(&self) -> bool {
67        matches!(self.kind, fc_block_type_t::fcb_normal)
68    }
69
70    pub fn is_indjump(&self) -> bool {
71        matches!(self.kind, fc_block_type_t::fcb_indjump)
72    }
73
74    pub fn is_ret(&self) -> bool {
75        matches!(self.kind, fc_block_type_t::fcb_ret)
76    }
77
78    pub fn is_cndret(&self) -> bool {
79        matches!(self.kind, fc_block_type_t::fcb_cndret)
80    }
81
82    pub fn is_noret(&self) -> bool {
83        matches!(self.kind, fc_block_type_t::fcb_noret)
84    }
85
86    pub fn is_enoret(&self) -> bool {
87        matches!(self.kind, fc_block_type_t::fcb_enoret)
88    }
89
90    pub fn is_extern(&self) -> bool {
91        matches!(self.kind, fc_block_type_t::fcb_extern)
92    }
93
94    pub fn is_error(&self) -> bool {
95        matches!(self.kind, fc_block_type_t::fcb_error)
96    }
97
98    pub fn succs<'b>(&'b self) -> impl ExactSizeIterator<Item = BasicBlockId> + 'b {
99        unsafe { idalib_qbasic_block_succs(self.block) }
100            .iter()
101            .map(|v| v.0 as _)
102    }
103
104    pub fn succs_with<'b>(
105        &'b self,
106        cfg: &'a FunctionCFG<'_>,
107    ) -> impl ExactSizeIterator<Item = BasicBlock<'a>> + 'b {
108        self.succs()
109            .map(|id| cfg.block_by_id(id).expect("valid block"))
110    }
111
112    pub fn preds<'b>(&'b self) -> impl ExactSizeIterator<Item = BasicBlockId> + 'b {
113        unsafe { idalib_qbasic_block_preds(self.block) }
114            .iter()
115            .map(|v| v.0 as _)
116    }
117
118    pub fn preds_with<'b>(
119        &'b self,
120        cfg: &'a FunctionCFG<'_>,
121    ) -> impl ExactSizeIterator<Item = BasicBlock<'a>> + 'b {
122        self.preds()
123            .map(|id| cfg.block_by_id(id).expect("valid block"))
124    }
125}
126
127pub type FunctionId = usize;
128pub type BasicBlockId = usize;
129
130bitflags! {
131    #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
132    pub struct FunctionFlags: u64 {
133        const NORET = flags::FUNC_NORET as u64;
134        const FAR = flags::FUNC_FAR as u64;
135        const LIB = flags::FUNC_LIB as u64;
136        const STATICDEF = flags::FUNC_STATICDEF as u64;
137        const FRAME = flags::FUNC_FRAME as u64;
138        const USERFAR = flags::FUNC_USERFAR as u64;
139        const HIDDEN = flags::FUNC_HIDDEN as u64;
140        const THUNK = flags::FUNC_THUNK as u64;
141        const BOTTOMBP = flags::FUNC_BOTTOMBP as u64;
142        const NORET_PENDING = flags::FUNC_NORET_PENDING as u64;
143        const SP_READY = flags::FUNC_SP_READY as u64;
144        const FUZZY_SP = flags::FUNC_FUZZY_SP as u64;
145        const PROLOG_OK = flags::FUNC_PROLOG_OK as u64;
146        const PURGED_OK = flags::FUNC_PURGED_OK as u64;
147        const TAIL = flags::FUNC_TAIL as u64;
148        const LUMINA = flags::FUNC_LUMINA as u64;
149        const OUTLINE = flags::FUNC_OUTLINE as u64;
150        const REANALYZE = flags::FUNC_REANALYZE as u64;
151        const RESERVED = flags::FUNC_RESERVED as u64;
152    }
153}
154
155bitflags! {
156    #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
157    pub struct FunctionCFGFlags: i32 {
158        const PRINT = cfg_flags::FC_PRINT as i32;
159        const NOEXT = cfg_flags::FC_NOEXT as i32;
160        const RESERVED = cfg_flags::FC_RESERVED as i32;
161        const APPND = cfg_flags::FC_APPND as i32;
162        const CHKBREAK = cfg_flags::FC_CHKBREAK as i32;
163        const CALL_ENDS = cfg_flags::FC_CALL_ENDS as i32;
164        const NOPREDS = cfg_flags::FC_NOPREDS as i32;
165        const OUTLINES = cfg_flags::FC_OUTLINES as i32;
166    }
167}
168
169impl<'a> Function<'a> {
170    pub(crate) fn from_ptr(ptr: *mut func_t) -> Self {
171        let lock = unsafe { Box::emplace(lock_func::new(ptr)) };
172        Self {
173            ptr,
174            _lock: lock,
175            _marker: PhantomData,
176        }
177    }
178
179    pub(crate) fn as_ptr(&self) -> *mut func_t {
180        self.ptr
181    }
182
183    fn as_range_t(&self) -> *const range_t {
184        self.ptr.cast()
185    }
186
187    pub fn start_address(&self) -> Address {
188        unsafe { (*self.as_range_t()).start_ea.into() }
189    }
190
191    pub fn end_address(&self) -> Address {
192        unsafe { (*self.as_range_t()).end_ea.into() }
193    }
194
195    pub fn contains_address(&self, addr: Address) -> bool {
196        unsafe { (*self.as_range_t()).contains(addr.into()) }
197    }
198
199    pub fn len(&self) -> usize {
200        unsafe { (*self.as_range_t()).size().0 as _ }
201    }
202
203    pub fn is_empty(&self) -> bool {
204        self.len() == 0
205    }
206
207    pub fn name(&self) -> Option<String> {
208        let name = unsafe { idalib_func_name(self.ptr) }.ok()?;
209
210        if name.is_empty() {
211            None
212        } else {
213            Some(name)
214        }
215    }
216
217    pub fn flags(&self) -> FunctionFlags {
218        let bits = unsafe { idalib_func_flags(self.ptr) };
219        FunctionFlags::from_bits_retain(bits)
220    }
221
222    pub fn is_far(&self) -> bool {
223        unsafe { (*self.ptr).is_far() }
224    }
225
226    pub fn does_return(&self) -> bool {
227        unsafe { (*self.ptr).does_return() }
228    }
229
230    pub fn analyzed_sp(&self) -> bool {
231        unsafe { (*self.ptr).analyzed_sp() }
232    }
233
234    pub fn need_prolog_analysis(&self) -> bool {
235        unsafe { (*self.ptr).need_prolog_analysis() }
236    }
237
238    pub fn has_external_refs(&self, ea: Address) -> bool {
239        unsafe { has_external_refs(self.ptr, ea.into()) }
240    }
241
242    pub fn calc_thunk_target(&self) -> Option<Address> {
243        let addr = unsafe { calc_thunk_func_target(self.ptr, ptr::null_mut()) };
244
245        if addr == BADADDR {
246            None
247        } else {
248            Some(addr.into())
249        }
250    }
251
252    pub fn cfg(&self) -> Result<FunctionCFG, IDAError> {
253        self.cfg_with(FunctionCFGFlags::empty())
254    }
255
256    pub fn cfg_with(&self, flags: FunctionCFGFlags) -> Result<FunctionCFG, IDAError> {
257        let ptr = unsafe { idalib_func_flow_chart(self.ptr, flags.bits().into()) };
258
259        Ok(FunctionCFG {
260            flow_chart: ptr.map_err(IDAError::ffi)?,
261            _marker: PhantomData,
262        })
263    }
264}
265
266impl<'a> FunctionCFG<'a> {
267    unsafe fn as_gdl_graph(&self) -> Option<&gdl_graph_t> {
268        self.flow_chart
269            .as_ref()
270            .map(|r| mem::transmute::<&qflow_chart_t, &gdl_graph_t>(r))
271    }
272
273    pub fn block_by_id(&self, id: BasicBlockId) -> Option<BasicBlock> {
274        let blk = unsafe {
275            idalib_qflow_graph_getn_block(self.flow_chart.as_ref().expect("valid pointer"), id)
276        };
277
278        if blk.is_null() {
279            return None;
280        }
281
282        let kind = unsafe {
283            self.flow_chart
284                .as_ref()
285                .expect("valid pointer")
286                .calc_block_type(id)
287        };
288
289        Some(BasicBlock::from_parts(blk, kind))
290    }
291
292    pub fn entry(&self) -> Option<BasicBlock> {
293        let id = unsafe { self.as_gdl_graph().expect("valid pointer").entry() };
294
295        if id.0 < 0 {
296            return None;
297        }
298
299        self.block_by_id(id.0 as _)
300    }
301
302    pub fn exit(&self) -> Option<BasicBlock> {
303        let id = unsafe { self.as_gdl_graph().expect("valid pointer").exit() };
304
305        if id.0 < 0 {
306            return None;
307        }
308
309        self.block_by_id(id.0 as _)
310    }
311
312    pub fn blocks_count(&self) -> usize {
313        unsafe { self.as_gdl_graph().expect("valid pointer").node_qty().0 as _ }
314    }
315
316    pub fn blocks<'b>(&'b self) -> impl ExactSizeIterator<Item = BasicBlock<'b>> + 'b {
317        (0..self.blocks_count()).map(|id| self.block_by_id(id).expect("valid block"))
318    }
319}