Skip to main content

idalib/
func.rs

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