panda/plugins/mod.rs
1//! Bindings for various built-in PANDA plugins
2
3use crate::{sys::panda_require, ARCH_NAME};
4use libloading::Symbol;
5use once_cell::sync::OnceCell;
6use std::ffi::CString;
7use std::path::{Path, PathBuf};
8
9pub mod cosi;
10pub mod glib;
11pub mod guest_plugin_manager;
12pub mod hooks;
13pub mod hooks2;
14pub mod osi;
15pub mod proc_start_linux;
16
17#[cfg(not(feature = "ppc"))]
18pub mod syscalls2;
19
20/// A macro for importing an external PANDA plugin to use
21///
22/// **Note:** it is recommended that, if the plugin you want to use already has
23/// panda-rs bindings, they should be used instead. Those are located in the
24/// [`plugins`](crate::plugins) module, and typically include a note about where
25/// the high-level bindings for the given plugin are located.
26///
27/// ## Example Usage
28///
29/// ### Declaring bindings for free function in an external plugin:
30///
31/// ```
32/// plugin_import!{
33/// static OSI: Osi = extern "osi" {
34/// fn get_process_handles(cpu: *mut CPUState) -> GBoxedSlice<OsiProcHandle>;
35/// fn get_current_thread(cpu: *mut CPUState) -> GBox<OsiThread>;
36/// fn get_modules(cpu: *mut CPUState) -> GBoxedSlice<OsiModule>;
37/// fn get_mappings(cpu: *mut CPUState, p: *mut OsiProc) -> GBoxedSlice<OsiModule>;
38/// fn get_processes(cpu: *mut CPUState) -> GBoxedSlice<OsiProc>;
39/// fn get_current_process(cpu: *mut CPUState) -> GBox<OsiProc>;
40/// };
41/// }
42/// ```
43///
44/// This will create a lazy initialized static variable named `OSI` in the current
45/// scope. This static will include all of the functions listed as methods, when
46/// any function is run the plugin (the name of which is specified by `extern "osi"`)
47/// will be loaded on the fly before executing the method.
48///
49/// To load a plugin without running any function, `plugin_import` also automatically
50/// creates an `ensure_init` method which initializes the plugin without any other
51/// side effects.
52///
53/// ### Plugin Callbacks
54///
55/// Plugin-to-Plugin callbacks in PANDA are typically quite verbose to make bindings for
56/// by hand, so the `plugin_import` macro provides a shorthand for defining a function
57/// prototype for the callback and it will generate all the code needed to add and remove
58/// callbacks for it.
59///
60/// ```
61/// plugin_import! {
62/// static PROC_START_LINUX: ProcStartLinux = extern "proc_start_linux" {
63/// callbacks {
64/// fn on_rec_auxv(cpu: &mut CPUState, tb: &mut TranslationBlock, auxv: &AuxvValues);
65/// }
66/// };
67/// }
68/// ```
69///
70/// the above creates another lazy static which has the following methods for working with
71/// the `on_rec_auxv` callback:
72///
73/// * `add_callback_on_rec_auxv` - add a callback by function pointer
74/// * `remove_callback_on_rec_auxv` - remove a callback by function pointer
75///
76/// One requirement of these function pointers is that they must use the C ABI. So the
77/// argument for both methods would be of the type:
78///
79/// ```
80/// extern "C" fn (&mut CPUState, &mut TranslationBlock, &AuxvValues)
81/// ```
82///
83/// This macro will also generate a trait allowing any plugin-to-plugin callbacks to be
84/// used via the [`PppCallback`] API. So the above would generate
85/// a trait called `ProcStartLinuxCallbacks` which would have a method called `on_rec_auxv`,
86/// which is automatically implemented for [`PppCallback`].
87///
88/// [`PppCallback`]: crate::PppCallback
89#[macro_export]
90macro_rules! plugin_import {
91 {
92 $(
93 #[ $type_meta:meta ]
94 )*
95 static $static:ident : $ty:ident = extern $name:literal {
96 $(
97 $(
98 #[$meta:meta]
99 )*
100 fn $fn_name:ident
101 $(
102 <
103 $(
104 $lifetimes:lifetime
105 ),*
106 $(,)?
107 >
108 )?
109 (
110 $(
111 $arg_name:ident : $arg_ty:ty
112 ),*
113 $(,)?
114 ) $(-> $fn_ret:ty)?;
115 )*
116 $(
117 callbacks {
118 $(
119 fn $cb_fn_name:ident(
120 $(
121 $cb_arg_name:ident : $cb_arg_ty:ty
122 ),*
123 $(,)?
124 ) $(-> $cb_fn_ret:ty)?;
125 )*
126 }
127 )?
128 };
129 } => {
130 $(
131 #[ $type_meta ]
132 )*
133 pub struct $ty {
134 plugin: $crate::plugins::Plugin
135 }
136
137 impl $ty {
138 /// Create a new handle to this plugin
139 pub fn new() -> Self {
140 Self {
141 plugin: $crate::plugins::Plugin::new($name)
142 }
143 }
144
145 /// Load the plugin and initialize it if it hasn't been loaded already.
146 pub fn ensure_init(&self) {}
147
148 $(
149 $(
150 #[$meta]
151 )*
152 pub fn $fn_name $(< $($lifetimes),* >)? (&self $(, $arg_name : $arg_ty )*) $(-> $fn_ret)? {
153 unsafe {
154 self.plugin.get::<unsafe extern "C" fn($($arg_ty),*) $(-> $fn_ret)?>(
155 stringify!($fn_name)
156 )(
157 $(
158 $arg_name
159 ),*
160 )
161 }
162 }
163 )*
164
165 $($(
166 $crate::paste::paste!{
167 pub fn [<add_callback_ $cb_fn_name>](
168 &self,
169 callback: extern "C" fn(
170 $($cb_arg_name: $cb_arg_ty),*
171 )
172 )
173 {
174 let add_cb = self.plugin.get::<
175 extern "C" fn(
176 extern "C" fn(
177 $($cb_arg_ty),*
178 ) $(-> $cb_fn_ret)?
179 )
180 >(
181 concat!("ppp_add_cb_", stringify!($cb_fn_name))
182 );
183
184 add_cb(callback);
185 }
186
187 pub fn [<remove_callback_ $cb_fn_name>](
188 &self,
189 callback: extern "C" fn(
190 $($cb_arg_name: $cb_arg_ty),*
191 )
192 )
193 {
194 let remove_cb = self.plugin.get::<
195 extern "C" fn(
196 extern "C" fn(
197 $($cb_arg_ty),*
198 ) $(-> $cb_fn_ret)?
199 )
200 >(
201 concat!("ppp_remove_cb_", stringify!($cb_fn_name))
202 );
203
204 remove_cb(callback);
205 }
206
207 #[doc(hidden)]
208 pub fn [<add_callback_ $cb_fn_name _with_context>](
209 &self,
210 callback: unsafe extern "C" fn(
211 *mut std::ffi::c_void, $($cb_arg_name: $cb_arg_ty),*
212 ),
213 context: *mut std::ffi::c_void,
214 )
215 {
216 let add_cb = self.plugin.get::<
217 extern "C" fn(
218 unsafe extern "C" fn(
219 *mut std::ffi::c_void, $($cb_arg_ty),*
220 ) $(-> $cb_fn_ret)?,
221 *mut std::ffi::c_void,
222 )
223 >(
224 concat!("ppp_add_cb_", stringify!($cb_fn_name), "_with_context")
225 );
226
227 add_cb(callback, context);
228 }
229
230 #[doc(hidden)]
231 pub fn [<remove_callback_ $cb_fn_name _with_context>](
232 &self,
233 callback: unsafe extern "C" fn(
234 *mut std::ffi::c_void, $($cb_arg_name: $cb_arg_ty),*
235 ),
236 context: *mut std::ffi::c_void,
237 )
238 {
239 let remove_cb = self.plugin.get::<
240 extern "C" fn(
241 unsafe extern "C" fn(
242 *mut std::ffi::c_void, $($cb_arg_ty),*
243 ) $(-> $cb_fn_ret)?,
244 *mut std::ffi::c_void,
245 )
246 >(
247 concat!("ppp_remove_cb_", stringify!($cb_fn_name), "_with_context")
248 );
249
250 remove_cb(callback, context);
251 }
252 }
253 )*)?
254 }
255
256 $crate::lazy_static::lazy_static!{
257 $(
258 #[ $type_meta ]
259 )*
260 pub static ref $static: $ty = $ty::new();
261 }
262
263 $(
264 $crate::paste::paste!{
265 /// A trait for expressing the plugin-to-plugin callbacks provided by
266 /// the given plugin. See `panda::PppCallback` for more information,
267 /// as this is intended to be used as an extension trait for it.
268 pub trait [<$ty Callbacks>] {
269 $(
270 /// Installs the given closure over the callback slot provided
271 /// by the `panda::PppCallback` this is called on, setting it to
272 /// be run whenever the `
273 #[doc = stringify!($cb_fn_name)]
274 ///` callback is hit.
275 ///
276 /// ## Arguments
277 ///
278 $(
279 #[doc = "* `"]
280 #[doc = stringify!($cb_arg_name)]
281 #[doc = "` - `"]
282 #[doc = stringify!($cb_arg_ty)]
283 #[doc = "`"]
284 #[doc = ""]
285 )*
286 /// ## Example
287 ///
288 /// ```
289 /// use panda::PppCallback;
290 /// use panda::prelude::*;
291 #[doc = concat!(
292 "use /*...*/::",
293 stringify!($ty),
294 "Callbacks;"
295 )]
296 ///
297 #[doc = concat!(
298 "PppCallbacks::new()\n .",
299 stringify!($cb_fn_name),
300 "(|",
301 $(
302 stringify!($cb_arg_name),
303 ": ",
304 stringify!($cb_arg_ty),
305 ", ",
306 )*
307 "|{\n // callback code\n });"
308 )]
309 /// ```
310 fn $cb_fn_name<CallbackFn>(self, callback: CallbackFn)
311 where CallbackFn: FnMut($($cb_arg_ty),*) $(-> $cb_fn_ret)? + 'static;
312 )*
313 }
314
315 impl [<$ty Callbacks>] for $crate::PppCallback {
316 $(
317 fn $cb_fn_name<CallbackFn>(self, callback: CallbackFn)
318 where CallbackFn: FnMut($($cb_arg_ty),*) $(-> $cb_fn_ret)? + 'static
319 {
320 use std::ffi::c_void;
321 let closure_ref: *mut c_void = unsafe {
322 let x: Box<Box<
323 dyn FnMut($($cb_arg_ty),*) $(-> $cb_fn_ret)?
324 >> = Box::new(
325 Box::new(callback) as Box<_>
326 );
327 core::mem::transmute(x)
328 };
329
330 unsafe extern "C" fn trampoline(
331 context: *mut c_void, $($cb_arg_name : $cb_arg_ty),*
332 ) $(-> $cb_fn_ret)?
333 {
334 let closure: &mut &mut (
335 dyn FnMut($($cb_arg_ty),*) $(-> $cb_fn_ret)?
336 ) = core::mem::transmute(
337 context
338 );
339
340 closure($($cb_arg_name),*)
341 }
342
343 unsafe fn drop_fn(this: *mut c_void) {
344 let _: Box<Box<
345 dyn FnMut($($cb_arg_ty),*) $(-> $cb_fn_ret)?
346 >> = core::mem::transmute(this);
347 }
348
349 unsafe fn enable(this: *mut c_void) {
350 $static.[<add_callback_ $cb_fn_name _with_context>](
351 trampoline,
352 this
353 );
354 }
355
356 unsafe fn disable(this: *mut c_void) {
357 $static.[<remove_callback_ $cb_fn_name _with_context>](
358 trampoline,
359 this
360 );
361 }
362
363 let callback = $crate::InternalPppClosureCallback {
364 closure_ref,
365 drop_fn,
366 enable,
367 disable,
368 is_enabled: false,
369 };
370 $crate::Panda::run_after_init(move || {
371 unsafe {
372 $crate::__internal_install_ppp_closure_callback(
373 self,
374 callback
375 );
376 }
377 });
378 }
379 )*
380 }
381 }
382 )?
383 }
384}
385
386/// A wrapper for a dynamic library loaded as a PANDA plugin. Is used internally by
387/// the [`plugin_import`] macro to manage loading/unloading PANDA plugins lazily.
388pub struct Plugin {
389 lib: libloading::Library,
390}
391
392const PANDA_GLOBAL_INSTALLS: &[&str] = &["/usr/local/lib/panda", "/usr/lib/panda"];
393
394fn get_panda_path() -> Option<&'static Path> {
395 static PANDA_PATH: OnceCell<Option<PathBuf>> = OnceCell::new();
396
397 PANDA_PATH
398 .get_or_init(|| {
399 if let Ok(path) = std::env::var("PANDA_PATH") {
400 Some(PathBuf::from(path))
401 } else {
402 for possible_path in PANDA_GLOBAL_INSTALLS {
403 let path = PathBuf::from(possible_path);
404
405 if path.exists() {
406 return Some(path);
407 }
408 }
409
410 None
411 }
412 })
413 .as_deref()
414}
415
416fn get_panda_plugin_dir() -> Option<&'static Path> {
417 static PANDA_PLUGIN_DIR: OnceCell<Option<PathBuf>> = OnceCell::new();
418
419 PANDA_PLUGIN_DIR
420 .get_or_init(|| {
421 let panda_path = get_panda_path()?;
422
423 if let Ok(path) = std::env::var("PANDA_PLUGIN_DIR") {
424 Some(panda_path.join(path))
425 } else {
426 let path = panda_path.join(&format!("{}/panda/plugins", ARCH_NAME));
427 if path.exists() {
428 return Some(path);
429 }
430
431 let path = panda_path.join(&format!("{}-softmmu/panda/plugins", ARCH_NAME));
432 if path.exists() {
433 return Some(path);
434 }
435
436 let path = panda_path.join(ARCH_NAME);
437 if path.exists() {
438 return Some(path);
439 }
440
441 None
442 }
443 })
444 .as_deref()
445}
446
447impl Plugin {
448 pub fn new(name: &str) -> Self {
449 let panda_path =
450 get_panda_path().expect("PANDA_PATH not set and PANDA is not installed globally");
451
452 unsafe {
453 std::env::set_var("PANDA_DIR", &panda_path);
454
455 let c_name = CString::new(name).unwrap();
456 panda_require(c_name.as_ptr());
457 }
458
459 let path = get_panda_plugin_dir()
460 .expect("Could not find panda plugin dir, consider setting PANDA_PLUGIN_DIR")
461 .join(&format!("panda_{}.so", name));
462
463 if !path.exists() {
464 panic!("Could not find plugin {} at {}", name, path.display());
465 }
466
467 Self {
468 lib: libloading::Library::new(path).expect("Failed to load plugin"),
469 }
470 }
471
472 pub fn get<T>(&self, sym: &str) -> Symbol<T> {
473 let symbol: Vec<_> = sym.bytes().chain(std::iter::once(0)).collect();
474 unsafe { self.lib.get(&symbol).expect("Could not find symbol") }
475 }
476}