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}