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