1use std::ffi::CString;
2use std::marker::PhantomData;
3use std::mem::MaybeUninit;
4#[cfg(not(feature = "plugin"))]
5use std::path::{Path, PathBuf};
6
7use crate::bookmarks::Bookmarks;
8use crate::decompiler::CFunction;
9use crate::ffi::BADADDR;
10use crate::ffi::bytes::*;
11use crate::ffi::comments::{append_cmt, idalib_get_cmt, set_cmt};
12use crate::ffi::conversions::idalib_ea2str;
13use crate::ffi::entry::{get_entry, get_entry_ordinal, get_entry_qty};
14use crate::ffi::func::{
15 add_func, del_func, get_func, get_func_qty, getn_func, idalib_get_func_cmt, idalib_set_func_cmt,
16};
17#[cfg(not(feature = "plugin"))]
18use crate::ffi::hexrays::term_hexrays_plugin;
19use crate::ffi::hexrays::{change_hexrays_config, decompile_function, init_hexrays_plugin};
20#[cfg(not(feature = "plugin"))]
21use crate::ffi::ida::{auto_wait, close_database_with, open_database_quiet};
22use crate::ffi::ida::{is_decompiler_available, make_signatures, set_screen_ea};
23use crate::ffi::insn::decode;
24use crate::ffi::plugin::find_plugin;
25use crate::ffi::processor::get_ph;
26use crate::ffi::search::{idalib_find_defined, idalib_find_imm, idalib_find_text};
27use crate::ffi::segment::{get_segm_by_name, get_segm_qty, getnseg, getseg};
28use crate::ffi::typeinf::{idalib_format_cfunc_decls, idalib_format_decls};
29use crate::ffi::util::{get_imagebase, is_align_insn, next_head, prev_head, str2reg};
30use crate::ffi::xref::{xrefblk_t, xrefblk_t_first_from, xrefblk_t_first_to};
31use crate::func::{Function, FunctionId};
32use crate::insn::{Insn, Register};
33use crate::meta::{Metadata, MetadataMut};
34use crate::name::NameList;
35use crate::plugin::Plugin;
36use crate::processor::Processor;
37use crate::segment::{Segment, SegmentId};
38use crate::strings::StringList;
39use crate::typeinf::FormatDeclsOptions;
40use crate::xref::{XRef, XRefQuery};
41use crate::{Address, AddressFlags, IDAError, IDARuntimeHandle, prepare_library};
42
43pub struct IDB {
44 #[cfg(not(feature = "plugin"))]
45 path: PathBuf,
46 #[cfg(not(feature = "plugin"))]
47 save: bool,
48 decompiler: bool,
49 _guard: IDARuntimeHandle,
50 _marker: PhantomData<*const ()>,
51}
52
53#[derive(Debug, Clone)]
54#[cfg(not(feature = "plugin"))]
55pub struct IDBOpenOptions {
56 idb: Option<PathBuf>,
57 ftype: Option<String>,
58
59 save: bool,
60 auto_analyse: bool,
61}
62
63#[cfg(not(feature = "plugin"))]
64impl Default for IDBOpenOptions {
65 fn default() -> Self {
66 Self {
67 idb: None,
68 ftype: None,
69 save: false,
70 auto_analyse: true,
71 }
72 }
73}
74
75#[cfg(not(feature = "plugin"))]
76impl IDBOpenOptions {
77 pub fn new() -> Self {
78 Self::default()
79 }
80
81 pub fn idb(&mut self, path: impl AsRef<Path>) -> &mut Self {
82 self.idb = Some(path.as_ref().to_owned());
83 self
84 }
85
86 pub fn save(&mut self, save: bool) -> &mut Self {
87 self.save = save;
88 self
89 }
90
91 pub fn file_type(&mut self, ftype: impl AsRef<str>) -> &mut Self {
92 self.ftype = Some(ftype.as_ref().to_owned());
93 self
94 }
95
96 pub fn auto_analyse(&mut self, auto_analyse: bool) -> &mut Self {
97 self.auto_analyse = auto_analyse;
98 self
99 }
100
101 pub fn open(&self, path: impl AsRef<Path>) -> Result<IDB, IDAError> {
102 let mut args = Vec::new();
103
104 if let Some(ftype) = self.ftype.as_ref() {
105 args.push(format!("-T{ftype}"));
106 }
107
108 if let Some(idb_path) = self.idb.as_ref() {
109 args.push("-c".to_owned());
110 args.push(format!("-o{}", idb_path.display()));
111 }
112
113 IDB::open_full_with(path, self.auto_analyse, self.save, &args)
114 }
115}
116
117impl IDB {
118 #[cfg(not(feature = "plugin"))]
119 pub fn open(path: impl AsRef<Path>) -> Result<Self, IDAError> {
120 Self::open_with(path, true, false)
121 }
122
123 #[cfg(not(feature = "plugin"))]
124 pub fn open_with(
125 path: impl AsRef<Path>,
126 auto_analyse: bool,
127 save: bool,
128 ) -> Result<Self, IDAError> {
129 Self::open_full_with(path, auto_analyse, save, &[] as &[&str])
130 }
131
132 #[cfg(not(feature = "plugin"))]
133 fn open_full_with(
134 path: impl AsRef<Path>,
135 auto_analyse: bool,
136 save: bool,
137 args: &[impl AsRef<str>],
138 ) -> Result<Self, IDAError> {
139 let _guard = prepare_library();
140 let path = path.as_ref();
141
142 if !path.exists() || !path.is_file() {
143 return Err(IDAError::not_found(path));
144 }
145
146 open_database_quiet(path, auto_analyse, args)?;
147
148 let decompiler = is_decompiler_available() && unsafe { init_hexrays_plugin(0.into()) };
149
150 Ok(Self {
151 path: path.to_owned(),
152 save,
153 decompiler,
154 _guard,
155 _marker: PhantomData,
156 })
157 }
158
159 #[cfg(feature = "plugin")]
160 pub fn current() -> Result<Self, IDAError> {
161 let _guard = prepare_library();
162
163 Ok(Self {
164 decompiler: is_decompiler_available() && unsafe { init_hexrays_plugin(0.into()) },
165 _guard,
166 _marker: PhantomData,
167 })
168 }
169
170 #[cfg(not(feature = "plugin"))]
171 pub fn path(&self) -> &Path {
172 &self.path
173 }
174
175 #[cfg(not(feature = "plugin"))]
176 pub fn save_on_close(&mut self, status: bool) {
177 self.save = status;
178 }
179
180 #[cfg(not(feature = "plugin"))]
181 pub fn auto_wait(&mut self) -> bool {
182 unsafe { auto_wait() }
183 }
184
185 pub fn set_screen_address(&mut self, ea: Address) {
186 set_screen_ea(ea.into());
187 }
188
189 pub fn make_signatures(&mut self, only_pat: bool) -> Result<(), IDAError> {
190 make_signatures(only_pat)
191 }
192
193 pub fn format_decls(&self) -> Result<String, IDAError> {
194 self.format_decls_with(FormatDeclsOptions::INCL_DEPS | FormatDeclsOptions::DEF_FWD)
197 }
198
199 pub fn format_decls_with(&self, options: FormatDeclsOptions) -> Result<String, IDAError> {
200 unsafe { idalib_format_decls(options.bits()) }.map_err(IDAError::ffi)
201 }
202
203 pub fn format_cfunc_decls<'a>(&'a self, cfunc: &CFunction<'a>) -> Result<String, IDAError> {
204 self.format_cfunc_decls_with(
207 cfunc,
208 FormatDeclsOptions::INCL_DEPS | FormatDeclsOptions::DEF_FWD,
209 )
210 }
211
212 pub fn format_cfunc_decls_with<'a>(
213 &'a self,
214 cfunc: &CFunction<'a>,
215 options: FormatDeclsOptions,
216 ) -> Result<String, IDAError> {
217 unsafe { idalib_format_cfunc_decls(cfunc.as_ptr(), options.bits()) }.map_err(IDAError::ffi)
218 }
219
220 pub fn decompiler_available(&self) -> bool {
221 self.decompiler
222 }
223
224 pub fn modify_decompiler_config(&mut self, directive: impl AsRef<str>) -> Result<(), IDAError> {
225 if !self.decompiler {
226 return Err(IDAError::ffi_with("no decompiler available"));
227 }
228
229 let directive = directive.as_ref();
230 let s = CString::new(directive).map_err(IDAError::ffi)?;
231
232 if unsafe { change_hexrays_config(s.as_ptr()) } {
233 Ok(())
234 } else {
235 Err(IDAError::ffi_with(format!(
236 "failed to apply hexrays config directive `{directive}`"
237 )))
238 }
239 }
240
241 pub fn meta(&self) -> Metadata<'_> {
242 Metadata::new()
243 }
244
245 pub fn meta_mut(&mut self) -> MetadataMut<'_> {
246 MetadataMut::new()
247 }
248
249 pub fn processor(&self) -> Processor<'_> {
250 let ptr = unsafe { get_ph() };
251 Processor::from_ptr(ptr)
252 }
253
254 pub fn entries(&self) -> EntryPointIter<'_> {
255 let limit = unsafe { get_entry_qty() };
256 EntryPointIter {
257 index: 0,
258 limit,
259 _marker: PhantomData,
260 }
261 }
262
263 pub fn function_at(&self, ea: Address) -> Option<Function<'_>> {
264 let ptr = unsafe { get_func(ea.into()) };
265
266 if ptr.is_null() {
267 return None;
268 }
269
270 Some(Function::from_ptr(ptr))
271 }
272
273 pub fn add_function(&mut self, start: Address) -> Result<(), IDAError> {
274 self.add_function_with(start, BADADDR.into())
275 }
276
277 pub fn add_function_with(&mut self, start: Address, end: Address) -> Result<(), IDAError> {
278 if unsafe { add_func(start.into(), end.into()) } {
279 Ok(())
280 } else {
281 Err(IDAError::ffi_with(format!(
282 "failed to add function at {start:#x}"
283 )))
284 }
285 }
286
287 pub fn remove_function(&mut self, start: Address) -> Result<(), IDAError> {
288 if unsafe { del_func(start.into()) } {
289 Ok(())
290 } else {
291 Err(IDAError::ffi_with(format!(
292 "failed to delete function at {start:#x}"
293 )))
294 }
295 }
296
297 pub fn next_head(&self, ea: Address) -> Option<Address> {
298 self.next_head_with(ea, BADADDR.into())
299 }
300
301 pub fn next_head_with(&self, ea: Address, max_ea: Address) -> Option<Address> {
302 let next = unsafe { next_head(ea.into(), max_ea.into()) };
303 if next == BADADDR {
304 None
305 } else {
306 Some(next.into())
307 }
308 }
309
310 pub fn prev_head(&self, ea: Address) -> Option<Address> {
311 self.prev_head_with(ea, 0)
312 }
313
314 pub fn prev_head_with(&self, ea: Address, min_ea: Address) -> Option<Address> {
315 let prev = unsafe { prev_head(ea.into(), min_ea.into()) };
316 if prev == BADADDR {
317 None
318 } else {
319 Some(prev.into())
320 }
321 }
322
323 pub fn insn_at(&self, ea: Address) -> Option<Insn> {
324 let insn = decode(ea.into())?;
325 Some(Insn::from_repr(insn))
326 }
327
328 pub fn decompile<'a>(&'a self, f: &Function<'a>) -> Result<CFunction<'a>, IDAError> {
329 self.decompile_with(f, false)
330 }
331
332 pub fn decompile_with<'a>(
333 &'a self,
334 f: &Function<'a>,
335 all_blocks: bool,
336 ) -> Result<CFunction<'a>, IDAError> {
337 if !self.decompiler {
338 return Err(IDAError::ffi_with("no decompiler available"));
339 }
340
341 Ok(unsafe {
342 decompile_function(f.start_address().into(), all_blocks)
343 .map(|f| CFunction::new(f).expect("null pointer checked"))?
344 })
345 }
346
347 pub fn function_by_id(&self, id: FunctionId) -> Option<Function<'_>> {
348 let ptr = unsafe { getn_func(id) };
349
350 if ptr.is_null() {
351 return None;
352 }
353
354 Some(Function::from_ptr(ptr))
355 }
356
357 pub fn functions<'a>(&'a self) -> impl Iterator<Item = (FunctionId, Function<'a>)> + 'a {
358 (0..self.function_count()).filter_map(|id| self.function_by_id(id).map(|f| (id, f)))
359 }
360
361 pub fn function_count(&self) -> usize {
362 unsafe { get_func_qty() }
363 }
364
365 pub fn segment_at(&self, ea: Address) -> Option<Segment<'_>> {
366 let ptr = unsafe { getseg(ea.into()) };
367
368 if ptr.is_null() {
369 return None;
370 }
371
372 Some(Segment::from_ptr(ptr))
373 }
374
375 pub fn segment_by_id(&self, id: SegmentId) -> Option<Segment<'_>> {
376 let ptr = unsafe { getnseg((id as i32).into()) };
377
378 if ptr.is_null() {
379 return None;
380 }
381
382 Some(Segment::from_ptr(ptr))
383 }
384
385 pub fn segment_by_name(&self, name: impl AsRef<str>) -> Option<Segment<'_>> {
386 let s = CString::new(name.as_ref()).ok()?;
387 let ptr = unsafe { get_segm_by_name(s.as_ptr()) };
388
389 if ptr.is_null() {
390 return None;
391 }
392
393 Some(Segment::from_ptr(ptr))
394 }
395
396 pub fn segments<'a>(&'a self) -> impl Iterator<Item = (SegmentId, Segment<'a>)> + 'a {
397 (0..self.segment_count()).filter_map(|id| self.segment_by_id(id).map(|s| (id, s)))
398 }
399
400 pub fn segment_count(&self) -> usize {
401 unsafe { get_segm_qty().0 as _ }
402 }
403
404 pub fn register_by_name(&self, name: impl AsRef<str>) -> Option<Register> {
405 let s = CString::new(name.as_ref()).ok()?;
406 let id = unsafe { str2reg(s.as_ptr()).0 };
407
408 if id == -1 { None } else { Some(id as _) }
409 }
410
411 pub fn insn_alignment_at(&self, ea: Address) -> Option<usize> {
412 let align = unsafe { is_align_insn(ea.into()).0 };
413 if align == 0 { None } else { Some(align as _) }
414 }
415
416 pub fn first_xref_from(&self, ea: Address, flags: XRefQuery) -> Option<XRef<'_>> {
417 let mut xref = MaybeUninit::<xrefblk_t>::zeroed();
418 let found =
419 unsafe { xrefblk_t_first_from(xref.as_mut_ptr(), ea.into(), flags.bits().into()) };
420
421 if found {
422 Some(XRef::from_repr(unsafe { xref.assume_init() }))
423 } else {
424 None
425 }
426 }
427
428 pub fn first_xref_to(&self, ea: Address, flags: XRefQuery) -> Option<XRef<'_>> {
429 let mut xref = MaybeUninit::<xrefblk_t>::zeroed();
430 let found =
431 unsafe { xrefblk_t_first_to(xref.as_mut_ptr(), ea.into(), flags.bits().into()) };
432
433 if found {
434 Some(XRef::from_repr(unsafe { xref.assume_init() }))
435 } else {
436 None
437 }
438 }
439
440 pub fn get_cmt(&self, ea: Address) -> Option<String> {
441 self.get_cmt_with(ea, false)
442 }
443
444 pub fn get_cmt_with(&self, ea: Address, rptble: bool) -> Option<String> {
445 let s = unsafe { idalib_get_cmt(ea.into(), rptble) };
446
447 if s.is_empty() { None } else { Some(s) }
448 }
449
450 pub fn get_func_cmt(&self, ea: Address) -> Option<String> {
451 self.get_func_cmt_with(ea, false)
452 }
453
454 pub fn get_func_cmt_with(&self, ea: Address, rptble: bool) -> Option<String> {
455 let f = self.function_at(ea)?;
456 let s = unsafe { idalib_get_func_cmt(f.as_ptr() as _, rptble) }.ok()?;
457
458 if s.is_empty() { None } else { Some(s) }
459 }
460
461 pub fn set_cmt(&self, ea: Address, comm: impl AsRef<str>) -> Result<(), IDAError> {
462 self.set_cmt_with(ea, comm, false)
463 }
464
465 pub fn set_cmt_with(
466 &self,
467 ea: Address,
468 comm: impl AsRef<str>,
469 rptble: bool,
470 ) -> Result<(), IDAError> {
471 let s = CString::new(comm.as_ref()).map_err(IDAError::ffi)?;
472 if unsafe { set_cmt(ea.into(), s.as_ptr(), rptble) } {
473 Ok(())
474 } else {
475 Err(IDAError::ffi_with(format!(
476 "failed to set comment at {ea:#x}"
477 )))
478 }
479 }
480
481 pub fn set_func_cmt(&self, ea: Address, comm: impl AsRef<str>) -> Result<(), IDAError> {
482 self.set_func_cmt_with(ea, comm, false)
483 }
484
485 pub fn set_func_cmt_with(
486 &self,
487 ea: Address,
488 comm: impl AsRef<str>,
489 rptble: bool,
490 ) -> Result<(), IDAError> {
491 let f = self
492 .function_at(ea)
493 .ok_or_else(|| IDAError::ffi_with(format!("no function found at address {ea:#x}")))?;
494 let s = CString::new(comm.as_ref()).map_err(IDAError::ffi)?;
495 if unsafe { idalib_set_func_cmt(f.as_ptr() as _, s.as_ptr(), rptble) } {
496 Ok(())
497 } else {
498 Err(IDAError::ffi_with(format!(
499 "failed to set function comment at {ea:#x}"
500 )))
501 }
502 }
503
504 pub fn append_cmt(&self, ea: Address, comm: impl AsRef<str>) -> Result<(), IDAError> {
505 self.append_cmt_with(ea, comm, false)
506 }
507
508 pub fn append_cmt_with(
509 &self,
510 ea: Address,
511 comm: impl AsRef<str>,
512 rptble: bool,
513 ) -> Result<(), IDAError> {
514 let s = CString::new(comm.as_ref()).map_err(IDAError::ffi)?;
515 if unsafe { append_cmt(ea.into(), s.as_ptr(), rptble) } {
516 Ok(())
517 } else {
518 Err(IDAError::ffi_with(format!(
519 "failed to append comment at {ea:#x}"
520 )))
521 }
522 }
523
524 pub fn remove_cmt(&self, ea: Address) -> Result<(), IDAError> {
525 self.remove_cmt_with(ea, false)
526 }
527
528 pub fn remove_cmt_with(&self, ea: Address, rptble: bool) -> Result<(), IDAError> {
529 if unsafe { set_cmt(ea.into(), c"".as_ptr(), rptble) } {
530 Ok(())
531 } else {
532 Err(IDAError::ffi_with(format!(
533 "failed to remove comment at {ea:#x}"
534 )))
535 }
536 }
537
538 pub fn remove_func_cmt(&self, ea: Address) -> Result<(), IDAError> {
539 self.remove_func_cmt_with(ea, false)
540 }
541
542 pub fn remove_func_cmt_with(&self, ea: Address, rptble: bool) -> Result<(), IDAError> {
543 let f = self
544 .function_at(ea)
545 .ok_or_else(|| IDAError::ffi_with(format!("no function found at address {ea:#x}")))?;
546 if unsafe { idalib_set_func_cmt(f.as_ptr(), c"".as_ptr(), rptble) } {
547 Ok(())
548 } else {
549 Err(IDAError::ffi_with(format!(
550 "failed to remove comment at {ea:#x}"
551 )))
552 }
553 }
554
555 pub fn bookmarks(&self) -> Bookmarks<'_> {
556 Bookmarks::new(self)
557 }
558
559 pub fn find_text(&self, start_ea: Address, text: impl AsRef<str>) -> Option<Address> {
560 let s = CString::new(text.as_ref()).ok()?;
561 let addr = unsafe { idalib_find_text(start_ea.into(), s.as_ptr()) };
562 if addr == BADADDR {
563 None
564 } else {
565 Some(addr.into())
566 }
567 }
568
569 pub fn find_text_iter<'a, T>(&'a self, text: T) -> impl Iterator<Item = Address> + 'a
570 where
571 T: AsRef<str> + 'a,
572 {
573 let mut cur = Some(0u64);
574 std::iter::from_fn(move || {
575 let found = self.find_text(cur?, text.as_ref())?;
576 cur = self.find_defined(found);
577 Some(found)
578 })
579 }
580
581 pub fn find_imm(&self, start_ea: Address, imm: u32) -> Option<Address> {
582 let addr = unsafe { idalib_find_imm(start_ea.into(), imm.into()) };
583 if addr == BADADDR {
584 None
585 } else {
586 Some(addr.into())
587 }
588 }
589
590 pub fn find_imm_iter<'a>(&'a self, imm: u32) -> impl Iterator<Item = Address> + 'a {
591 let mut cur = 0u64;
592 std::iter::from_fn(move || {
593 cur = self.find_imm(cur, imm)?;
594 Some(cur)
595 })
596 }
597
598 pub fn find_defined(&self, start_ea: Address) -> Option<Address> {
599 let addr = unsafe { idalib_find_defined(start_ea.into()) };
600 if addr == BADADDR {
601 None
602 } else {
603 Some(addr.into())
604 }
605 }
606
607 pub fn strings(&self) -> StringList<'_> {
608 StringList::new(self)
609 }
610
611 pub fn names(&self) -> crate::name::NameList<'_> {
612 NameList::new(self)
613 }
614
615 pub fn address_to_string(&self, ea: Address) -> Option<String> {
616 let s = unsafe { idalib_ea2str(ea.into()) };
617
618 if s.is_empty() { None } else { Some(s) }
619 }
620
621 pub fn flags_at(&self, ea: Address) -> AddressFlags<'_> {
622 AddressFlags::new(unsafe { get_flags(ea.into()) })
623 }
624
625 pub fn image_base(&self) -> Address {
626 unsafe { get_imagebase() }.into()
627 }
628
629 pub fn get_byte(&self, ea: Address) -> u8 {
630 unsafe { idalib_get_byte(ea.into()) }
631 }
632
633 pub fn get_word(&self, ea: Address) -> u16 {
634 unsafe { idalib_get_word(ea.into()) }
635 }
636
637 pub fn get_dword(&self, ea: Address) -> u32 {
638 unsafe { idalib_get_dword(ea.into()) }
639 }
640
641 pub fn get_qword(&self, ea: Address) -> u64 {
642 unsafe { idalib_get_qword(ea.into()) }
643 }
644
645 pub fn get_bytes(&self, ea: Address, size: usize) -> Vec<u8> {
646 let mut buf = Vec::with_capacity(size);
647
648 let Ok(new_len) = (unsafe { idalib_get_bytes(ea.into(), &mut buf) }) else {
649 return Vec::with_capacity(0);
650 };
651
652 unsafe {
653 buf.set_len(new_len);
654 }
655
656 buf
657 }
658
659 pub fn find_plugin(
660 &self,
661 name: impl AsRef<str>,
662 load_if_needed: bool,
663 ) -> Result<Plugin<'_>, IDAError> {
664 let plugin = CString::new(name.as_ref()).map_err(IDAError::ffi)?;
665 let ptr = unsafe { find_plugin(plugin.as_ptr(), load_if_needed) };
666
667 if ptr.is_null() {
668 Err(IDAError::ffi_with(format!(
669 "failed to load {} plugin",
670 name.as_ref()
671 )))
672 } else {
673 Ok(Plugin::from_ptr(ptr as *const _))
674 }
675 }
676
677 pub fn load_plugin(&self, name: impl AsRef<str>) -> Result<Plugin<'_>, IDAError> {
678 self.find_plugin(name, true)
679 }
680}
681
682#[cfg(not(feature = "plugin"))]
683impl Drop for IDB {
684 fn drop(&mut self) {
685 if self.decompiler {
686 unsafe {
687 term_hexrays_plugin();
688 }
689 }
690 close_database_with(self.save);
691 }
692}
693
694pub struct EntryPointIter<'a> {
695 index: usize,
696 limit: usize,
697 _marker: PhantomData<&'a IDB>,
698}
699
700impl<'a> Iterator for EntryPointIter<'a> {
701 type Item = Address;
702
703 fn next(&mut self) -> Option<Self::Item> {
704 while self.index < self.limit {
705 let index = self.index;
706 self.index += 1;
707
708 let ordinal = unsafe { get_entry_ordinal(index) };
709 let addr = unsafe { get_entry(ordinal) };
710
711 if addr != BADADDR {
712 return Some(addr.into());
713 }
714 }
715
716 None
717 }
718
719 fn size_hint(&self) -> (usize, Option<usize>) {
720 let lim = self.limit - self.index;
721 (0, Some(lim))
722 }
723}