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