Skip to main content

idalib_macros/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::quote;
4use syn::parse::{Parse, ParseStream};
5use syn::punctuated::Punctuated;
6use syn::{Expr, ExprLit, ItemImpl, Lit, LitCStr, Token, parse_macro_input};
7
8const PLUGIN_MOD: i32 = 0x0001;
9const PLUGIN_UNL: i32 = 0x0008;
10const PLUGIN_FIX: i32 = 0x0080;
11const PLUGIN_MULTI: i32 = 0x0100;
12
13#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
14enum PluginKind {
15    #[default]
16    Default,
17    Resident,
18    Oneshot,
19}
20
21struct PluginArgs {
22    name: String,
23    comment: Option<String>,
24    help: Option<String>,
25    hotkey: Option<String>,
26    version: Option<i32>,
27    kind: PluginKind,
28}
29
30impl Parse for PluginArgs {
31    fn parse(input: ParseStream) -> syn::Result<Self> {
32        let mut name = None;
33        let mut comment = None;
34        let mut help = None;
35        let mut hotkey = None;
36        let mut version = None;
37        let mut kind = PluginKind::Default;
38
39        let pairs = Punctuated::<syn::MetaNameValue, Token![,]>::parse_terminated(input)?;
40        for pair in pairs {
41            let Some(key) = pair.path.get_ident() else {
42                return Err(syn::Error::new_spanned(pair.path, "expected identifier"));
43            };
44
45            if key == "name" {
46                if let Expr::Lit(ExprLit {
47                    lit: Lit::Str(s), ..
48                }) = pair.value
49                {
50                    name = Some(s.value());
51                    continue;
52                } else {
53                    return Err(syn::Error::new_spanned(
54                        pair.value,
55                        "expected string literal",
56                    ));
57                }
58            }
59
60            if key == "comment" {
61                if let Expr::Lit(ExprLit {
62                    lit: Lit::Str(s), ..
63                }) = pair.value
64                {
65                    comment = Some(s.value());
66                    continue;
67                } else {
68                    return Err(syn::Error::new_spanned(
69                        pair.value,
70                        "expected string literal",
71                    ));
72                }
73            }
74
75            if key == "help" {
76                if let Expr::Lit(ExprLit {
77                    lit: Lit::Str(s), ..
78                }) = pair.value
79                {
80                    help = Some(s.value());
81                    continue;
82                } else {
83                    return Err(syn::Error::new_spanned(
84                        pair.value,
85                        "expected string literal",
86                    ));
87                }
88            }
89
90            if key == "hotkey" {
91                if let Expr::Lit(ExprLit {
92                    lit: Lit::Str(s), ..
93                }) = pair.value
94                {
95                    hotkey = Some(s.value());
96                    continue;
97                } else {
98                    return Err(syn::Error::new_spanned(
99                        pair.value,
100                        "expected string literal",
101                    ));
102                }
103            }
104
105            if key == "version" {
106                if let Expr::Lit(ExprLit {
107                    lit: Lit::Int(i), ..
108                }) = pair.value
109                {
110                    version = Some(i.base10_parse()?);
111                    continue;
112                } else {
113                    return Err(syn::Error::new_spanned(
114                        pair.value,
115                        "expected integer literal",
116                    ));
117                }
118            }
119
120            if key == "kind" {
121                if let Expr::Path(ref path) = pair.value
122                    && let Some(ident) = path.path.get_ident()
123                {
124                    kind = if ident == "default" {
125                        PluginKind::Default
126                    } else if ident == "resident" {
127                        PluginKind::Resident
128                    } else if ident == "oneshot" {
129                        PluginKind::Oneshot
130                    } else {
131                        return Err(syn::Error::new_spanned(
132                            ident,
133                            format!(
134                                "unknown kind `{ident}`, expected `default`, `resident`, or `oneshot`"
135                            ),
136                        ));
137                    };
138                    continue;
139                } else {
140                    return Err(syn::Error::new_spanned(
141                        &pair.value,
142                        "expected identifier: `default`, `resident`, or `oneshot`",
143                    ));
144                }
145            }
146
147            return Err(syn::Error::new_spanned(
148                &pair.path,
149                format!("unknown attribute `{key}`"),
150            ));
151        }
152
153        Ok(Self {
154            name: name.ok_or_else(|| syn::Error::new(input.span(), "missing `name` attribute"))?,
155            comment,
156            help,
157            hotkey,
158            version,
159            kind,
160        })
161    }
162}
163
164fn make_cstr_literal(s: &str) -> LitCStr {
165    let cstring = std::ffi::CString::new(s).expect("string contains null byte");
166    LitCStr::new(&cstring, Span::call_site())
167}
168
169#[proc_macro_attribute]
170pub fn plugin(attr: TokenStream, item: TokenStream) -> TokenStream {
171    let args = parse_macro_input!(attr as PluginArgs);
172    let impl_block = parse_macro_input!(item as ItemImpl);
173    let self_ty = &impl_block.self_ty;
174
175    let name = &args.name;
176    let name_cstr = make_cstr_literal(name);
177    let comment_cstr = make_cstr_literal(args.comment.as_deref().unwrap_or_default());
178    let help_cstr = make_cstr_literal(args.help.as_deref().unwrap_or_default());
179    let hotkey_cstr = make_cstr_literal(args.hotkey.as_deref().unwrap_or_default());
180
181    let base_flags = PLUGIN_MULTI | PLUGIN_MOD;
182    let kind_flag = match args.kind {
183        PluginKind::Default => 0,
184        PluginKind::Resident => PLUGIN_FIX,
185        PluginKind::Oneshot => PLUGIN_UNL,
186    };
187    let computed_flags = base_flags | kind_flag;
188    let version = args.version.unwrap_or(900);
189
190    let expanded = quote! {
191        #impl_block
192
193        extern "C" fn __idalib_plugin_init() -> *mut idalib::ffi::plugin::plugmod_t {
194            let mut idb = match idalib::IDB::current() {
195                Ok(idb) => idb,
196                Err(e) => {
197                    let _ = unsafe {
198                        idalib::ffi::ida::msg(&format!("[{}] plugin initialisation failed: {e}\n", #name))
199                    };
200                    return ::std::ptr::null_mut();
201                }
202            };
203
204            let mut ida = idalib::IDA::new(&idb);
205
206            match <#self_ty as idalib::plugin::IDAPlugin>::init(&mut ida, &mut idb) {
207                Ok(plugin) => {
208                    let wrapper = idalib::plugin::PlugmodWrapper::new(#name, plugin);
209                    let plugmod = Box::new(idalib::ffi::plugin::PlugMod::new(wrapper));
210                    unsafe { idalib::ffi::plugin::idalib_create_plugmod(plugmod) }
211                }
212                Err(e) => {
213                    ida.msg(&format!("[{}] plugin initialisation failed: {e}\n", #name)).ok();
214                    ::std::ptr::null_mut()
215                }
216            }
217        }
218
219        #[unsafe(no_mangle)]
220        pub static mut PLUGIN: idalib::ffi::plugin::plugin_t = idalib::ffi::plugin::plugin_t {
221            version: #version,
222            flags: #computed_flags,
223            init: Some(__idalib_plugin_init),
224            term: None,
225            run: None,
226            comment: #comment_cstr.as_ptr(),
227            help: #help_cstr.as_ptr(),
228            wanted_name: #name_cstr.as_ptr(),
229            wanted_hotkey: #hotkey_cstr.as_ptr(),
230        };
231    };
232
233    expanded.into()
234}