idalib/
bookmarks.rs

1use std::ffi::CString;
2use std::marker::PhantomData;
3
4use crate::ffi::bookmarks::{
5    idalib_bookmarks_t_erase, idalib_bookmarks_t_find_index, idalib_bookmarks_t_get,
6    idalib_bookmarks_t_get_desc, idalib_bookmarks_t_mark, idalib_bookmarks_t_size,
7};
8use crate::ffi::BADADDR;
9
10use crate::idb::IDB;
11use crate::{Address, IDAError};
12
13pub type BookmarkIndex = u32;
14
15const BOOKMARKS_BAD_INDEX: BookmarkIndex = 0xffffffff; // (uint32(-1))
16
17pub struct Bookmarks<'a> {
18    _marker: PhantomData<&'a IDB>,
19}
20
21impl<'a> Bookmarks<'a> {
22    pub(crate) fn new(_: &'a IDB) -> Self {
23        Self {
24            _marker: PhantomData,
25        }
26    }
27
28    pub fn mark(&self, ea: Address, desc: impl AsRef<str>) -> Result<BookmarkIndex, IDAError> {
29        self.mark_with(ea, self.len(), desc)
30    }
31
32    // Notes:
33    // * Adding a bookmark at an already marked address creates an overlaid bookmark
34    // * Adding a bookmark at an already used index has no effect and no error is returned
35    // * Adding a bookmark at an index > `len()` increments `len()` accordingly, while leaving
36    //   the unused bookmark slots empty
37    // * The `MAX_MARK_SLOT` limit (1024) doesn't seem to play an actual role ¯\_(ツ)_/¯
38    pub fn mark_with(
39        &self,
40        ea: Address,
41        idx: BookmarkIndex,
42        desc: impl AsRef<str>,
43    ) -> Result<BookmarkIndex, IDAError> {
44        let desc = CString::new(desc.as_ref()).map_err(IDAError::ffi)?;
45
46        let slot = unsafe { idalib_bookmarks_t_mark(ea.into(), idx.into(), desc.as_ptr()) };
47
48        if slot != BOOKMARKS_BAD_INDEX {
49            Ok(slot)
50        } else {
51            Err(IDAError::ffi_with(format!(
52                "failed to set bookmark at address {ea:#x}, index {idx}"
53            )))
54        }
55    }
56
57    pub fn get_description(&self, ea: Address) -> Option<String> {
58        self.get_description_by_index(self.find_index(ea)?)
59    }
60
61    // Note: The `bookmarks_t::get` function is used here only to get the address
62    pub fn get_address(&self, idx: BookmarkIndex) -> Option<Address> {
63        let addr = unsafe { idalib_bookmarks_t_get(idx.into()) };
64
65        if addr == BADADDR {
66            None
67        } else {
68            Some(addr.into())
69        }
70    }
71
72    // Note: The address parameter has been removed because it is unused by IDA's API
73    pub fn get_description_by_index(&self, idx: BookmarkIndex) -> Option<String> {
74        let s = unsafe { idalib_bookmarks_t_get_desc(idx.into()) };
75
76        if s.is_empty() {
77            None
78        } else {
79            Some(s)
80        }
81    }
82
83    pub fn erase(&self, ea: Address) -> Result<(), IDAError> {
84        self.erase_by_index(self.find_index(ea).ok_or(IDAError::ffi_with(format!(
85            "failed to find bookmark index for address {ea:#x}"
86        )))?)
87    }
88
89    // Notes:
90    // * When a bookmark is erased, all the following indexes are decremented to fill the gap
91    // * The address parameter has been removed because it is unused by IDA's API
92    pub fn erase_by_index(&self, idx: BookmarkIndex) -> Result<(), IDAError> {
93        // Prevent IDA's internal error 1312 that triggers when an invalid index is supplied
94        if idx >= self.len() {
95            return Err(IDAError::ffi_with(format!(
96                "failed to erase bookmark at index {idx}"
97            )));
98        }
99        if unsafe { idalib_bookmarks_t_erase(idx.into()) } {
100            Ok(())
101        } else {
102            Err(IDAError::ffi_with(format!(
103                "failed to erase bookmark at index {idx}"
104            )))
105        }
106    }
107
108    pub fn find_index(&self, ea: Address) -> Option<BookmarkIndex> {
109        let index = unsafe { idalib_bookmarks_t_find_index(ea.into()) };
110
111        if index != BOOKMARKS_BAD_INDEX {
112            Some(index)
113        } else {
114            None
115        }
116    }
117
118    // Note: The address parameter has been removed because it is unused by IDA's API
119    pub fn len(&self) -> BookmarkIndex {
120        unsafe { idalib_bookmarks_t_size() }
121    }
122
123    pub fn is_empty(&self) -> bool {
124        self.len() == 0
125    }
126}