#![deny(missing_docs)]
#![cfg_attr(feature = "num-traits", feature(generic_const_exprs))]
#[cfg(windows)]
mod win_link_hook;
use crate::sys::{
qemu_plugin_cb_flags, qemu_plugin_id_t, qemu_plugin_insn, qemu_plugin_mem_rw,
qemu_plugin_meminfo_t, qemu_plugin_op, qemu_plugin_simple_cb_t, qemu_plugin_tb,
qemu_plugin_vcpu_simple_cb_t, qemu_plugin_vcpu_syscall_cb_t, qemu_plugin_vcpu_syscall_ret_cb_t,
qemu_plugin_vcpu_tb_trans_cb_t,
};
#[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))]
use crate::sys::{qemu_plugin_reg_descriptor, qemu_plugin_u64};
#[cfg(not(any(
feature = "plugin-api-v0",
feature = "plugin-api-v1",
feature = "plugin-api-v1",
feature = "plugin-api-v2"
)))]
use qemu_plugin_sys::qemu_plugin_cond;
#[cfg(not(feature = "plugin-api-v0"))]
use std::{ffi::CStr, path::PathBuf};
use std::{
ffi::{CString, c_uint, c_void},
sync::{Mutex, OnceLock},
};
pub mod error;
#[allow(unused_imports)]
pub use error::*;
pub mod install;
pub use install::*;
pub mod plugin;
pub use plugin::*;
pub mod instruction;
pub mod sys;
pub use instruction::*;
pub mod translation_block;
pub use translation_block::*;
#[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))]
pub mod register;
#[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))]
pub use register::*;
pub mod memory;
pub use memory::*;
#[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))]
pub mod scoreboard;
#[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))]
pub use scoreboard::*;
pub mod glib;
pub(crate) use glib::*;
pub type VCPUIndex = c_uint;
#[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))]
pub type PluginU64 = qemu_plugin_u64;
pub type CallbackFlags = qemu_plugin_cb_flags;
pub type MemRW = qemu_plugin_mem_rw;
#[cfg(not(any(
feature = "plugin-api-v0",
feature = "plugin-api-v1",
feature = "plugin-api-v2"
)))]
pub type PluginCondition = qemu_plugin_cond;
pub type PluginOp = qemu_plugin_op;
pub type PluginId = qemu_plugin_id_t;
pub type VCPUInitCallback = qemu_plugin_vcpu_simple_cb_t;
pub type VCPUExitCallback = qemu_plugin_vcpu_simple_cb_t;
pub type VCPUIdleCallback = qemu_plugin_vcpu_simple_cb_t;
pub type VCPUResumeCallback = qemu_plugin_vcpu_simple_cb_t;
pub type VCPUTranslationBlockTranslationCallback = qemu_plugin_vcpu_tb_trans_cb_t;
pub type FlushCallback = qemu_plugin_simple_cb_t;
pub type SyscallCallback = qemu_plugin_vcpu_syscall_cb_t;
pub type SyscallReturnCallback = qemu_plugin_vcpu_syscall_ret_cb_t;
#[allow(clippy::type_complexity)]
static UNINSTALL_CALLBACK: OnceLock<
Mutex<Option<Box<Box<dyn FnOnce(qemu_plugin_id_t) + Send + Sync + 'static>>>>,
> = OnceLock::new();
#[allow(clippy::type_complexity)]
static RESET_CALLBACK: OnceLock<
Mutex<Option<Box<Box<dyn FnOnce(qemu_plugin_id_t) + Send + Sync + 'static>>>>,
> = OnceLock::new();
extern "C" fn handle_qemu_plugin_uninstall_callback(id: qemu_plugin_id_t) {
if let Some(callback) = UNINSTALL_CALLBACK.get()
&& let Ok(mut callback) = callback.lock()
&& let Some(callback) = callback.take()
{
callback(id);
}
}
extern "C" fn handle_qemu_plugin_reset_callback(id: qemu_plugin_id_t) {
if let Some(callback) = UNINSTALL_CALLBACK.get()
&& let Ok(mut callback) = callback.lock()
&& let Some(callback) = callback.take()
{
callback(id);
}
}
pub fn qemu_plugin_uninstall<F>(id: qemu_plugin_id_t, cb: F) -> Result<()>
where
F: FnOnce(qemu_plugin_id_t) + Send + Sync + 'static,
{
UNINSTALL_CALLBACK
.set(Mutex::new(Some(Box::new(Box::new(cb)))))
.map_err(|_| Error::ConcurrentPluginUninstallCallbackSet)?;
unsafe { crate::sys::qemu_plugin_uninstall(id, Some(handle_qemu_plugin_uninstall_callback)) };
Ok(())
}
pub fn qemu_plugin_reset<F>(id: qemu_plugin_id_t, cb: F) -> Result<()>
where
F: FnOnce(qemu_plugin_id_t) + Send + Sync + 'static,
{
if let Some(callback) = RESET_CALLBACK.get() {
let Ok(mut callback) = callback.lock() else {
return Err(Error::PluginResetCallbackState);
};
let _ = callback.replace(Box::new(Box::new(cb)));
} else {
RESET_CALLBACK
.set(Mutex::new(Some(Box::new(Box::new(cb)))))
.map_err(|_| Error::ConcurrentPluginResetCallbackSet)?;
}
unsafe { crate::sys::qemu_plugin_reset(id, Some(handle_qemu_plugin_reset_callback)) };
Ok(())
}
pub fn qemu_plugin_register_vcpu_init_cb(id: qemu_plugin_id_t, cb: VCPUInitCallback) -> Result<()> {
unsafe { crate::sys::qemu_plugin_register_vcpu_init_cb(id, cb) };
Ok(())
}
pub fn qemu_plugin_register_vcpu_exit_cb(id: qemu_plugin_id_t, cb: VCPUExitCallback) -> Result<()> {
unsafe { crate::sys::qemu_plugin_register_vcpu_exit_cb(id, cb) };
Ok(())
}
pub fn qemu_plugin_register_vcpu_idle_cb(id: qemu_plugin_id_t, cb: VCPUIdleCallback) -> Result<()> {
unsafe { crate::sys::qemu_plugin_register_vcpu_idle_cb(id, cb) };
Ok(())
}
pub fn qemu_plugin_register_vcpu_resume_cb(
id: qemu_plugin_id_t,
cb: VCPUResumeCallback,
) -> Result<()> {
unsafe { crate::sys::qemu_plugin_register_vcpu_resume_cb(id, cb) };
Ok(())
}
pub fn qemu_plugin_register_vcpu_tb_trans_cb(
id: qemu_plugin_id_t,
cb: VCPUTranslationBlockTranslationCallback,
) -> Result<()> {
unsafe { crate::sys::qemu_plugin_register_vcpu_tb_trans_cb(id, cb) };
Ok(())
}
extern "C" fn handle_qemu_plugin_register_vcpu_tb_exec_cb<F>(
vcpu_index: VCPUIndex,
userdata: *mut c_void,
) where
F: FnMut(VCPUIndex) + Send + Sync + 'static,
{
let mut cb: Box<Box<F>> = unsafe { Box::from_raw(userdata as *mut _) };
cb(vcpu_index);
Box::leak(cb);
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn qemu_plugin_register_vcpu_tb_exec_cb<F>(tb: TranslationBlock, cb: F, flags: CallbackFlags)
where
F: FnMut(VCPUIndex) + Send + Sync + 'static,
{
tb.register_execute_callback_flags(cb, flags);
}
#[cfg(not(any(
feature = "plugin-api-v0",
feature = "plugin-api-v1",
feature = "plugin-api-v2"
)))]
pub fn qemu_plugin_register_vcpu_tb_exec_cond_cb<F>(
tb: TranslationBlock,
cb: F,
flags: CallbackFlags,
cond: PluginCondition,
entry: PluginU64,
immediate: u64,
) where
F: FnMut(VCPUIndex) + Send + Sync + 'static,
{
tb.register_conditional_execute_callback_flags(cb, flags, cond, entry, immediate);
}
#[cfg(any(feature = "plugin-api-v0", feature = "plugin-api-v1"))]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn qemu_plugin_register_vcpu_tb_exec_inline(
tb: TranslationBlock,
op: PluginOp,
ptr: *mut c_void,
imm: u64,
) {
unsafe {
crate::sys::qemu_plugin_register_vcpu_tb_exec_inline(
tb.translation_block as *mut qemu_plugin_tb,
op,
ptr,
imm,
);
}
}
#[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu(
tb: TranslationBlock,
op: PluginOp,
entry: PluginU64,
imm: u64,
) {
unsafe {
crate::sys::qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu(
tb.translation_block as *mut qemu_plugin_tb,
op,
entry,
imm,
);
}
}
extern "C" fn handle_qemu_plugin_register_vcpu_insn_exec_cb<F>(
vcpu_index: VCPUIndex,
userdata: *mut c_void,
) where
F: FnMut(VCPUIndex) + Send + Sync + 'static,
{
let mut cb: Box<Box<F>> = unsafe { Box::from_raw(userdata as *mut _) };
cb(vcpu_index);
Box::leak(cb);
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn qemu_plugin_register_vcpu_insn_exec_cb<F>(insn: Instruction, cb: F, flags: CallbackFlags)
where
F: FnMut(VCPUIndex) + Send + Sync + 'static,
{
insn.register_execute_callback_flags(cb, flags);
}
#[cfg(not(any(
feature = "plugin-api-v0",
feature = "plugin-api-v1",
feature = "plugin-api-v2"
)))]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn qemu_plugin_register_vcpu_insn_exec_cond_cb<F>(
insn: Instruction,
cb: F,
flags: CallbackFlags,
cond: PluginCondition,
entry: PluginU64,
immediate: u64,
) where
F: FnMut(VCPUIndex) + Send + Sync + 'static,
{
insn.register_conditional_execute_callback_flags(cb, flags, cond, entry, immediate);
}
#[cfg(any(feature = "plugin-api-v0", feature = "plugin-api-v1"))]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn qemu_plugin_register_vcpu_insn_exec_inline(
insn: Instruction,
op: PluginOp,
ptr: *mut c_void,
imm: u64,
) {
unsafe {
crate::sys::qemu_plugin_register_vcpu_insn_exec_inline(
insn.instruction as *mut qemu_plugin_insn,
op,
ptr,
imm,
);
}
}
#[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu(
insn: Instruction,
op: PluginOp,
entry: PluginU64,
imm: u64,
) {
unsafe {
crate::sys::qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu(
insn.instruction as *mut qemu_plugin_insn,
op,
entry,
imm,
);
}
}
extern "C" fn handle_qemu_plugin_register_vcpu_mem_cb<F>(
vcpu_index: VCPUIndex,
meminfo: qemu_plugin_meminfo_t,
vaddr: u64,
userdata: *mut c_void,
) where
F: for<'a> FnMut(VCPUIndex, MemoryInfo<'a>, u64) + Send + Sync + 'static,
{
let mut cb: Box<Box<F>> = unsafe { Box::from_raw(userdata as *mut _) };
let meminfo = MemoryInfo::from(meminfo);
cb(vcpu_index, meminfo, vaddr);
Box::leak(cb);
}
pub fn qemu_plugin_register_vcpu_mem_cb<F>(
insn: Instruction,
cb: F,
flags: CallbackFlags,
rw: MemRW,
) where
F: for<'a> FnMut(VCPUIndex, MemoryInfo<'a>, u64) + Send + Sync + 'static,
{
insn.register_memory_access_callback_flags(cb, rw, flags);
}
#[cfg(any(feature = "plugin-api-v0", feature = "plugin-api-v1"))]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn qemu_plugin_register_vcpu_mem_inline(
insn: Instruction,
rw: MemRW,
op: PluginOp,
ptr: *mut c_void,
imm: u64,
) {
unsafe {
crate::sys::qemu_plugin_register_vcpu_mem_inline(
insn.instruction as *mut qemu_plugin_insn,
rw,
op,
ptr,
imm,
);
}
}
#[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn qemu_plugin_register_vcpu_mem_inline_per_vcpu(
insn: Instruction,
rw: MemRW,
op: PluginOp,
entry: PluginU64,
imm: u64,
) {
unsafe {
crate::sys::qemu_plugin_register_vcpu_mem_inline_per_vcpu(
insn.instruction as *mut qemu_plugin_insn,
rw,
op,
entry,
imm,
);
}
}
extern "C" fn handle_qemu_plugin_register_atexit_cb<F>(id: qemu_plugin_id_t, userdata: *mut c_void)
where
F: FnOnce(qemu_plugin_id_t) + Send + Sync + 'static,
{
let cb: Box<Box<F>> = unsafe { Box::from_raw(userdata as *mut _) };
cb(id);
}
pub fn qemu_plugin_register_atexit_cb<F>(id: qemu_plugin_id_t, cb: F) -> Result<()>
where
F: FnOnce(qemu_plugin_id_t) + Send + Sync + 'static,
{
let callback = Box::new(cb);
let callback_box = Box::new(callback);
unsafe {
crate::sys::qemu_plugin_register_atexit_cb(
id,
Some(handle_qemu_plugin_register_atexit_cb::<F>),
Box::into_raw(callback_box) as *mut c_void,
)
};
Ok(())
}
pub fn qemu_plugin_register_flush_cb(id: qemu_plugin_id_t, cb: FlushCallback) {
unsafe { crate::sys::qemu_plugin_register_flush_cb(id, cb) };
}
pub fn qemu_plugin_register_vcpu_syscall_cb(id: qemu_plugin_id_t, cb: SyscallCallback) {
unsafe { crate::sys::qemu_plugin_register_vcpu_syscall_cb(id, cb) };
}
pub fn qemu_plugin_register_vcpu_syscall_ret_cb(id: qemu_plugin_id_t, cb: SyscallReturnCallback) {
unsafe { crate::sys::qemu_plugin_register_vcpu_syscall_ret_cb(id, cb) };
}
pub fn qemu_plugin_outs<S>(string: S) -> Result<()>
where
S: AsRef<str>,
{
unsafe {
crate::sys::qemu_plugin_outs(CString::new(string.as_ref())?.as_ptr());
}
Ok(())
}
#[cfg(feature = "plugin-api-v0")]
pub fn qemu_plugin_bool_parse<S>(name: S, val: S) -> Result<bool>
where
S: AsRef<str>,
{
match val.as_ref() {
"on" | "yes" | "true" => Ok(true),
"off" | "no" | "false" => Ok(false),
_ => Err(Error::InvalidBool {
name: name.as_ref().to_string(),
val: val.as_ref().to_string(),
}),
}
}
#[cfg(not(feature = "plugin-api-v0"))]
pub fn qemu_plugin_bool_parse<S>(name: S, val: S) -> Result<bool>
where
S: AsRef<str>,
{
let mut value = false;
if unsafe {
crate::sys::qemu_plugin_bool_parse(
CString::new(name.as_ref())?.as_ptr(),
CString::new(val.as_ref())?.as_ptr(),
&mut value,
)
} {
Ok(value)
} else {
Err(Error::InvalidBool {
name: name.as_ref().to_string(),
val: val.as_ref().to_string(),
})
}
}
#[cfg(not(feature = "plugin-api-v0"))]
pub fn qemu_plugin_path_to_binary() -> Result<Option<PathBuf>> {
let path_str = unsafe { crate::sys::qemu_plugin_path_to_binary() };
if path_str.is_null() {
Ok(None)
} else {
let path = unsafe { PathBuf::from(CStr::from_ptr(path_str).to_str()?) };
unsafe { g_free(path_str as *mut _) };
Ok(Some(path))
}
}
#[cfg(not(feature = "plugin-api-v0"))]
pub fn qemu_plugin_start_code() -> Option<u64> {
let start = unsafe { crate::sys::qemu_plugin_start_code() };
if start == 0 { None } else { Some(start) }
}
#[cfg(not(feature = "plugin-api-v0"))]
pub fn qemu_plugin_end_code() -> Option<u64> {
let end = unsafe { crate::sys::qemu_plugin_end_code() };
if end == 0 { None } else { Some(end) }
}
#[cfg(not(feature = "plugin-api-v0"))]
pub fn qemu_plugin_entry_code() -> Option<u64> {
let entry = unsafe { crate::sys::qemu_plugin_entry_code() };
if entry == 0 { None } else { Some(entry) }
}
#[cfg(any(feature = "plugin-api-v0", feature = "plugin-api-v1"))]
pub fn qemu_plugin_n_vcpus() -> Option<i32> {
let vcpus = unsafe { crate::sys::qemu_plugin_n_vcpus() };
if vcpus == -1 { None } else { Some(vcpus) }
}
#[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))]
pub fn qemu_plugin_num_vcpus() -> Option<i32> {
let vcpus = unsafe { crate::sys::qemu_plugin_num_vcpus() };
if vcpus == -1 { None } else { Some(vcpus) }
}
#[cfg(any(feature = "plugin-api-v0", feature = "plugin-api-v1"))]
pub fn qemu_plugin_n_max_vcpus() -> Option<i32> {
let max_cpus = unsafe { crate::sys::qemu_plugin_n_max_vcpus() };
if max_cpus == -1 { None } else { Some(max_cpus) }
}
#[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))]
pub fn qemu_plugin_get_registers<'a>() -> Result<Vec<RegisterDescriptor<'a>>> {
use std::slice::from_raw_parts;
let array = unsafe { crate::sys::qemu_plugin_get_registers() };
let registers = unsafe {
from_raw_parts(
(*array).data as *mut qemu_plugin_reg_descriptor,
(*array).len as usize,
)
}
.iter()
.map(|desc| RegisterDescriptor::from(*desc))
.collect::<Vec<_>>();
assert_eq!(
unsafe { g_array_free(array, true) },
std::ptr::null_mut(),
"g_array_free return value must be NULL"
);
Ok(registers)
}
#[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))]
pub fn qemu_plugin_u64_add(entry: PluginU64, vcpu_index: VCPUIndex, added: u64) -> Result<()> {
unsafe { crate::sys::qemu_plugin_u64_add(entry, vcpu_index, added) };
Ok(())
}
#[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))]
pub fn qemu_plugin_u64_get(entry: PluginU64, vcpu_index: VCPUIndex) -> u64 {
unsafe { crate::sys::qemu_plugin_u64_get(entry, vcpu_index) }
}
#[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))]
pub fn qemu_plugin_u64_set(entry: PluginU64, vcpu_index: VCPUIndex, value: u64) {
unsafe { crate::sys::qemu_plugin_u64_set(entry, vcpu_index, value) }
}
#[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))]
pub fn qemu_plugin_scoreboard_sum(entry: PluginU64) -> u64 {
unsafe { crate::sys::qemu_plugin_u64_sum(entry) }
}