1use std::ffi::CString;
2use std::marker::PhantomData;
3use std::mem::MaybeUninit;
4use std::path::{Path, PathBuf};
5
6use crate::ffi::bytes::*;
7use crate::ffi::comments::{append_cmt, idalib_get_cmt, set_cmt};
8use crate::ffi::conversions::idalib_ea2str;
9use crate::ffi::entry::{get_entry, get_entry_ordinal, get_entry_qty};
10use crate::ffi::func::{get_func, get_func_qty, getn_func};
11use crate::ffi::hexrays::{decompile_func, init_hexrays_plugin, term_hexrays_plugin};
12use crate::ffi::ida::{
13 auto_wait, close_database_with, make_signatures, open_database_quiet, set_screen_ea,
14};
15use crate::ffi::insn::decode;
16use crate::ffi::loader::find_plugin;
17use crate::ffi::processor::get_ph;
18use crate::ffi::search::{idalib_find_defined, idalib_find_imm, idalib_find_text};
19use crate::ffi::segment::{get_segm_qty, getnseg, getseg};
20use crate::ffi::util::{is_align_insn, next_head, prev_head, str2reg};
21use crate::ffi::xref::{xrefblk_t, xrefblk_t_first_from, xrefblk_t_first_to};
22use crate::ffi::BADADDR;
23
24use crate::bookmarks::Bookmarks;
25use crate::decompiler::CFunction;
26use crate::func::{Function, FunctionId};
27use crate::insn::{Insn, Register};
28use crate::meta::{Metadata, MetadataMut};
29use crate::plugin::Plugin;
30use crate::processor::Processor;
31use crate::segment::{Segment, SegmentId};
32use crate::strings::StringList;
33use crate::xref::{XRef, XRefQuery};
34use crate::{prepare_library, Address, IDAError, IDARuntimeHandle};
35
36pub struct IDB {
37 path: PathBuf,
38 save: bool,
39 decompiler: bool,
40 _guard: IDARuntimeHandle,
41 _marker: PhantomData<*const ()>,
42}
43
44impl IDB {
45 pub fn open(path: impl AsRef<Path>) -> Result<Self, IDAError> {
46 Self::open_with(path, true, false)
47 }
48
49 pub fn open_with(
50 path: impl AsRef<Path>,
51 auto_analyse: bool,
52 save: bool,
53 ) -> Result<Self, IDAError> {
54 let _guard = prepare_library();
55 let path = path.as_ref();
56
57 if !path.exists() || !path.is_file() {
58 return Err(IDAError::not_found(path));
59 }
60
61 open_database_quiet(path, auto_analyse)?;
62
63 let decompiler = unsafe { init_hexrays_plugin(0.into()) };
64
65 Ok(Self {
66 path: path.to_owned(),
67 save,
68 decompiler,
69 _guard,
70 _marker: PhantomData,
71 })
72 }
73
74 pub fn path(&self) -> &Path {
75 &self.path
76 }
77
78 pub fn save_on_close(&mut self, status: bool) {
79 self.save = status;
80 }
81
82 pub fn auto_wait(&mut self) -> bool {
83 unsafe { auto_wait() }
84 }
85
86 pub fn set_screen_address(&mut self, ea: Address) {
87 set_screen_ea(ea.into());
88 }
89
90 pub fn make_signatures(&mut self, only_pat: bool) -> Result<(), IDAError> {
91 make_signatures(only_pat)
92 }
93
94 pub fn decompiler_available(&self) -> bool {
95 self.decompiler
96 }
97
98 pub fn meta(&self) -> Metadata {
99 Metadata::new()
100 }
101
102 pub fn meta_mut(&mut self) -> MetadataMut {
103 MetadataMut::new()
104 }
105
106 pub fn processor(&self) -> Processor {
107 let ptr = unsafe { get_ph() };
108 Processor::from_ptr(ptr)
109 }
110
111 pub fn entries(&self) -> EntryPointIter {
112 let limit = unsafe { get_entry_qty() };
113 EntryPointIter {
114 index: 0,
115 limit,
116 _marker: PhantomData,
117 }
118 }
119
120 pub fn function_at(&self, ea: Address) -> Option<Function> {
121 let ptr = unsafe { get_func(ea.into()) };
122
123 if ptr.is_null() {
124 return None;
125 }
126
127 Some(Function::from_ptr(ptr))
128 }
129
130 pub fn next_head(&self, ea: Address) -> Option<Address> {
131 self.next_head_with(ea, BADADDR.into())
132 }
133
134 pub fn next_head_with(&self, ea: Address, max_ea: Address) -> Option<Address> {
135 let next = unsafe { next_head(ea.into(), max_ea.into()) };
136 if next == BADADDR {
137 None
138 } else {
139 Some(next.into())
140 }
141 }
142
143 pub fn prev_head(&self, ea: Address) -> Option<Address> {
144 self.prev_head_with(ea, 0)
145 }
146
147 pub fn prev_head_with(&self, ea: Address, min_ea: Address) -> Option<Address> {
148 let prev = unsafe { prev_head(ea.into(), min_ea.into()) };
149 if prev == BADADDR {
150 None
151 } else {
152 Some(prev.into())
153 }
154 }
155
156 pub fn insn_at(&self, ea: Address) -> Option<Insn> {
157 let insn = decode(ea.into())?;
158 Some(Insn::from_repr(insn))
159 }
160
161 pub fn decompile<'a>(&'a self, f: &Function<'a>) -> Result<CFunction<'a>, IDAError> {
162 self.decompile_with(f, false)
163 }
164
165 pub fn decompile_with<'a>(
166 &'a self,
167 f: &Function<'a>,
168 all_blocks: bool,
169 ) -> Result<CFunction<'a>, IDAError> {
170 if !self.decompiler {
171 return Err(IDAError::ffi_with("no decompiler available"));
172 }
173
174 Ok(unsafe {
175 decompile_func(f.as_ptr(), all_blocks)
176 .map(|f| CFunction::new(f).expect("null pointer checked"))?
177 })
178 }
179
180 pub fn function_by_id(&self, id: FunctionId) -> Option<Function> {
181 let ptr = unsafe { getn_func(id) };
182
183 if ptr.is_null() {
184 return None;
185 }
186
187 Some(Function::from_ptr(ptr))
188 }
189
190 pub fn functions<'a>(&'a self) -> impl Iterator<Item = (FunctionId, Function<'a>)> + 'a {
191 (0..self.function_count()).filter_map(|id| self.function_by_id(id).map(|f| (id, f)))
192 }
193
194 pub fn function_count(&self) -> usize {
195 unsafe { get_func_qty() }
196 }
197
198 pub fn segment_at(&self, ea: Address) -> Option<Segment> {
199 let ptr = unsafe { getseg(ea.into()) };
200
201 if ptr.is_null() {
202 return None;
203 }
204
205 Some(Segment::from_ptr(ptr))
206 }
207
208 pub fn segment_by_id(&self, id: SegmentId) -> Option<Segment> {
209 let ptr = unsafe { getnseg((id as i32).into()) };
210
211 if ptr.is_null() {
212 return None;
213 }
214
215 Some(Segment::from_ptr(ptr))
216 }
217
218 pub fn segments<'a>(&'a self) -> impl Iterator<Item = (SegmentId, Segment<'a>)> + 'a {
219 (0..self.segment_count()).filter_map(|id| self.segment_by_id(id).map(|s| (id, s)))
220 }
221
222 pub fn segment_count(&self) -> usize {
223 unsafe { get_segm_qty().0 as _ }
224 }
225
226 pub fn register_by_name(&self, name: impl AsRef<str>) -> Option<Register> {
227 let s = CString::new(name.as_ref()).ok()?;
228 let id = unsafe { str2reg(s.as_ptr()).0 };
229
230 if id == -1 {
231 None
232 } else {
233 Some(id as _)
234 }
235 }
236
237 pub fn insn_alignment_at(&self, ea: Address) -> Option<usize> {
238 let align = unsafe { is_align_insn(ea.into()).0 };
239 if align == 0 {
240 None
241 } else {
242 Some(align as _)
243 }
244 }
245
246 pub fn first_xref_from(&self, ea: Address, flags: XRefQuery) -> Option<XRef> {
247 let mut xref = MaybeUninit::<xrefblk_t>::zeroed();
248 let found =
249 unsafe { xrefblk_t_first_from(xref.as_mut_ptr(), ea.into(), flags.bits().into()) };
250
251 if found {
252 Some(XRef::from_repr(unsafe { xref.assume_init() }))
253 } else {
254 None
255 }
256 }
257
258 pub fn first_xref_to(&self, ea: Address, flags: XRefQuery) -> Option<XRef> {
259 let mut xref = MaybeUninit::<xrefblk_t>::zeroed();
260 let found =
261 unsafe { xrefblk_t_first_to(xref.as_mut_ptr(), ea.into(), flags.bits().into()) };
262
263 if found {
264 Some(XRef::from_repr(unsafe { xref.assume_init() }))
265 } else {
266 None
267 }
268 }
269
270 pub fn get_cmt(&self, ea: Address) -> Option<String> {
271 self.get_cmt_with(ea, false)
272 }
273
274 pub fn get_cmt_with(&self, ea: Address, rptble: bool) -> Option<String> {
275 let s = unsafe { idalib_get_cmt(ea.into(), rptble) };
276
277 if s.is_empty() {
278 None
279 } else {
280 Some(s)
281 }
282 }
283
284 pub fn set_cmt(&self, ea: Address, comm: impl AsRef<str>) -> Result<(), IDAError> {
285 self.set_cmt_with(ea, comm, false)
286 }
287
288 pub fn set_cmt_with(
289 &self,
290 ea: Address,
291 comm: impl AsRef<str>,
292 rptble: bool,
293 ) -> Result<(), IDAError> {
294 let s = CString::new(comm.as_ref()).map_err(IDAError::ffi)?;
295 if unsafe { set_cmt(ea.into(), s.as_ptr(), rptble) } {
296 Ok(())
297 } else {
298 Err(IDAError::ffi_with(format!(
299 "failed to set comment at {ea:#x}"
300 )))
301 }
302 }
303
304 pub fn append_cmt(&self, ea: Address, comm: impl AsRef<str>) -> Result<(), IDAError> {
305 self.append_cmt_with(ea, comm, false)
306 }
307
308 pub fn append_cmt_with(
309 &self,
310 ea: Address,
311 comm: impl AsRef<str>,
312 rptble: bool,
313 ) -> Result<(), IDAError> {
314 let s = CString::new(comm.as_ref()).map_err(IDAError::ffi)?;
315 if unsafe { append_cmt(ea.into(), s.as_ptr(), rptble) } {
316 Ok(())
317 } else {
318 Err(IDAError::ffi_with(format!(
319 "failed to append comment at {ea:#x}"
320 )))
321 }
322 }
323
324 pub fn remove_cmt(&self, ea: Address) -> Result<(), IDAError> {
325 self.remove_cmt_with(ea, false)
326 }
327
328 pub fn remove_cmt_with(&self, ea: Address, rptble: bool) -> Result<(), IDAError> {
329 let s = CString::new("").map_err(IDAError::ffi)?;
330 if unsafe { set_cmt(ea.into(), s.as_ptr(), rptble) } {
331 Ok(())
332 } else {
333 Err(IDAError::ffi_with(format!(
334 "failed to remove comment at {ea:#x}"
335 )))
336 }
337 }
338
339 pub fn bookmarks(&self) -> Bookmarks {
340 Bookmarks::new(self)
341 }
342
343 pub fn find_text(&self, start_ea: Address, text: impl AsRef<str>) -> Option<Address> {
344 let s = CString::new(text.as_ref()).ok()?;
345 let addr = unsafe { idalib_find_text(start_ea.into(), s.as_ptr()) };
346 if addr == BADADDR {
347 None
348 } else {
349 Some(addr.into())
350 }
351 }
352
353 pub fn find_text_iter<'a, T>(&'a self, text: T) -> impl Iterator<Item = Address> + 'a
354 where
355 T: AsRef<str> + 'a,
356 {
357 let mut cur = 0u64;
358 std::iter::from_fn(move || {
359 let found = self.find_text(cur, text.as_ref())?;
360 cur = self.find_defined(found).unwrap_or(BADADDR.into());
361 Some(found)
362 })
363 }
364
365 pub fn find_imm(&self, start_ea: Address, imm: u32) -> Option<Address> {
366 let addr = unsafe { idalib_find_imm(start_ea.into(), imm.into()) };
367 if addr == BADADDR {
368 None
369 } else {
370 Some(addr.into())
371 }
372 }
373
374 pub fn find_imm_iter<'a>(&'a self, imm: u32) -> impl Iterator<Item = Address> + 'a {
375 let mut cur = 0u64;
376 std::iter::from_fn(move || {
377 cur = self.find_imm(cur, imm)?;
378 Some(cur)
379 })
380 }
381
382 pub fn find_defined(&self, start_ea: Address) -> Option<Address> {
383 let addr = unsafe { idalib_find_defined(start_ea.into()) };
384 if addr == BADADDR {
385 None
386 } else {
387 Some(addr.into())
388 }
389 }
390
391 pub fn strings(&self) -> StringList {
392 StringList::new(self)
393 }
394
395 pub fn address_to_string(&self, ea: Address) -> Option<String> {
396 let s = unsafe { idalib_ea2str(ea.into()) };
397
398 if s.is_empty() {
399 None
400 } else {
401 Some(s)
402 }
403 }
404
405 pub fn get_byte(&self, ea: Address) -> u8 {
406 unsafe { idalib_get_byte(ea.into()) }
407 }
408
409 pub fn get_word(&self, ea: Address) -> u16 {
410 unsafe { idalib_get_word(ea.into()) }
411 }
412
413 pub fn get_dword(&self, ea: Address) -> u32 {
414 unsafe { idalib_get_dword(ea.into()) }
415 }
416
417 pub fn get_qword(&self, ea: Address) -> u64 {
418 unsafe { idalib_get_qword(ea.into()) }
419 }
420
421 pub fn get_bytes(&self, ea: Address, size: usize) -> Vec<u8> {
422 let mut buf = Vec::with_capacity(size);
423
424 let Ok(new_len) = (unsafe { idalib_get_bytes(ea.into(), &mut buf) }) else {
425 return Vec::with_capacity(0);
426 };
427
428 unsafe {
429 buf.set_len(new_len);
430 }
431
432 buf
433 }
434
435 pub fn find_plugin(
436 &self,
437 name: impl AsRef<str>,
438 load_if_needed: bool,
439 ) -> Result<Plugin, IDAError> {
440 let plugin = CString::new(name.as_ref()).map_err(IDAError::ffi)?;
441 let ptr = unsafe { find_plugin(plugin.as_ptr(), load_if_needed) };
442
443 if ptr.is_null() {
444 Err(IDAError::ffi_with(format!(
445 "failed to load {} plugin",
446 name.as_ref()
447 )))
448 } else {
449 Ok(Plugin::from_ptr(ptr))
450 }
451 }
452
453 pub fn load_plugin(&self, name: impl AsRef<str>) -> Result<Plugin, IDAError> {
454 self.find_plugin(name, true)
455 }
456}
457
458impl Drop for IDB {
459 fn drop(&mut self) {
460 if self.decompiler {
461 unsafe {
462 term_hexrays_plugin();
463 }
464 }
465 close_database_with(self.save);
466 }
467}
468
469pub struct EntryPointIter<'a> {
470 index: usize,
471 limit: usize,
472 _marker: PhantomData<&'a IDB>,
473}
474
475impl<'a> Iterator for EntryPointIter<'a> {
476 type Item = Address;
477
478 fn next(&mut self) -> Option<Self::Item> {
479 if self.index >= self.limit {
480 return None;
481 }
482
483 let ordinal = unsafe { get_entry_ordinal(self.index) };
484 let addr = unsafe { get_entry(ordinal) };
485
486 if addr == BADADDR {
488 self.index += 1;
489 return self.next();
490 }
491
492 Some(addr.into())
493 }
494
495 fn size_hint(&self) -> (usize, Option<usize>) {
496 let lim = self.limit - self.index;
497 (0, Some(lim))
498 }
499}