qemu_plugin/plugin/
mod.rs

1//! Traits and helpers enabling idiomatic QEMU plugin implementation
2
3use std::sync::{Mutex, OnceLock};
4
5use crate::{
6    PluginId, TranslationBlock, VCPUIndex,
7    install::{Args, Info},
8};
9use crate::{
10    qemu_plugin_register_flush_cb, qemu_plugin_register_vcpu_exit_cb,
11    qemu_plugin_register_vcpu_idle_cb, qemu_plugin_register_vcpu_init_cb,
12    qemu_plugin_register_vcpu_resume_cb, qemu_plugin_register_vcpu_syscall_cb,
13    qemu_plugin_register_vcpu_syscall_ret_cb, qemu_plugin_register_vcpu_tb_trans_cb,
14};
15
16/// Handler for callbacks registered via the `qemu_plugin_register_vcpu_init_cb`
17/// function. These callbacks are called when a vCPU is initialized in QEMU (in softmmu
18/// mode only) and notify us which vCPU index is newly initialized.
19extern "C" fn handle_qemu_plugin_register_vcpu_init_cb(id: PluginId, vcpu_id: VCPUIndex) {
20    let Some(plugin) = PLUGIN.get() else {
21        panic!("Plugin not set");
22    };
23
24    let Ok(mut plugin) = plugin.lock() else {
25        panic!("Failed to lock plugin");
26    };
27
28    plugin
29        .on_vcpu_init(id, vcpu_id)
30        .expect("Failed running callback on_vcpu_init");
31}
32
33/// Handler for callbacks registered via the `qemu_plugin_register_vcpu_exit_cb`
34/// function. These callbacks are called when a vCPU exits in QEMU (in softmmu mode
35/// only) and notify us which vCPU index is exiting.
36extern "C" fn handle_qemu_plugin_register_vcpu_exit_cb(id: PluginId, vcpu_id: VCPUIndex) {
37    let Some(plugin) = PLUGIN.get() else {
38        panic!("Plugin not set");
39    };
40
41    let Ok(mut plugin) = plugin.lock() else {
42        panic!("Failed to lock plugin");
43    };
44
45    plugin
46        .on_vcpu_exit(id, vcpu_id)
47        .expect("Failed running callback on_vcpu_exit");
48}
49
50/// Handler for callbacks registered via the `qemu_plugin_register_vcpu_idle_cb`
51/// function. These callbacks are called when a vCPU goes idle in QEMU (in softmmu mode
52/// only) and notify us which vCPU index is going idle.
53extern "C" fn handle_qemu_plugin_register_vcpu_idle_cb(id: PluginId, vcpu_id: VCPUIndex) {
54    let Some(plugin) = PLUGIN.get() else {
55        panic!("Plugin not set");
56    };
57
58    let Ok(mut plugin) = plugin.lock() else {
59        panic!("Failed to lock plugin");
60    };
61
62    plugin
63        .on_vcpu_idle(id, vcpu_id)
64        .expect("Failed running callback on_vcpu_idle");
65}
66
67/// Handler for callbacks registered via the `qemu_plugin_register_vcpu_resume_cb`
68/// function. These callbacks are called when a vCPU resumes in QEMU (in softmmu mode
69/// only) and notify us which vCPU index is resuming.
70extern "C" fn handle_qemu_plugin_register_vcpu_resume_cb(id: PluginId, vcpu_id: VCPUIndex) {
71    let Some(plugin) = PLUGIN.get() else {
72        panic!("Plugin not set");
73    };
74
75    let Ok(mut plugin) = plugin.lock() else {
76        panic!("Failed to lock plugin");
77    };
78
79    plugin
80        .on_vcpu_resume(id, vcpu_id)
81        .expect("Failed running callback on_vcpu_resume");
82}
83
84/// Handler for callbacks registered via the `qemu_plugin_register_vcpu_tb_trans_cb`
85/// function. These callbacks are called when a translation block is translated in QEMU
86/// and pass an opaque pointer to the translation block.
87extern "C" fn handle_qemu_plugin_register_vcpu_tb_trans_cb(
88    id: PluginId,
89    tb: *mut crate::sys::qemu_plugin_tb,
90) {
91    let Some(plugin) = PLUGIN.get() else {
92        panic!("Plugin not set");
93    };
94
95    let Ok(mut plugin) = plugin.lock() else {
96        panic!("Failed to lock plugin");
97    };
98
99    let tb = TranslationBlock::from(tb);
100
101    plugin
102        .on_translation_block_translate(id, tb)
103        .expect("Failed running callback on_translation_block_translate");
104}
105
106/// Handler for callbacks registered via the `qemu_plugin_register_flush_cb`
107/// function. These callbacks are called when QEMU flushes all TBs, which is
108/// roughly equivalent to a TLB flush to invalidate all cached instructions.
109extern "C" fn handle_qemu_plugin_register_flush_cb(id: PluginId) {
110    let Some(plugin) = PLUGIN.get() else {
111        panic!("Plugin not set");
112    };
113
114    let Ok(mut plugin) = plugin.lock() else {
115        panic!("Failed to lock plugin");
116    };
117
118    plugin
119        .on_flush(id)
120        .expect("Failed running callback on_flush");
121}
122
123/// Handler for callbacks registered via the `qemu_plugin_register_vcpu_syscall_cb`
124/// function. These callbacks are called when a syscall is made in QEMU and pass the
125/// syscall number and its arguments.
126extern "C" fn handle_qemu_plugin_register_syscall_cb(
127    id: PluginId,
128    vcpu_index: VCPUIndex,
129    num: i64,
130    a1: u64,
131    a2: u64,
132    a3: u64,
133    a4: u64,
134    a5: u64,
135    a6: u64,
136    a7: u64,
137    a8: u64,
138) {
139    let Some(plugin) = PLUGIN.get() else {
140        panic!("Plugin not set");
141    };
142
143    let Ok(mut plugin) = plugin.lock() else {
144        panic!("Failed to lock plugin");
145    };
146
147    plugin
148        .on_syscall(id, vcpu_index, num, a1, a2, a3, a4, a5, a6, a7, a8)
149        .expect("Failed running callback on_syscall");
150}
151
152/// Handler for callbacks registered via the `qemu_plugin_register_vcpu_syscall_ret_cb`
153/// function. These callbacks are called when a syscall returns in QEMU and pass the
154/// syscall number and its return value.
155extern "C" fn handle_qemu_plugin_register_syscall_ret_cb(
156    id: PluginId,
157    vcpu_index: VCPUIndex,
158    num: i64,
159    ret: i64,
160) {
161    let Some(plugin) = PLUGIN.get() else {
162        panic!("Plugin not set");
163    };
164
165    let Ok(mut plugin) = plugin.lock() else {
166        panic!("Failed to lock plugin");
167    };
168
169    plugin
170        .on_syscall_return(id, vcpu_index, num, ret)
171        .expect("Failed running callback on_syscall_return");
172}
173
174/// Trait which implemenents registering the callbacks implemented on a struct which
175/// `HasCallbacks` with QEMU
176///
177/// # Example
178///
179/// Using default registration, you can simply declare an empty `impl` block for
180/// `Register` on your type. Then, on plugin load, any callbacks you implement in
181/// `HasCallbacks` will be automatically registered with QEMU. Callback events you don't
182/// implement will default to no-ops. The only drawback of this approach is a small
183/// performance penalty for events even when there is no callback registered for them.
184///
185/// ```
186/// struct MyPlugin;
187///
188/// impl qemu_plugin::plugin::HasCallbacks for MyPlugin {
189///     fn on_translation_block_translate(&mut self, _: qemu_plugin::PluginId, tb: qemu_plugin::TranslationBlock) -> anyhow::Result<()> {
190///         println!("Translation block translated");
191///         Ok(())
192///     }
193/// }
194///
195/// impl qemu_plugin::plugin::Register for MyPlugin {}
196/// ```
197///
198/// For more granular control or to register your own callback handlers, you can
199/// implement the `register` method yourself.
200///
201/// ```
202/// struct MyPlugin;
203///
204/// impl qemu_plugin::plugin::HasCallbacks for MyPlugin {}
205/// impl qemu_plugin::plugin::Register for MyPlugin {
206///    fn register(&mut self, id: qemu_plugin::PluginId, args: &qemu_plugin::install::Args, info: &qemu_plugin::install::Info) -> Result<(), anyhow::Error> {
207///       // Custom registration logic here
208///       Ok(())
209///    }
210/// }
211/// ```
212///
213/// Finally, if you want to override the default registration behavior, you can
214/// implement `register_default` yourself. This allows you to circumvent any minor
215/// performance penalties.
216///
217/// ```
218/// struct MyPlugin;
219///
220/// impl qemu_plugin::plugin::HasCallbacks for MyPlugin {}
221/// impl qemu_plugin::plugin::Register for MyPlugin {
222///     fn register_default(
223///        &mut self,   
224///        id: qemu_plugin::PluginId,
225///        args: &qemu_plugin::install::Args,
226///        info: &qemu_plugin::install::Info,
227///     ) -> Result<(), anyhow::Error> {
228///         // Custom registration logic here, maybe registering a different
229///         // function as a callback rather than using `HasCallbacks`
230///         Ok(())
231///    }
232/// }
233/// ```
234///
235pub trait Register: HasCallbacks + Send + Sync + 'static {
236    #[allow(unused)]
237    /// Called by QEMU when registering the plugin. This method should only be overridden if no
238    /// default callbacks are desired, and will require re-implementing handlers which is not
239    /// recommended.
240    fn register_default(
241        &mut self,
242        id: PluginId,
243        args: &Args,
244        info: &Info,
245    ) -> Result<(), anyhow::Error> {
246        qemu_plugin_register_vcpu_init_cb(id, Some(handle_qemu_plugin_register_vcpu_init_cb))?;
247
248        qemu_plugin_register_vcpu_exit_cb(id, Some(handle_qemu_plugin_register_vcpu_exit_cb))?;
249
250        qemu_plugin_register_vcpu_idle_cb(id, Some(handle_qemu_plugin_register_vcpu_idle_cb))?;
251
252        qemu_plugin_register_vcpu_resume_cb(id, Some(handle_qemu_plugin_register_vcpu_resume_cb))?;
253
254        qemu_plugin_register_vcpu_tb_trans_cb(
255            id,
256            Some(handle_qemu_plugin_register_vcpu_tb_trans_cb),
257        )?;
258
259        qemu_plugin_register_flush_cb(id, Some(handle_qemu_plugin_register_flush_cb));
260
261        qemu_plugin_register_vcpu_syscall_cb(id, Some(handle_qemu_plugin_register_syscall_cb));
262
263        qemu_plugin_register_vcpu_syscall_ret_cb(
264            id,
265            Some(handle_qemu_plugin_register_syscall_ret_cb),
266        );
267
268        self.register(id, args, info)?;
269
270        Ok(())
271    }
272
273    #[allow(unused)]
274    /// Called when registering the plugin. User definition of on-registration behavior should
275    /// be implemented here.
276    fn register(&mut self, id: PluginId, args: &Args, info: &Info) -> Result<(), anyhow::Error> {
277        Ok(())
278    }
279}
280
281/// Trait implemented by structs which have callbacks which should be registered with QEMU.
282///
283/// # Example
284///
285/// ```
286/// struct MyPlugin;
287///
288/// impl qemu_plugin::plugin::HasCallbacks for MyPlugin {
289///     // This callback will be registered on plugin load
290///     fn on_translation_block_translate(&mut self, _: qemu_plugin::PluginId, tb: qemu_plugin::TranslationBlock) -> anyhow::Result<()> {
291///         println!("Translation block translated");
292///         Ok(())
293///     }
294/// }
295///
296/// impl qemu_plugin::plugin::Register for MyPlugin {}
297/// ```
298pub trait HasCallbacks: Send + Sync + 'static {
299    #[allow(unused)]
300    /// Callback triggered on vCPU init
301    ///
302    /// # Arguments
303    ///
304    /// * `id` - The ID of the plugin
305    /// * `vcpu_id` - The ID of the vCPU
306    ///
307    /// # Example
308    ///
309    /// ```
310    /// struct MyPlugin;
311    ///
312    /// impl qemu_plugin::plugin::HasCallbacks for MyPlugin {
313    ///     fn on_vcpu_init(&mut self, id: qemu_plugin::PluginId, vcpu_id: qemu_plugin::VCPUIndex) -> Result<(), anyhow::Error> {
314    ///         println!("vCPU {} initialized for plugin {}", vcpu_id, id);
315    ///         Ok(())
316    ///     }
317    /// }
318    /// ```
319    /// struct MyPlugin;
320    fn on_vcpu_init(&mut self, id: PluginId, vcpu_id: VCPUIndex) -> Result<(), anyhow::Error> {
321        Ok(())
322    }
323
324    #[allow(unused)]
325    /// Callback triggered on vCPU exit
326    ///
327    /// # Arguments
328    ///
329    /// * `id` - The ID of the plugin
330    /// * `vcpu_id` - The ID of the vCPU
331    ///
332    /// # Example
333    fn on_vcpu_exit(&mut self, id: PluginId, vcpu_id: VCPUIndex) -> Result<(), anyhow::Error> {
334        Ok(())
335    }
336
337    #[allow(unused)]
338    /// Callback triggered on vCPU idle
339    ///
340    /// # Arguments
341    ///
342    /// * `id` - The ID of the plugin
343    /// * `vcpu_id` - The ID of the vCPU
344    fn on_vcpu_idle(&mut self, id: PluginId, vcpu_id: VCPUIndex) -> Result<(), anyhow::Error> {
345        Ok(())
346    }
347
348    #[allow(unused)]
349    /// Callback triggered on vCPU resume
350    ///
351    /// # Arguments
352    ///
353    /// * `id` - The ID of the plugin
354    /// * `vcpu_id` - The ID of the vCPU
355    fn on_vcpu_resume(&mut self, id: PluginId, vcpu_id: VCPUIndex) -> Result<(), anyhow::Error> {
356        Ok(())
357    }
358
359    #[allow(unused)]
360    /// Callback triggered on translation block translation
361    ///
362    /// # Arguments
363    ///
364    /// * `id` - The ID of the plugin
365    /// * `tb` - The translation block
366    fn on_translation_block_translate(
367        &mut self,
368        id: PluginId,
369        tb: TranslationBlock,
370    ) -> Result<(), anyhow::Error> {
371        Ok(())
372    }
373
374    #[allow(unused)]
375    /// Callback triggered on flush
376    ///
377    /// # Arguments
378    ///
379    /// * `id` - The ID of the plugin
380    fn on_flush(&mut self, id: PluginId) -> Result<(), anyhow::Error> {
381        Ok(())
382    }
383
384    #[allow(unused, clippy::too_many_arguments)]
385    /// Callback triggered on syscall
386    ///
387    /// # Arguments
388    ///
389    /// * `id` - The ID of the plugin
390    /// * `vcpu_index` - The ID of the vCPU
391    /// * `num` - The syscall number
392    /// * `a1` - The first syscall argument
393    /// * `a2` - The second syscall argument
394    /// * `a3` - The third syscall argument
395    /// * `a4` - The fourth syscall argument
396    /// * `a5` - The fifth syscall argument
397    /// * `a6` - The sixth syscall argument
398    /// * `a7` - The seventh syscall argument
399    /// * `a8` - The eighth syscall argument
400    fn on_syscall(
401        &mut self,
402        id: PluginId,
403        vcpu_index: VCPUIndex,
404        num: i64,
405        a1: u64,
406        a2: u64,
407        a3: u64,
408        a4: u64,
409        a5: u64,
410        a6: u64,
411        a7: u64,
412        a8: u64,
413    ) -> Result<(), anyhow::Error> {
414        Ok(())
415    }
416
417    #[allow(unused)]
418    /// Callback triggered on syscall return
419    ///
420    /// # Arguments
421    ///
422    /// * `id` - The ID of the plugin
423    /// * `vcpu_index` - The ID of the vCPU
424    /// * `num` - The syscall number
425    /// * `ret` - The return value of the syscall
426    fn on_syscall_return(
427        &mut self,
428        id: PluginId,
429        vcpu_index: VCPUIndex,
430        num: i64,
431        ret: i64,
432    ) -> Result<(), anyhow::Error> {
433        Ok(())
434    }
435}
436
437/// Trait implemented by structs which are QEMU plugin contexts
438pub trait Plugin: Register + HasCallbacks {}
439
440impl<T> Plugin for T where T: Register + HasCallbacks {}
441
442#[doc(hidden)]
443/// The global plugin item
444pub static PLUGIN: OnceLock<Mutex<Box<dyn Plugin>>> = OnceLock::new();
445
446#[doc(hidden)]
447#[inline(never)]
448pub fn register_plugin(plugin: impl Plugin) {
449    PLUGIN
450        .set(Mutex::new(Box::new(plugin)))
451        .map_err(|_| anyhow::anyhow!("Failed to set plugin"))
452        .expect("Failed to set plugin");
453}
454
455#[macro_export]
456/// Register a plugin
457macro_rules! register {
458    ($plugin:expr) => {
459        #[cfg_attr(target_os = "linux", unsafe(link_section = ".text.startup"))]
460        extern "C" fn __plugin_ctor() {
461            $crate::plugin::register_plugin($plugin);
462        }
463
464        #[used]
465        // .init_array.XXXXX sections are processed in lexicographical order
466        #[cfg_attr(target_os = "linux", unsafe(link_section = ".init_array"))]
467        // But there is no way to specify such an ordering on MacOS, even with
468        // __TEXT,__init_offsets
469        #[cfg_attr(
470            target_os = "macos",
471            unsafe(link_section = "__DATA,__mod_init_func,mod_init_funcs")
472        )]
473        // On Windows, it's from .CRT$XCA to .CRT$XCZ, where usually XCU =
474        // early, XCT = middle, XCL = late
475        #[cfg_attr(windows, unsafe(link_section = ".CRT$XCU"))]
476        static __PLUGIN_CTOR: unsafe extern "C" fn() = __plugin_ctor;
477    };
478}