├── .github ├── .gitignore ├── rsrc │ ├── .gitignore │ ├── seed.img │ ├── id_rsa.pub │ └── id_rsa └── workflows │ └── ci.yml ├── plugins ├── tracer │ ├── tests │ │ ├── test.c │ │ ├── test-aarch64 │ │ └── build.ninja │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── tracer-events │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── tiny │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── icount │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── tiny-system │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── tracer-driver │ ├── Cargo.toml │ └── src │ └── main.rs ├── qemu-plugin ├── src │ ├── sys │ │ └── mod.rs │ ├── scoreboard │ │ └── mod.rs │ ├── win_link_hook │ │ └── mod.rs │ ├── glib │ │ └── mod.rs │ ├── register │ │ └── mod.rs │ ├── translation_block │ │ └── mod.rs │ ├── install │ │ └── mod.rs │ ├── error │ │ └── mod.rs │ ├── instruction │ │ └── mod.rs │ ├── memory │ │ └── mod.rs │ └── plugin │ │ └── mod.rs └── Cargo.toml ├── .vscode └── settings.json ├── .dockerignore ├── .gitignore ├── qemu-plugin-sys ├── src │ ├── lib.rs │ ├── qemu_plugin_api_v0.def │ ├── qemu_plugin_api_v1.def │ ├── qemu_plugin_api_v2.def │ ├── qemu_plugin_api_v3.def │ ├── qemu_plugin_api_v4.def │ ├── qemu_plugin_api_v5.def │ └── bindings_v0.rs ├── README.md ├── Cargo.toml ├── build.rs └── generate-bindings.rs ├── Cargo.toml ├── scripts ├── mk-cloudinit.sh ├── check.sh ├── qemu-plugin-versions.sh ├── check.ps1 └── ci.sh └── README.md /.github/.gitignore: -------------------------------------------------------------------------------- 1 | logs -------------------------------------------------------------------------------- /.github/rsrc/.gitignore: -------------------------------------------------------------------------------- 1 | *.yml -------------------------------------------------------------------------------- /.github/rsrc/seed.img: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novafacing/qemu-rs/HEAD/.github/rsrc/seed.img -------------------------------------------------------------------------------- /plugins/tracer/tests/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { printf("Hello, world!\n"); } -------------------------------------------------------------------------------- /plugins/tracer/tests/test-aarch64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novafacing/qemu-rs/HEAD/plugins/tracer/tests/test-aarch64 -------------------------------------------------------------------------------- /qemu-plugin/src/sys/mod.rs: -------------------------------------------------------------------------------- 1 | //! Re-exported sys API bindings. These bindings should *not* be used directly in most cases. 2 | 3 | pub use qemu_plugin_sys::*; 4 | -------------------------------------------------------------------------------- /plugins/tracer/tests/build.ninja: -------------------------------------------------------------------------------- 1 | rule cc 2 | command = clang -fuse-ld=lld -target aarch64-unknown-linux-gnu -o $out $in 3 | 4 | build test-aarch64: cc test.c 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.buildScripts.enable": false, 3 | "rust-analyzer.diagnostics.disabled": [ 4 | "unlinked-file" 5 | ] 6 | } -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | .env 12 | .secrets 13 | *~ -------------------------------------------------------------------------------- /plugins/tracer-events/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tracer-events" 3 | authors.workspace = true 4 | categories.workspace = true 5 | description.workspace = true 6 | edition.workspace = true 7 | homepage.workspace = true 8 | license.workspace = true 9 | publish.workspace = true 10 | readme.workspace = true 11 | repository.workspace = true 12 | version.workspace = true 13 | rust-version.workspace = true 14 | 15 | [dependencies] 16 | serde = { version = "1.0.219", features = ["derive"] } 17 | typed-builder = "0.21.2" 18 | -------------------------------------------------------------------------------- /.github/rsrc/id_rsa.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDZtbTUmb26NZjRWgO4vcPheZDT0PVOTKD3wxChc2Dudnogk/YXw9Y3g4dHCt3r9+VwOPlzxh+CTCoKvK7x/+qr6eWBpDx2yQkhSdotrdi1VrHYqtC7/bMcfpK0hpuJBgoz7ovQ49nHCttf65J+z9AQAXQoLFxdUOO9eOm86Vr4xa6eb0H6zXXKP6p6xJz4xamBkq9lgfwWYgjL4/vtyeeMOBfHnW8cd07ysji4u6g2loxCOpu2kY/ekVhuoAh3WWtBIHNUnjJuuFnD/6l/OGhescMJ3iVN3p+KGPQgdCQ+a7GB3T0Rk41+yIMZfuJShOtG8a31aMJUKXO+M+AIjL/SGlQbj/7UYC1fhXes3ziBPV76pOEUvwaKaI/NRcvVptPlhCJYJs394n3SR39ON+GwCEPXE44rxOUeL/bwI4X5zOFJ6S2dybRaueDnBZilmQkbvJfgYTkwpaKSAJcHhK8JbzXT4h6+CTWDucvZzyjKdiLEbYl8TireBwWY5/1TCLE= fedora@localhost 2 | -------------------------------------------------------------------------------- /plugins/tiny/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tiny" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | qemu-plugin = { workspace = true, default-features = false } 11 | 12 | [features] 13 | default = ["plugin-api-v5"] 14 | plugin-api-v0 = ["qemu-plugin/plugin-api-v0"] 15 | plugin-api-v1 = ["qemu-plugin/plugin-api-v1"] 16 | plugin-api-v2 = ["qemu-plugin/plugin-api-v2"] 17 | plugin-api-v3 = ["qemu-plugin/plugin-api-v3"] 18 | plugin-api-v4 = ["qemu-plugin/plugin-api-v4"] 19 | plugin-api-v5 = ["qemu-plugin/plugin-api-v5"] 20 | -------------------------------------------------------------------------------- /plugins/icount/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "icount" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | qemu-plugin = { workspace = true, default-features = false } 11 | 12 | [features] 13 | default = ["plugin-api-v5"] 14 | plugin-api-v0 = ["qemu-plugin/plugin-api-v0"] 15 | plugin-api-v1 = ["qemu-plugin/plugin-api-v1"] 16 | plugin-api-v2 = ["qemu-plugin/plugin-api-v2"] 17 | plugin-api-v3 = ["qemu-plugin/plugin-api-v3"] 18 | plugin-api-v4 = ["qemu-plugin/plugin-api-v4"] 19 | plugin-api-v5 = ["qemu-plugin/plugin-api-v5"] 20 | -------------------------------------------------------------------------------- /plugins/tiny-system/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tiny-system" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | qemu-plugin = { workspace = true, default-features = false } 11 | 12 | [features] 13 | default = ["plugin-api-v5"] 14 | plugin-api-v0 = ["qemu-plugin/plugin-api-v0"] 15 | plugin-api-v1 = ["qemu-plugin/plugin-api-v1"] 16 | plugin-api-v2 = ["qemu-plugin/plugin-api-v2"] 17 | plugin-api-v3 = ["qemu-plugin/plugin-api-v3"] 18 | plugin-api-v4 = ["qemu-plugin/plugin-api-v4"] 19 | plugin-api-v5 = ["qemu-plugin/plugin-api-v5"] 20 | -------------------------------------------------------------------------------- /qemu-plugin-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Low level bindings to the QEMU Plugin API 2 | //! 3 | //! These bindings are generated from the QEMU source code, and should not be used directly. 4 | //! Instead, use the `qemu-plugin` crate. 5 | 6 | #![allow(non_upper_case_globals)] 7 | #![allow(non_camel_case_types)] 8 | 9 | #[cfg(feature = "plugin-api-v0")] 10 | include!("bindings_v0.rs"); 11 | 12 | #[cfg(feature = "plugin-api-v1")] 13 | include!("bindings_v1.rs"); 14 | 15 | #[cfg(feature = "plugin-api-v2")] 16 | include!("bindings_v2.rs"); 17 | 18 | #[cfg(feature = "plugin-api-v3")] 19 | include!("bindings_v3.rs"); 20 | 21 | #[cfg(feature = "plugin-api-v4")] 22 | include!("bindings_v4.rs"); 23 | 24 | #[cfg(feature = "plugin-api-v5")] 25 | include!("bindings_v5.rs"); 26 | -------------------------------------------------------------------------------- /plugins/icount/src/lib.rs: -------------------------------------------------------------------------------- 1 | use qemu_plugin::{ 2 | HasCallbacks, PluginId, Register, Result, TranslationBlock, VCPUIndex, register, 3 | }; 4 | 5 | #[derive(Default)] 6 | struct ICount; 7 | 8 | impl Register for ICount {} 9 | 10 | impl HasCallbacks for ICount { 11 | fn on_vcpu_init(&mut self, _id: PluginId, _vcpu_id: VCPUIndex) -> Result<()> { 12 | Ok(()) 13 | } 14 | 15 | fn on_translation_block_translate( 16 | &mut self, 17 | _id: PluginId, 18 | tb: TranslationBlock, 19 | ) -> Result<()> { 20 | tb.instructions().try_for_each(|insn| { 21 | let vaddr = insn.vaddr(); 22 | let disas = insn.disas()?; 23 | 24 | insn.register_execute_callback(move |_idx| { 25 | println!("{vaddr:08x}: {disas}"); 26 | }); 27 | 28 | Ok(()) 29 | }) 30 | } 31 | } 32 | 33 | register!(ICount); 34 | -------------------------------------------------------------------------------- /plugins/tracer-driver/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tracer-driver" 3 | authors.workspace = true 4 | categories.workspace = true 5 | description.workspace = true 6 | edition.workspace = true 7 | homepage.workspace = true 8 | license.workspace = true 9 | publish.workspace = true 10 | readme.workspace = true 11 | repository.workspace = true 12 | version.workspace = true 13 | rust-version.workspace = true 14 | 15 | [dependencies] 16 | anyhow = "1.0.99" 17 | clap = { version = "4.5.47", features = ["derive"] } 18 | rand = "0.9.2" 19 | serde_cbor = "0.11.2" 20 | serde_json = "1.0.143" 21 | tokio = { version = "1.47.1", features = ["full"] } 22 | tracing = "0.1.41" 23 | tracing-subscriber = "0.3.20" 24 | tracer-events = { path = "../tracer-events" } 25 | 26 | [features] 27 | default = ["plugin-api-v5"] 28 | plugin-api-v0 = [] 29 | plugin-api-v1 = [] 30 | plugin-api-v2 = [] 31 | plugin-api-v3 = [] 32 | plugin-api-v4 = [] 33 | plugin-api-v5 = [] 34 | -------------------------------------------------------------------------------- /plugins/tracer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tracer" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [lib] 7 | name = "tracer" 8 | crate-type = ["cdylib", "lib"] 9 | 10 | [dependencies] 11 | anyhow = "1.0.99" 12 | qemu-plugin = { workspace = true, default-features = false, features = [ 13 | "anyhow", 14 | ] } 15 | serde = { version = "1.0.219", features = ["derive"] } 16 | serde_cbor = "0.11.2" 17 | tokio = { version = "1.47.1", features = ["full"] } 18 | typed-builder = "0.21.2" 19 | yaxpeax-x86 = "2.0.0" 20 | serde_json = "1.0.143" 21 | tracer-events = { path = "../tracer-events" } 22 | 23 | [features] 24 | default = ["plugin-api-v5"] 25 | plugin-api-v0 = ["qemu-plugin/plugin-api-v5"] 26 | plugin-api-v1 = ["qemu-plugin/plugin-api-v1"] 27 | plugin-api-v2 = ["qemu-plugin/plugin-api-v2"] 28 | plugin-api-v3 = ["qemu-plugin/plugin-api-v3"] 29 | plugin-api-v4 = ["qemu-plugin/plugin-api-v4"] 30 | plugin-api-v5 = ["qemu-plugin/plugin-api-v5"] 31 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace.package] 2 | authors = ["Rowan Hart "] 3 | categories = ["virtualization"] 4 | description = "Rust bindings and binary installers for QEMU" 5 | edition = "2024" 6 | homepage = "https://github.com/novafacing/qemu-rs" 7 | license = "GPL-2.0-or-later" 8 | publish = true 9 | readme = "README.md" 10 | repository = "https://github.com/novafacing/qemu-rs" 11 | version = "10.1.0-v2" 12 | rust-version = "1.88" 13 | 14 | [workspace] 15 | resolver = "2" 16 | members = [ 17 | "qemu-plugin", 18 | "qemu-plugin-sys", 19 | "plugins/tiny", 20 | "plugins/tiny-system", 21 | "plugins/tracer", 22 | "plugins/tracer-driver", 23 | "plugins/tracer-events", 24 | "plugins/icount", 25 | ] 26 | default-members = ["qemu-plugin", "qemu-plugin-sys"] 27 | 28 | [workspace.dependencies] 29 | qemu-plugin = { version = "10.1.0-v2", path = "qemu-plugin", default-features = false } 30 | qemu-plugin-sys = { version = "10.1.0-v2", path = "qemu-plugin-sys", default-features = false } 31 | -------------------------------------------------------------------------------- /qemu-plugin-sys/README.md: -------------------------------------------------------------------------------- 1 | # QEMU-PLUGIN-SYS 2 | 3 | Low level auto-generated FFI bindings to the QEMU Plugin API (`qemu-plugin.h`). This 4 | crate should not be used directly, check out the `qemu-plugin` crate for the idiomatic 5 | high-level bindings. 6 | 7 | ## Versioning 8 | 9 | As of QEMU 8.2.4, the QEMU plugin API has more than a single version. This enables some 10 | great features like register inspection and conditional callbacks. Versioning is 11 | implemented in the `qemu-plugin-sys` crate via compile-time features, because a dynamic 12 | library can only be compatible with one version at a time. To choose a version, set a 13 | listing like: 14 | 15 | ```toml 16 | qemu-plugin-sys = { version = "9.0.0-v0", features = ["plugin-api-v2"], default-features = false } 17 | ``` 18 | 19 | The `qemu-plugin-sys` crate's default plugin version is set to the latest version that 20 | is officially released in QEMU. Currently, this is V2, released in 8.2.4 and 9.0.0. If 21 | you need a different version, you *must* set `default-features = false`. -------------------------------------------------------------------------------- /scripts/mk-cloudinit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # sudo -E dnf install -y cloud-utils 6 | 7 | mkdir -p .github/rsrc/ 8 | rm .github/rsrc/id_* || true 9 | if [ ! -f .github/rsrc/id_rsa ]; then 10 | ssh-keygen -C fedora@localhost -t rsa -q -f .github/rsrc/id_rsa -N "" 11 | fi 12 | 13 | KEY="$(cat .github/rsrc/id_rsa.pub)" 14 | # password is "password" 15 | # mkpasswd --method=SHA-512 --rounds=4096 16 | PASSWORD='$6$rounds=4096$At.ZMrhUfvsFwTiG$VJ8aQCC3nr8SpUL99OHcWsR6BvlVur5qvKQHni8n5v1HxB0E3.2eLX0tbxq8nHv.JJb2cU5mXr8bAgogCd5Ke1' 17 | cat < .github/rsrc/user-data.yml 18 | #cloud-config 19 | bootcmd: 20 | - useradd -m -p ${PASSWORD} -s /bin/bash fedora 21 | - mkdir -p /home/fedora/.ssh 22 | - echo "${KEY}" >> /home/fedora/.ssh/authorized_keys 23 | - chown -R fedora:fedora /home/fedora/.ssh 24 | - chmod 700 /home/fedora/.ssh 25 | - chmod 600 /home/fedora/.ssh/authorized_keys 26 | - echo "fedora ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers 27 | EOF 28 | 29 | cloud-localds .github/rsrc/seed.img .github/rsrc/user-data.yml -------------------------------------------------------------------------------- /plugins/tiny-system/src/lib.rs: -------------------------------------------------------------------------------- 1 | use qemu_plugin::{HasCallbacks, PluginId, Register, Result, register}; 2 | 3 | struct TinyTrace; 4 | 5 | impl Register for TinyTrace {} 6 | 7 | impl HasCallbacks for TinyTrace { 8 | fn on_vcpu_init(&mut self, id: PluginId, vcpu_id: qemu_plugin::VCPUIndex) -> Result<()> { 9 | println!("on_vcpu_init: id: {id:?}, vcpu_id: {vcpu_id:?}"); 10 | Ok(()) 11 | } 12 | 13 | fn on_vcpu_idle(&mut self, id: PluginId, vcpu_id: qemu_plugin::VCPUIndex) -> Result<()> { 14 | println!("on_vcpu_idle: id: {id:?}, vcpu_id: {vcpu_id:?}"); 15 | Ok(()) 16 | } 17 | 18 | fn on_vcpu_exit(&mut self, id: PluginId, vcpu_id: qemu_plugin::VCPUIndex) -> Result<()> { 19 | println!("on_vcpu_exit: id: {id:?}, vcpu_id: {vcpu_id:?}"); 20 | Ok(()) 21 | } 22 | 23 | fn on_vcpu_resume(&mut self, id: PluginId, vcpu_id: qemu_plugin::VCPUIndex) -> Result<()> { 24 | println!("on_vcpu_resume: id: {id:?}, vcpu_id: {vcpu_id:?}"); 25 | Ok(()) 26 | } 27 | } 28 | 29 | register!(TinyTrace); 30 | -------------------------------------------------------------------------------- /qemu-plugin-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "qemu-plugin-sys" 3 | authors.workspace = true 4 | categories.workspace = true 5 | description = "Low level bindings to the QEMU plugin API" 6 | edition.workspace = true 7 | homepage.workspace = true 8 | license.workspace = true 9 | publish.workspace = true 10 | readme.workspace = true 11 | repository.workspace = true 12 | version.workspace = true 13 | rust-version.workspace = true 14 | 15 | [build-dependencies] 16 | anyhow = "1.0.99" 17 | 18 | [lints.rust] 19 | non_snake_case = "allow" 20 | 21 | [features] 22 | default = ["plugin-api-v5"] 23 | # Use the V0 plugin API, which is defined starting in version 4.2.0 24 | plugin-api-v0 = [] 25 | # Use the V1 plugin API, which is defined starting in version 6.0.0 26 | plugin-api-v1 = [] 27 | # Use the V2 plugin API, which is defined starting in version 9.0.0 28 | plugin-api-v2 = [] 29 | # Use the V3 plugin API, which is defined starting in version 9.1.0 30 | plugin-api-v3 = [] 31 | # Use the V4 plugin API, which is defined starting in version 9.2.0 32 | plugin-api-v4 = [] 33 | # Use the V5 plugin API, which is defined starting in version 10.1.0 34 | plugin-api-v5 = [] 35 | -------------------------------------------------------------------------------- /qemu-plugin-sys/src/qemu_plugin_api_v0.def: -------------------------------------------------------------------------------- 1 | EXPORTS 2 | qemu_plugin_get_hwaddr 3 | qemu_plugin_hwaddr_device_offset 4 | qemu_plugin_hwaddr_is_io 5 | qemu_plugin_insn_data 6 | qemu_plugin_insn_disas 7 | qemu_plugin_insn_haddr 8 | qemu_plugin_insn_size 9 | qemu_plugin_insn_vaddr 10 | qemu_plugin_mem_is_big_endian 11 | qemu_plugin_mem_is_sign_extended 12 | qemu_plugin_mem_is_store 13 | qemu_plugin_mem_size_shift 14 | qemu_plugin_n_max_vcpus 15 | qemu_plugin_n_vcpus 16 | qemu_plugin_outs 17 | qemu_plugin_register_atexit_cb 18 | qemu_plugin_register_flush_cb 19 | qemu_plugin_register_vcpu_exit_cb 20 | qemu_plugin_register_vcpu_idle_cb 21 | qemu_plugin_register_vcpu_init_cb 22 | qemu_plugin_register_vcpu_insn_exec_cb 23 | qemu_plugin_register_vcpu_insn_exec_inline 24 | qemu_plugin_register_vcpu_mem_cb 25 | qemu_plugin_register_vcpu_mem_inline 26 | qemu_plugin_register_vcpu_resume_cb 27 | qemu_plugin_register_vcpu_syscall_cb 28 | qemu_plugin_register_vcpu_syscall_ret_cb 29 | qemu_plugin_register_vcpu_tb_exec_cb 30 | qemu_plugin_register_vcpu_tb_exec_inline 31 | qemu_plugin_register_vcpu_tb_trans_cb 32 | qemu_plugin_reset 33 | qemu_plugin_tb_get_insn 34 | qemu_plugin_tb_n_insns 35 | qemu_plugin_tb_vaddr 36 | qemu_plugin_uninstall 37 | qemu_plugin_vcpu_for_each 38 | -------------------------------------------------------------------------------- /scripts/check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd) 6 | REPO_ROOT=$(realpath "$SCRIPT_DIR/..") 7 | 8 | CHECK_PATHS=( 9 | "$REPO_ROOT/qemu-plugin" 10 | "$REPO_ROOT/qemu-plugin-sys" 11 | "$REPO_ROOT/plugins/icount" 12 | "$REPO_ROOT/plugins/tiny" 13 | "$REPO_ROOT/plugins/tiny-system" 14 | "$REPO_ROOT/plugins/tracer" 15 | "$REPO_ROOT/plugins/tracer-driver" 16 | ) 17 | 18 | cargo +nightly check --manifest-path "$REPO_ROOT/plugins/tracer-events/Cargo.toml" 19 | cargo +nightly clippy --manifest-path "$REPO_ROOT/plugins/tracer-events/Cargo.toml" 20 | 21 | FEATURES="plugin-api-v0,plugin-api-v1,plugin-api-v2,plugin-api-v3,plugin-api-v4,plugin-api-v5" 22 | 23 | pushd "$REPO_ROOT" > /dev/null 24 | cargo fmt --all --check 25 | popd > /dev/null 26 | 27 | for CHECK_PATH in "${CHECK_PATHS[@]}"; do 28 | MANIFEST_PATH="$CHECK_PATH/Cargo.toml" 29 | 30 | cargo +nightly hack --manifest-path "$MANIFEST_PATH" \ 31 | "--mutually-exclusive-features=$FEATURES" \ 32 | "--at-least-one-of=$FEATURES" \ 33 | "--feature-powerset" \ 34 | "--exclude-no-default-features" \ 35 | check 36 | cargo +nightly hack --manifest-path "$MANIFEST_PATH" \ 37 | "--mutually-exclusive-features=$FEATURES" \ 38 | "--at-least-one-of=$FEATURES" \ 39 | "--feature-powerset" \ 40 | "--exclude-no-default-features" \ 41 | clippy 42 | done -------------------------------------------------------------------------------- /qemu-plugin-sys/src/qemu_plugin_api_v1.def: -------------------------------------------------------------------------------- 1 | EXPORTS 2 | qemu_plugin_bool_parse 3 | qemu_plugin_end_code 4 | qemu_plugin_entry_code 5 | qemu_plugin_get_hwaddr 6 | qemu_plugin_hwaddr_device_name 7 | qemu_plugin_hwaddr_is_io 8 | qemu_plugin_hwaddr_phys_addr 9 | qemu_plugin_insn_data 10 | qemu_plugin_insn_disas 11 | qemu_plugin_insn_haddr 12 | qemu_plugin_insn_size 13 | qemu_plugin_insn_symbol 14 | qemu_plugin_insn_vaddr 15 | qemu_plugin_mem_is_big_endian 16 | qemu_plugin_mem_is_sign_extended 17 | qemu_plugin_mem_is_store 18 | qemu_plugin_mem_size_shift 19 | qemu_plugin_n_max_vcpus 20 | qemu_plugin_n_vcpus 21 | qemu_plugin_outs 22 | qemu_plugin_path_to_binary 23 | qemu_plugin_register_atexit_cb 24 | qemu_plugin_register_flush_cb 25 | qemu_plugin_register_vcpu_exit_cb 26 | qemu_plugin_register_vcpu_idle_cb 27 | qemu_plugin_register_vcpu_init_cb 28 | qemu_plugin_register_vcpu_insn_exec_cb 29 | qemu_plugin_register_vcpu_insn_exec_inline 30 | qemu_plugin_register_vcpu_mem_cb 31 | qemu_plugin_register_vcpu_mem_inline 32 | qemu_plugin_register_vcpu_resume_cb 33 | qemu_plugin_register_vcpu_syscall_cb 34 | qemu_plugin_register_vcpu_syscall_ret_cb 35 | qemu_plugin_register_vcpu_tb_exec_cb 36 | qemu_plugin_register_vcpu_tb_exec_inline 37 | qemu_plugin_register_vcpu_tb_trans_cb 38 | qemu_plugin_reset 39 | qemu_plugin_start_code 40 | qemu_plugin_tb_get_insn 41 | qemu_plugin_tb_n_insns 42 | qemu_plugin_tb_vaddr 43 | qemu_plugin_uninstall 44 | qemu_plugin_vcpu_for_each 45 | -------------------------------------------------------------------------------- /plugins/tracer-events/src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::collections::HashMap; 3 | use typed_builder::TypedBuilder; 4 | 5 | #[derive(TypedBuilder, Clone, Debug, Default, Deserialize, Serialize)] 6 | pub struct InstructionEvent { 7 | pub vaddr: u64, 8 | pub haddr: u64, 9 | pub disas: String, 10 | pub symbol: Option, 11 | pub data: Vec, 12 | } 13 | 14 | #[derive(TypedBuilder, Clone, Debug, Default, Deserialize, Serialize)] 15 | pub struct MemoryEvent { 16 | pub vaddr: u64, 17 | pub haddr: Option, 18 | pub haddr_is_io: Option, 19 | pub haddr_device_name: Option, 20 | pub size_shift: usize, 21 | pub size_bytes: usize, 22 | pub sign_extended: bool, 23 | pub is_store: bool, 24 | pub big_endian: bool, 25 | } 26 | 27 | #[derive(TypedBuilder, Clone, Debug, Default, PartialEq, Eq, Hash)] 28 | pub struct SyscallSource { 29 | plugin_id: u64, 30 | vcpu_index: u32, 31 | } 32 | 33 | #[derive(TypedBuilder, Clone, Debug, Default, Deserialize, Serialize)] 34 | pub struct SyscallEvent { 35 | pub num: i64, 36 | pub return_value: i64, 37 | pub args: [u64; 8], 38 | #[builder(default)] 39 | pub buffers: HashMap>, 40 | } 41 | 42 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 43 | pub struct Registers(pub HashMap>); 44 | 45 | #[derive(Clone, Debug, Deserialize, Serialize)] 46 | pub enum Event { 47 | Instruction { 48 | event: InstructionEvent, 49 | registers: Registers, 50 | }, 51 | Memory(MemoryEvent), 52 | Syscall(SyscallEvent), 53 | } 54 | -------------------------------------------------------------------------------- /scripts/qemu-plugin-versions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check arguments 4 | if [ $# -ne 1 ]; then 5 | echo "Usage: $0 " 6 | exit 1 7 | fi 8 | 9 | REPO_PATH="$1" 10 | FILE_PATH="include/qemu/qemu-plugin.h" 11 | PATTERN="#define QEMU_PLUGIN_VERSION" 12 | 13 | # Check if directory exists and is a git repository 14 | if [ ! -d "$REPO_PATH" ]; then 15 | echo "Error: Directory $REPO_PATH does not exist" 16 | exit 1 17 | fi 18 | 19 | cd "$REPO_PATH" || exit 1 20 | 21 | if ! git rev-parse --git-dir > /dev/null 2>&1; then 22 | echo "Error: $REPO_PATH is not a git repository" 23 | exit 1 24 | fi 25 | 26 | # Get all tags sorted by version 27 | TAGS=$(git tag --sort=version:refname 2>/dev/null) 28 | 29 | if [ -z "$TAGS" ]; then 30 | echo "No git tags found in this repository" 31 | exit 0 32 | fi 33 | 34 | # Print header 35 | printf "%-20s %s\n" "TAG" "VERSION" 36 | printf "%-20s %s\n" "---" "-------" 37 | 38 | # Process each tag 39 | while IFS= read -r tag; do 40 | # Get the file content at this tag using git show 41 | FILE_CONTENT=$(git show "$tag:$FILE_PATH" 2>/dev/null) 42 | 43 | if [ $? -ne 0 ] || [ -z "$FILE_CONTENT" ]; then 44 | printf "%-30s %s\n" "$tag" "file not found" 45 | continue 46 | fi 47 | 48 | # Search for the pattern and extract version 49 | VERSION=$(echo "$FILE_CONTENT" | grep "$PATTERN" | head -1 | sed -n "s/.*${PATTERN}[[:space:]]\+\([0-9]\+\).*/\1/p") 50 | 51 | if [ -n "$VERSION" ]; then 52 | printf "%-30s %s\n" "$tag" "$VERSION" 53 | else 54 | printf "%-30s %s\n" "$tag" "pattern not found" 55 | fi 56 | 57 | done <<< "$TAGS" -------------------------------------------------------------------------------- /scripts/check.ps1: -------------------------------------------------------------------------------- 1 | Set-StrictMode -Version Latest 2 | $ErrorActionPreference = 'Stop' 3 | Set-Variable -Name PSNativeCommandUseErrorActionPreference -Value $true -Scope Global -ErrorAction SilentlyContinue 4 | 5 | $ScriptDir = $PSScriptRoot 6 | $RepoRoot = (Resolve-Path (Join-Path $ScriptDir '..')).Path 7 | 8 | $checkPaths = @( 9 | (Join-Path $RepoRoot 'qemu-plugin'), 10 | (Join-Path $RepoRoot 'qemu-plugin-sys') 11 | # (Join-Path $RepoRoot 'plugins/icount'), 12 | # (Join-Path $RepoRoot 'plugins/tiny'), 13 | # (Join-Path $RepoRoot 'plugins/tiny-system'), 14 | # (Join-Path $RepoRoot 'plugins/tracer') 15 | ) 16 | 17 | Push-Location $RepoRoot 18 | try { 19 | & cargo fmt --all --check -v 20 | } finally { 21 | Pop-Location 22 | } 23 | 24 | foreach ($checkPath in $checkPaths) { 25 | $manifestPath = Join-Path $checkPath 'Cargo.toml' 26 | 27 | & cargo +nightly hack --manifest-path $manifestPath ` 28 | --mutually-exclusive-features=plugin-api-v0,plugin-api-v1,plugin-api-v2,plugin-api-v3,plugin-api-v4,plugin-api-v5 ` 29 | --at-least-one-of=plugin-api-v0,plugin-api-v1,plugin-api-v2,plugin-api-v3,plugin-api-v4,plugin-api-v5 ` 30 | --feature-powerset ` 31 | --exclude-no-default-features ` 32 | check --lib -vv 33 | 34 | & cargo +nightly hack --manifest-path $manifestPath ` 35 | --mutually-exclusive-features=plugin-api-v0,plugin-api-v1,plugin-api-v2,plugin-api-v3,plugin-api-v4,plugin-api-v5 ` 36 | --at-least-one-of=plugin-api-v0,plugin-api-v1,plugin-api-v2,plugin-api-v3,plugin-api-v4,plugin-api-v5 ` 37 | --feature-powerset ` 38 | --exclude-no-default-features ` 39 | clippy --lib -vv 40 | } 41 | -------------------------------------------------------------------------------- /plugins/tiny/src/lib.rs: -------------------------------------------------------------------------------- 1 | use qemu_plugin::{HasCallbacks, PluginId, Register, Result, TranslationBlock, register}; 2 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 3 | use qemu_plugin::{RegisterDescriptor, VCPUIndex, qemu_plugin_get_registers}; 4 | 5 | #[derive(Default)] 6 | struct TinyTrace { 7 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 8 | registers: Vec>, 9 | } 10 | 11 | impl Register for TinyTrace {} 12 | 13 | impl HasCallbacks for TinyTrace { 14 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 15 | fn on_vcpu_init(&mut self, _id: PluginId, _vcpu_id: VCPUIndex) -> Result<()> { 16 | self.registers = qemu_plugin_get_registers()?; 17 | Ok(()) 18 | } 19 | fn on_translation_block_translate( 20 | &mut self, 21 | _id: PluginId, 22 | tb: TranslationBlock, 23 | ) -> Result<()> { 24 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 25 | let registers = self.registers.clone(); 26 | 27 | tb.instructions().try_for_each(|insn| { 28 | println!("{:08x}: {}", insn.vaddr(), insn.disas()?); 29 | 30 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 31 | { 32 | for register in ®isters { 33 | let value = register.read()?; 34 | println!(" {}: {:?}", register.name, value); 35 | } 36 | } 37 | 38 | Ok(()) 39 | }) 40 | } 41 | 42 | fn on_exit(&mut self, id: PluginId) -> Result<()> { 43 | println!("TinyTrace::on_exit() for id {}", id); 44 | Ok(()) 45 | } 46 | } 47 | 48 | register!(TinyTrace::default()); 49 | -------------------------------------------------------------------------------- /qemu-plugin-sys/src/qemu_plugin_api_v2.def: -------------------------------------------------------------------------------- 1 | EXPORTS 2 | qemu_plugin_bool_parse 3 | qemu_plugin_end_code 4 | qemu_plugin_entry_code 5 | qemu_plugin_get_hwaddr 6 | qemu_plugin_get_registers 7 | qemu_plugin_hwaddr_device_name 8 | qemu_plugin_hwaddr_is_io 9 | qemu_plugin_hwaddr_phys_addr 10 | qemu_plugin_insn_data 11 | qemu_plugin_insn_disas 12 | qemu_plugin_insn_haddr 13 | qemu_plugin_insn_size 14 | qemu_plugin_insn_symbol 15 | qemu_plugin_insn_vaddr 16 | qemu_plugin_mem_is_big_endian 17 | qemu_plugin_mem_is_sign_extended 18 | qemu_plugin_mem_is_store 19 | qemu_plugin_mem_size_shift 20 | qemu_plugin_num_vcpus 21 | qemu_plugin_outs 22 | qemu_plugin_path_to_binary 23 | qemu_plugin_read_register 24 | qemu_plugin_register_atexit_cb 25 | qemu_plugin_register_flush_cb 26 | qemu_plugin_register_vcpu_exit_cb 27 | qemu_plugin_register_vcpu_idle_cb 28 | qemu_plugin_register_vcpu_init_cb 29 | qemu_plugin_register_vcpu_insn_exec_cb 30 | qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu 31 | qemu_plugin_register_vcpu_mem_cb 32 | qemu_plugin_register_vcpu_mem_inline_per_vcpu 33 | qemu_plugin_register_vcpu_resume_cb 34 | qemu_plugin_register_vcpu_syscall_cb 35 | qemu_plugin_register_vcpu_syscall_ret_cb 36 | qemu_plugin_register_vcpu_tb_exec_cb 37 | qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu 38 | qemu_plugin_register_vcpu_tb_trans_cb 39 | qemu_plugin_reset 40 | qemu_plugin_scoreboard_find 41 | qemu_plugin_scoreboard_free 42 | qemu_plugin_scoreboard_new 43 | qemu_plugin_start_code 44 | qemu_plugin_tb_get_insn 45 | qemu_plugin_tb_n_insns 46 | qemu_plugin_tb_vaddr 47 | qemu_plugin_u64_add 48 | qemu_plugin_u64_get 49 | qemu_plugin_u64_set 50 | qemu_plugin_u64_sum 51 | qemu_plugin_uninstall 52 | qemu_plugin_vcpu_for_each 53 | -------------------------------------------------------------------------------- /qemu-plugin-sys/src/qemu_plugin_api_v3.def: -------------------------------------------------------------------------------- 1 | EXPORTS 2 | qemu_plugin_bool_parse 3 | qemu_plugin_end_code 4 | qemu_plugin_entry_code 5 | qemu_plugin_get_hwaddr 6 | qemu_plugin_get_registers 7 | qemu_plugin_hwaddr_device_name 8 | qemu_plugin_hwaddr_is_io 9 | qemu_plugin_hwaddr_phys_addr 10 | qemu_plugin_insn_data 11 | qemu_plugin_insn_disas 12 | qemu_plugin_insn_haddr 13 | qemu_plugin_insn_size 14 | qemu_plugin_insn_symbol 15 | qemu_plugin_insn_vaddr 16 | qemu_plugin_mem_is_big_endian 17 | qemu_plugin_mem_is_sign_extended 18 | qemu_plugin_mem_is_store 19 | qemu_plugin_mem_size_shift 20 | qemu_plugin_num_vcpus 21 | qemu_plugin_outs 22 | qemu_plugin_path_to_binary 23 | qemu_plugin_read_register 24 | qemu_plugin_register_atexit_cb 25 | qemu_plugin_register_flush_cb 26 | qemu_plugin_register_vcpu_exit_cb 27 | qemu_plugin_register_vcpu_idle_cb 28 | qemu_plugin_register_vcpu_init_cb 29 | qemu_plugin_register_vcpu_insn_exec_cb 30 | qemu_plugin_register_vcpu_insn_exec_cond_cb 31 | qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu 32 | qemu_plugin_register_vcpu_mem_cb 33 | qemu_plugin_register_vcpu_mem_inline_per_vcpu 34 | qemu_plugin_register_vcpu_resume_cb 35 | qemu_plugin_register_vcpu_syscall_cb 36 | qemu_plugin_register_vcpu_syscall_ret_cb 37 | qemu_plugin_register_vcpu_tb_exec_cb 38 | qemu_plugin_register_vcpu_tb_exec_cond_cb 39 | qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu 40 | qemu_plugin_register_vcpu_tb_trans_cb 41 | qemu_plugin_reset 42 | qemu_plugin_scoreboard_find 43 | qemu_plugin_scoreboard_free 44 | qemu_plugin_scoreboard_new 45 | qemu_plugin_start_code 46 | qemu_plugin_tb_get_insn 47 | qemu_plugin_tb_n_insns 48 | qemu_plugin_tb_vaddr 49 | qemu_plugin_u64_add 50 | qemu_plugin_u64_get 51 | qemu_plugin_u64_set 52 | qemu_plugin_u64_sum 53 | qemu_plugin_uninstall 54 | qemu_plugin_vcpu_for_each 55 | -------------------------------------------------------------------------------- /qemu-plugin-sys/src/qemu_plugin_api_v4.def: -------------------------------------------------------------------------------- 1 | EXPORTS 2 | qemu_plugin_bool_parse 3 | qemu_plugin_end_code 4 | qemu_plugin_entry_code 5 | qemu_plugin_get_hwaddr 6 | qemu_plugin_get_registers 7 | qemu_plugin_hwaddr_device_name 8 | qemu_plugin_hwaddr_is_io 9 | qemu_plugin_hwaddr_phys_addr 10 | qemu_plugin_insn_data 11 | qemu_plugin_insn_disas 12 | qemu_plugin_insn_haddr 13 | qemu_plugin_insn_size 14 | qemu_plugin_insn_symbol 15 | qemu_plugin_insn_vaddr 16 | qemu_plugin_mem_get_value 17 | qemu_plugin_mem_is_big_endian 18 | qemu_plugin_mem_is_sign_extended 19 | qemu_plugin_mem_is_store 20 | qemu_plugin_mem_size_shift 21 | qemu_plugin_num_vcpus 22 | qemu_plugin_outs 23 | qemu_plugin_path_to_binary 24 | qemu_plugin_read_memory_vaddr 25 | qemu_plugin_read_register 26 | qemu_plugin_register_atexit_cb 27 | qemu_plugin_register_flush_cb 28 | qemu_plugin_register_vcpu_exit_cb 29 | qemu_plugin_register_vcpu_idle_cb 30 | qemu_plugin_register_vcpu_init_cb 31 | qemu_plugin_register_vcpu_insn_exec_cb 32 | qemu_plugin_register_vcpu_insn_exec_cond_cb 33 | qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu 34 | qemu_plugin_register_vcpu_mem_cb 35 | qemu_plugin_register_vcpu_mem_inline_per_vcpu 36 | qemu_plugin_register_vcpu_resume_cb 37 | qemu_plugin_register_vcpu_syscall_cb 38 | qemu_plugin_register_vcpu_syscall_ret_cb 39 | qemu_plugin_register_vcpu_tb_exec_cb 40 | qemu_plugin_register_vcpu_tb_exec_cond_cb 41 | qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu 42 | qemu_plugin_register_vcpu_tb_trans_cb 43 | qemu_plugin_request_time_control 44 | qemu_plugin_reset 45 | qemu_plugin_scoreboard_find 46 | qemu_plugin_scoreboard_free 47 | qemu_plugin_scoreboard_new 48 | qemu_plugin_start_code 49 | qemu_plugin_tb_get_insn 50 | qemu_plugin_tb_n_insns 51 | qemu_plugin_tb_vaddr 52 | qemu_plugin_u64_add 53 | qemu_plugin_u64_get 54 | qemu_plugin_u64_set 55 | qemu_plugin_u64_sum 56 | qemu_plugin_uninstall 57 | qemu_plugin_update_ns 58 | qemu_plugin_vcpu_for_each 59 | -------------------------------------------------------------------------------- /qemu-plugin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "qemu-plugin" 3 | authors.workspace = true 4 | categories.workspace = true 5 | description = "High level bindings to the QEMU plugin API" 6 | edition.workspace = true 7 | homepage.workspace = true 8 | license.workspace = true 9 | publish.workspace = true 10 | readme.workspace = true 11 | repository.workspace = true 12 | version.workspace = true 13 | rust-version.workspace = true 14 | 15 | [dependencies] 16 | anyhow = { version = "1.0.99", optional = true } 17 | num-traits = { version = "0.2.19", optional = true } 18 | qemu-plugin-sys = { workspace = true, default-features = false } 19 | thiserror = "2.0.16" 20 | 21 | [target.'cfg(windows)'.dependencies] 22 | windows = { version = "0.62.0", features = [ 23 | "Win32_System_WindowsProgramming", 24 | "Win32_System_LibraryLoader", 25 | "Win32_Foundation", 26 | ] } 27 | 28 | libc = "0.2.175" 29 | libloading = "0.8.8" 30 | lazy_static = "1.5.0" 31 | 32 | [features] 33 | default = ["plugin-api-v5"] 34 | # Use the V0 plugin API, which is defined starting in version 4.2.0 35 | plugin-api-v0 = ["qemu-plugin-sys/plugin-api-v0"] 36 | # Use the V1 plugin API, which is defined starting in version 6.0.0 37 | plugin-api-v1 = ["qemu-plugin-sys/plugin-api-v1"] 38 | # Use the V2 plugin API, which is defined starting in version 9.0.0 39 | plugin-api-v2 = ["qemu-plugin-sys/plugin-api-v2"] 40 | # Use the V3 plugin API, which is defined starting in version 9.1.0 41 | plugin-api-v3 = ["qemu-plugin-sys/plugin-api-v3"] 42 | # Use the V4 plugin API, which is defined starting in version 9.2.0 43 | plugin-api-v4 = ["qemu-plugin-sys/plugin-api-v4"] 44 | # Use the V5 plugin API, which is defined starting in version 10.1.0 45 | plugin-api-v5 = ["qemu-plugin-sys/plugin-api-v5"] 46 | # Enable the `num-traits` dependency, which enables endian-aware register read 47 | # operations 48 | num-traits = ["dep:num-traits"] 49 | # Enable the `anyhow` dependency, which provides compatibility for converting 50 | # from `anyhow::Error` to a `qemu_plugin::Error` 51 | anyhow = ["dep:anyhow"] 52 | 53 | [lints.rust] 54 | incomplete-features = "allow" 55 | -------------------------------------------------------------------------------- /qemu-plugin-sys/src/qemu_plugin_api_v5.def: -------------------------------------------------------------------------------- 1 | EXPORTS 2 | qemu_plugin_bool_parse 3 | qemu_plugin_end_code 4 | qemu_plugin_entry_code 5 | qemu_plugin_get_hwaddr 6 | qemu_plugin_get_registers 7 | qemu_plugin_hwaddr_device_name 8 | qemu_plugin_hwaddr_is_io 9 | qemu_plugin_hwaddr_phys_addr 10 | qemu_plugin_insn_data 11 | qemu_plugin_insn_disas 12 | qemu_plugin_insn_haddr 13 | qemu_plugin_insn_size 14 | qemu_plugin_insn_symbol 15 | qemu_plugin_insn_vaddr 16 | qemu_plugin_mem_get_value 17 | qemu_plugin_mem_is_big_endian 18 | qemu_plugin_mem_is_sign_extended 19 | qemu_plugin_mem_is_store 20 | qemu_plugin_mem_size_shift 21 | qemu_plugin_num_vcpus 22 | qemu_plugin_outs 23 | qemu_plugin_path_to_binary 24 | qemu_plugin_read_memory_hwaddr 25 | qemu_plugin_read_memory_vaddr 26 | qemu_plugin_read_register 27 | qemu_plugin_register_atexit_cb 28 | qemu_plugin_register_flush_cb 29 | qemu_plugin_register_vcpu_exit_cb 30 | qemu_plugin_register_vcpu_idle_cb 31 | qemu_plugin_register_vcpu_init_cb 32 | qemu_plugin_register_vcpu_insn_exec_cb 33 | qemu_plugin_register_vcpu_insn_exec_cond_cb 34 | qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu 35 | qemu_plugin_register_vcpu_mem_cb 36 | qemu_plugin_register_vcpu_mem_inline_per_vcpu 37 | qemu_plugin_register_vcpu_resume_cb 38 | qemu_plugin_register_vcpu_syscall_cb 39 | qemu_plugin_register_vcpu_syscall_ret_cb 40 | qemu_plugin_register_vcpu_tb_exec_cb 41 | qemu_plugin_register_vcpu_tb_exec_cond_cb 42 | qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu 43 | qemu_plugin_register_vcpu_tb_trans_cb 44 | qemu_plugin_request_time_control 45 | qemu_plugin_reset 46 | qemu_plugin_scoreboard_find 47 | qemu_plugin_scoreboard_free 48 | qemu_plugin_scoreboard_new 49 | qemu_plugin_start_code 50 | qemu_plugin_tb_get_insn 51 | qemu_plugin_tb_n_insns 52 | qemu_plugin_tb_vaddr 53 | qemu_plugin_translate_vaddr 54 | qemu_plugin_u64_add 55 | qemu_plugin_u64_get 56 | qemu_plugin_u64_set 57 | qemu_plugin_u64_sum 58 | qemu_plugin_uninstall 59 | qemu_plugin_update_ns 60 | qemu_plugin_vcpu_for_each 61 | qemu_plugin_write_memory_hwaddr 62 | qemu_plugin_write_memory_vaddr 63 | qemu_plugin_write_register 64 | -------------------------------------------------------------------------------- /qemu-plugin-sys/build.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | #[cfg(windows)] 3 | use anyhow::anyhow; 4 | #[cfg(windows)] 5 | use std::{env::var, path::PathBuf, process::Command, str::FromStr}; 6 | 7 | #[cfg(feature = "plugin-api-v0")] 8 | pub const PLUGIN_API_DEF_FILE_NAME: &str = "qemu_plugin_api_v0.def"; 9 | #[cfg(feature = "plugin-api-v1")] 10 | pub const PLUGIN_API_DEF_FILE_NAME: &str = "qemu_plugin_api_v1.def"; 11 | #[cfg(feature = "plugin-api-v2")] 12 | pub const PLUGIN_API_DEF_FILE_NAME: &str = "qemu_plugin_api_v2.def"; 13 | #[cfg(feature = "plugin-api-v3")] 14 | pub const PLUGIN_API_DEF_FILE_NAME: &str = "qemu_plugin_api_v3.def"; 15 | #[cfg(feature = "plugin-api-v4")] 16 | pub const PLUGIN_API_DEF_FILE_NAME: &str = "qemu_plugin_api_v4.def"; 17 | #[cfg(feature = "plugin-api-v5")] 18 | pub const PLUGIN_API_DEF_FILE_NAME: &str = "qemu_plugin_api_v5.def"; 19 | 20 | #[cfg(windows)] 21 | fn out_dir() -> Result { 22 | Ok(PathBuf::from( 23 | var("OUT_DIR").map_err(|e| anyhow!("OUT_DIR not set: {e}"))?, 24 | )) 25 | } 26 | 27 | fn main() -> Result<()> { 28 | #[cfg(windows)] 29 | { 30 | let out_dir = out_dir()?; 31 | let def_file = PathBuf::from_str(&format!("src/{PLUGIN_API_DEF_FILE_NAME}"))?; 32 | let def_file_str = def_file.to_string_lossy(); 33 | let lib_file = out_dir.join("qemu_plugin_api.lib"); 34 | let lib_file_str = lib_file.to_string_lossy(); 35 | let ch = Command::new("dlltool") 36 | .args([ 37 | "--input-def", 38 | &def_file_str, 39 | "--output-delaylib", 40 | &lib_file_str, 41 | "--dllname", 42 | "qemu.exe", 43 | ]) 44 | .spawn()? 45 | .wait()?; 46 | if !ch.success() { 47 | return Err(anyhow!("dlltool failed")); 48 | } 49 | println!("cargo:rustc-link-search={}", out_dir.display()); 50 | println!("cargo:rustc-link-lib=qemu_plugin_api"); 51 | } 52 | 53 | #[cfg(all(target_family = "unix", not(target_os = "macos")))] 54 | { 55 | println!("cargo:rustc-link-arg-cdylib=-Wl,-z,undefs") 56 | } 57 | 58 | #[cfg(target_os = "macos")] 59 | { 60 | println!("cargo::rustc-cdylib-link-arg=-undefined"); 61 | println!("cargo::rustc-cdylib-link-arg=dynamic_lookup"); 62 | } 63 | 64 | Ok(()) 65 | } 66 | -------------------------------------------------------------------------------- /qemu-plugin/src/scoreboard/mod.rs: -------------------------------------------------------------------------------- 1 | //! Scoreboard-related functionality for QEMU plugins 2 | 3 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 4 | use crate::VCPUIndex; 5 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 6 | use crate::sys::qemu_plugin_scoreboard; 7 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 8 | use std::{marker::PhantomData, mem::MaybeUninit}; 9 | 10 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 11 | #[derive(Debug)] 12 | /// A wrapper structure for a `qemu_plugin_scoreboard *`. This is a way of having one 13 | /// entry per VCPU, the count of which is managed automatically by QEMU. Keep in mind 14 | /// that additional entries *and* existing entries will be allocated and reallocated by 15 | /// *qemu*, not by the plugin, so every use of a `T` should include a check for whether 16 | /// it is initialized. 17 | pub struct Scoreboard<'a, T> 18 | where 19 | T: Sized, 20 | { 21 | handle: usize, 22 | marker: PhantomData<&'a T>, 23 | } 24 | 25 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 26 | impl<'a, T> Scoreboard<'a, T> { 27 | /// Allocate a new scoreboard object. This must be freed by calling 28 | /// `qemu_plugin_scoreboard_free` (or by being dropped). 29 | pub fn new() -> Self { 30 | let handle = 31 | unsafe { crate::sys::qemu_plugin_scoreboard_new(std::mem::size_of::()) as usize }; 32 | 33 | Self { 34 | handle, 35 | marker: PhantomData, 36 | } 37 | } 38 | 39 | /// Returns a reference to entry of a scoreboard matching a given vcpu index. This address 40 | /// is only valid until the next call to `get` or `set`. 41 | pub fn find<'b>(&mut self, vcpu_index: VCPUIndex) -> &'b mut MaybeUninit { 42 | unsafe { 43 | &mut *(crate::sys::qemu_plugin_scoreboard_find( 44 | self.handle as *mut qemu_plugin_scoreboard, 45 | vcpu_index, 46 | ) as *mut MaybeUninit) 47 | } 48 | } 49 | } 50 | 51 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 52 | impl<'a, T> Default for Scoreboard<'a, T> { 53 | fn default() -> Self { 54 | Self::new() 55 | } 56 | } 57 | 58 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 59 | impl<'a, T> Drop for Scoreboard<'a, T> { 60 | fn drop(&mut self) { 61 | unsafe { 62 | crate::sys::qemu_plugin_scoreboard_free(self.handle as *mut qemu_plugin_scoreboard) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /qemu-plugin/src/win_link_hook/mod.rs: -------------------------------------------------------------------------------- 1 | //! Hook for linking against exported QEMU symbols at runtime 2 | 3 | use windows::Win32::Foundation::HMODULE; 4 | use windows::Win32::System::WindowsProgramming::DELAYLOAD_INFO; 5 | 6 | /// The helper function for linker-supported delayed loading which is what actually 7 | /// loads the DLL at runtime. 8 | type DelayHook = unsafe extern "C" fn(dli_notify: DliNotify, pdli: DELAYLOAD_INFO) -> HMODULE; 9 | 10 | #[unsafe(no_mangle)] 11 | /// Helper function invoked when failures occur in delay linking (as opposed to 12 | /// notifications) 13 | static __pfnDliFailureHook2: DelayHook = delaylink_hook; 14 | 15 | #[allow(dead_code, clippy::enum_variant_names)] //We only need one of these variants. 16 | #[repr(C)] 17 | /// Delay load import hook notifications 18 | /// 19 | enum DliNotify { 20 | /// Used to bypass 21 | DliNoteStartProcessing = 0, 22 | /// Called just before LoadLibrary, can override w/ new HMODULE return val 23 | DliNotePreLoadLibrary, 24 | /// Called just before GetProcAddress, override w/ new FARPROC return value 25 | DliNotePreGetProcAddress, 26 | /// Failed to load library, fix it by 27 | /// returning a valid HMODULE 28 | DliFailLoadLib, 29 | /// Failed to get proc address, fix it by 30 | /// returning a valid FARPROC 31 | DliFailGetProc, 32 | /// Called after all processing is done, no bypass possible at this point except by 33 | /// longjmp()/throw()/RaiseException. 34 | DliNoteEndProcessing, 35 | } 36 | 37 | /// Helper function invoked when notifications or failures occur in delay linking 38 | /// 39 | /// # Arguments 40 | /// 41 | /// * `dli_notify` - The type of notification 42 | /// * `pdli` - The delay load information 43 | /// 44 | /// # Return value 45 | /// 46 | /// * `HMODULE` - The handle to the module 47 | extern "C" fn delaylink_hook(dli_notify: DliNotify, pdli: DELAYLOAD_INFO) -> HMODULE { 48 | if let DliNotify::DliFailLoadLib = dli_notify { 49 | // SAFETY: Conversion of `PCSTR` to String is not safe because it involves an unchecked 50 | // nul-byte dependent `strcpy`. In this instance, it is as safe as OS guarantees because 51 | // the target dll name is provided by Windows and is null-terminated. 52 | let name = unsafe { pdli.TargetDllName.to_string() }.unwrap_or_default(); 53 | 54 | // NOTE: QEMU executables on windows are named qemu-system.*.exe 55 | if name == "qemu.exe" { 56 | return HMODULE( 57 | libloading::os::windows::Library::this() 58 | .expect("Get QEMU module") 59 | .into_raw() as *mut _, 60 | ); 61 | } 62 | } 63 | 64 | HMODULE::default() 65 | } 66 | -------------------------------------------------------------------------------- /.github/rsrc/id_rsa: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn 3 | NhAAAAAwEAAQAAAYEA2bW01Jm9ujWY0VoDuL3D4XmQ09D1Tkyg98MQoXNg7nZ6IJP2F8PW 4 | N4OHRwrd6/flcDj5c8YfgkwqCryu8f/qq+nlgaQ8dskJIUnaLa3YtVax2KrQu/2zHH6StI 5 | abiQYKM+6L0OPZxwrbX+uSfs/QEAF0KCxcXVDjvXjpvOla+MWunm9B+s11yj+qesSc+MWp 6 | gZKvZYH8FmIIy+P77cnnjDgXx51vHHdO8rI4uLuoNpaMQjqbtpGP3pFYbqAId1lrQSBzVJ 7 | 4ybrhZw/+pfzhoXrHDCd4lTd6fihj0IHQkPmuxgd09EZONfsiDGX7iUoTrRvGt9WjCVClz 8 | vjPgCIy/0hpUG4/+1GAtX4V3rN84gT1e+qThFL8GimiPzUXL1abT5YQiWCbN/eJ90kd/Tj 9 | fhsAhD1xOOK8TlHi/28COF+czhSektncm0Wrng5wWYpZkJG7yX4GE5MKWikgCXB4SvCW81 10 | 0+Ievgk1g7nL2c8oynYixG2JfE4q3gcFmOf9UwixAAAFiE9S/jRPUv40AAAAB3NzaC1yc2 11 | EAAAGBANm1tNSZvbo1mNFaA7i9w+F5kNPQ9U5MoPfDEKFzYO52eiCT9hfD1jeDh0cK3ev3 12 | 5XA4+XPGH4JMKgq8rvH/6qvp5YGkPHbJCSFJ2i2t2LVWsdiq0Lv9sxx+krSGm4kGCjPui9 13 | Dj2ccK21/rkn7P0BABdCgsXF1Q47146bzpWvjFrp5vQfrNdco/qnrEnPjFqYGSr2WB/BZi 14 | CMvj++3J54w4F8edbxx3TvKyOLi7qDaWjEI6m7aRj96RWG6gCHdZa0Egc1SeMm64WcP/qX 15 | 84aF6xwwneJU3en4oY9CB0JD5rsYHdPRGTjX7Igxl+4lKE60bxrfVowlQpc74z4AiMv9Ia 16 | VBuP/tRgLV+Fd6zfOIE9Xvqk4RS/Bopoj81Fy9Wm0+WEIlgmzf3ifdJHf0434bAIQ9cTji 17 | vE5R4v9vAjhfnM4UnpLZ3JtFq54OcFmKWZCRu8l+BhOTClopIAlweErwlvNdPiHr4JNYO5 18 | y9nPKMp2IsRtiXxOKt4HBZjn/VMIsQAAAAMBAAEAAAGAB2nBl1zoDgRz0HqcvnRXPHjsyJ 19 | qglLbFIySdLwswR3RpMI1rO3hNlbi0wTN2dqnLciqde17JXUDhzFlsCVVcELgjWoqMrMSI 20 | Cx4yN7yYAMY7wm+AEauoBvoMHamo94WpMKcgc4ejp2x4J3QsegUSXg4nnfPTPhHyXEX3Cw 21 | nE4UxNZ6uNCwjGmFE6SUmDOREVdlIX5vhh7KLwTt2Dqz9VEyQokqFqqnTpTzgoeGla5ydx 22 | jZKEcXczx3nKYaNxQFue/ElJw9KabSAZ3v3T9rzw0z3rP7n0Bvn/xs7+i2cKJhJIkQtPUa 23 | Z1r/GMrE+yp3BMYw5vcqKx+OOvNPkoIiRSzuyJ3qJK4okibtDCkYfPk/FxuCX/7nVlL/Ak 24 | n3CaVWWWGTGOyohW6+MxJ/z16gqgXNy8gwKRnsIEUtpc7RoGIO8MT5fVrMOhlwmJHnPnEm 25 | 2/185hraTHLrb8V6suTwCvbzB65e3TSQaJXyMqOyYSdOXM3fmpZrSmHw7y8DqJ74lhAAAA 26 | wDIxqOfvRc7j736AKgp2qDhJftc/mb8osZsbPgApCbVLZmI9Zvy1PFDEcNfDunU06Mue+2 27 | yaWhaeQ180UAqJ4E3vgE1OkBMae+bPyk/v5HeGmZfxvcFhxP87/zMxqARxVLy2ECDS2NR5 28 | A/G5bL4VJatmFJiAa5N137X5FDlctvy5h+ZEhMJdy/LYCJld5mM7EMoa3AKErkSnhdI+mH 29 | v9x34mWSzeB198qGb7QMqvhGLu7ZEVqRAIaAZ5HhkIx8oO0QAAAMEA97+ZMXEbNQFTyFMO 30 | GuT42A3wU0RJmKqRD3wQDeW5Ua/RF+WeaMxAWRZG5Pj8X0p4WAsupJEfU4FZFsQhmu0Kzr 31 | vvTEHmFBmupnYdwmQSDk4SkJLrXNQSpkFoxTdSDZ3/P+WgDAdC1pU1/ClAXS7gQZhWLzCo 32 | IGDIFkUEvcUCxh0Jf8YszFWWb3WZ7lOBFdQCyS6SmvQtyq+M8ghi0Ce+btU3TvpFPRKa0p 33 | LT9vw3LMhjeYidBrzZu/gvWWuhJb3hAAAAwQDg9fyPweE++XeHXtyzqDZIRRw5O/fsEGow 34 | cgSDfII2zJhiPOzO55HuguS92uak7oLsoFx5+Ulw9jdFu+spn2W2+iAaSlg9CTQVSt/Z/W 35 | gyN48rYziYtoC0eKcKNdXoMX+ovAdwRA7LfGVc3MKm1jX+tlJ6lcWT5ZAodGdDX+mzMJfA 36 | 8U6K4Z/lgId5D/xZjNMm5n+dZ7rnLcQoUsa7nnrJ6wob4nDDEYDIGx4FvDIyyEupRmesmt 37 | 2S9f5i0kz7hNEAAAAQZmVkb3JhQGxvY2FsaG9zdAECAw== 38 | -----END OPENSSH PRIVATE KEY----- 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qemu-rs 2 | 3 | This repository provides tools for building [QEMU](https://www.qemu.org) 4 | [TCG](https://www.qemu.org/docs/master/devel/index-tcg.html) plugins in Rust! 5 | 6 | If you're unfamiliar with TCG plugins, they provide the ability to get callbacks 7 | on a range of events: 8 | 9 | - VCPU Initialize (QEMU system/softmmu only) 10 | - VCPU Exit (QEMU system/softmmu only) 11 | - VCPU Idle (QEMU system/softmmu only) 12 | - Translation Block Cache Flush 13 | - Translation Block Translation: The TCG translated native code to TCG instructions 14 | - Translation Block Executed: A block of translated instructions executes 15 | - Instruction Executed: A specific translated instruction executes 16 | - Instruction Memory Access: An instruction executes that accesses memory 17 | - Syscall Executed (QEMU user mode only) 18 | - Syscall Return (QEMU user mode only) 19 | 20 | They also allow you to read and write registers, virtual memory, and physical 21 | memory. This provides the building blocks for a number of analyses and tools 22 | from profilers to fuzzers to tracers and beyond. 23 | 24 | ## Quickstart 25 | 26 | To build a plugin on qemu-rs, all you need to do is: 27 | 28 | 1. Create a new crate: `cargo new --lib myplugin` 29 | 2. Make it a `cdylib` crate type and add features to toggle 30 | between support for different versions of the QEMU API (see [versions](#versions)) 31 | 32 | ```toml 33 | cat <> myplugin/Cargo.toml 34 | [lib] 35 | crate-type = ["cdylib"] 36 | 37 | [features] 38 | default = ["plugin-api-v5"] 39 | plugin-api-v0 = ["qemu-plugin/plugin-api-v0"] 40 | plugin-api-v1 = ["qemu-plugin/plugin-api-v1"] 41 | plugin-api-v2 = ["qemu-plugin/plugin-api-v2"] 42 | plugin-api-v3 = ["qemu-plugin/plugin-api-v3"] 43 | plugin-api-v4 = ["qemu-plugin/plugin-api-v4"] 44 | plugin-api-v5 = ["qemu-plugin/plugin-api-v5"] 45 | EOF 46 | ``` 47 | 3. Add dependencies: `cargo -C myplugin add qemu-plugin anyhow` 48 | 4. Create a new `lib.rs` that declares a plugin: 49 | 50 | ```rust 51 | cat < myplugin/src/lib.rs 52 | use anyhow::Result; 53 | use qemu_plugin::{ 54 | HasCallbacks, Register, PluginId, TranslationBlock, CallbackFlags, register 55 | }; 56 | 57 | struct QemuPlugin; 58 | 59 | impl Register for QemuPlugin {} 60 | impl HasCallbacks for QemuPlugin { 61 | fn on_translation_block_translate( 62 | &mut self, 63 | _id: PluginId, 64 | tb: TranslationBlock 65 | ) -> Result<()> { 66 | tb.instructions().try_for_each(|insn| { 67 | let insn_disas = insn.disas()?; 68 | insn.register_execute_callback_flags(|vcpu_index| { 69 | println!("[{vcpu_index}]: {insn_disas}"); 70 | }, 71 | CallbackFlags::QEMU_PLUGIN_CB_NO_REGS 72 | ) 73 | }) 74 | } 75 | } 76 | 77 | register!(QemuPlugin); 78 | EOF 79 | ``` 80 | 81 | 5. Build your plugin: `cargo build -r` 82 | 6. Make sure you have a `qemu` built with plugin support: `qemu-x86_64 -h | grep qemu` 83 | 7. Run your plugin: `qemu-x86_64 -plugin target/release/libmyplugin.so /bin/ls` 84 | 85 | ## Versions 86 | 87 | QEMU versions its plugin API --- plugins are mostly forward compatible but 88 | are not backward compatible. 89 | 90 | The following QEMU versions introduce the corresponding plugin API versions. 91 | 92 | | QEMU Version | Plugin API Version | 93 | | ------------ | ------------------ | 94 | | 4.2.0 | 0 | 95 | | 6.0.0 | 1 | 96 | | 9.0.0 | 2 | 97 | | 9.1.0 | 3 | 98 | | 9.2.0 | 4 | 99 | | 10.1.0 | 5 | -------------------------------------------------------------------------------- /scripts/ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright (C) 2023 Intel Corporation 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | #Run workflows locally using act 7 | 8 | SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) 9 | WORKFLOW_FILE="${SCRIPT_DIR}/../.github/workflows/ci.yml" 10 | SECRETS_FILE="${SCRIPT_DIR}/../.secrets" 11 | 12 | if [ ! -f "${SECRETS_FILE}" ]; then 13 | echo "No file '${SECRETS_FILE}' found. Please create one. It must have the following keys: 14 | GITHUB_TOKEN" \ 15 | "You can find your GitHub token with 'gh auth token'" 16 | exit 1 17 | fi 18 | 19 | if ! command -v act &>/dev/null; then 20 | echo "act must be installed! Install at https://github.com/nektos/act" 21 | exit 1 22 | fi 23 | 24 | if ! command -v unbuffer &>/dev/null; then 25 | echo "unbuffer must be installed! Install 'expect' from your package manager" 26 | exit 1 27 | fi 28 | 29 | populate_env_file() { 30 | ENV_FILE="${1}" 31 | echo "Attempting automatic configuration of proxy with ENV_FILE=${ENV_FILE}" 32 | 33 | if [ -z "${HTTP_PROXY}" ] && [ -f ~/.docker/config.json ]; then 34 | HTTP_PROXY=$(grep httpProxy ~/.docker/config.json | awk -F'\"[:space:]*:[:space:]*' '{split($2,a,"\""); print a[2]}') 35 | echo "Exported docker config HTTP_PROXY=${HTTP_PROXY}" 36 | elif [ -n "${HTTP_PROXY}" ]; then 37 | echo "Exported docker config HTTP_PROXY=${HTTP_PROXY}" 38 | fi 39 | echo "HTTP_PROXY=${HTTP_PROXY}" >>"${ENV_FILE}" 40 | echo "proxy=${HTTP_PROXY}" >>"${ENV_FILE}" 41 | 42 | if [ -z "${HTTPS_PROXY}" ] && [ -f ~/.docker/config.json ]; then 43 | HTTPS_PROXY=$(grep httpsProxy ~/.docker/config.json | awk -F'\"[:space:]*:[:space:]*' '{split($2,a,"\""); print a[2]}') 44 | echo "Exported docker config HTTPS_PROXY=${HTTPS_PROXY}" 45 | elif [ -n "${HTTPS_PROXY}" ]; then 46 | echo "Exported docker config HTTPS_PROXY=${HTTPS_PROXY}" 47 | fi 48 | echo "HTTPS_PROXY=${HTTPS_PROXY}" >>"${ENV_FILE}" 49 | 50 | if [ -z "${http_proxy}" ] && [ -f ~/.docker/config.json ]; then 51 | http_proxy=$(grep httpProxy ~/.docker/config.json | awk -F'\"[:space:]*:[:space:]*' '{split($2,a,"\""); print a[2]}') 52 | echo "Exported docker config http_proxy=${http_proxy}" 53 | elif [ -n "${http_proxy}" ]; then 54 | echo "Exported docker config http_proxy=${http_proxy}" 55 | fi 56 | echo "http_proxy=${http_proxy}" >>"${ENV_FILE}" 57 | 58 | if [ -z "${https_proxy}" ] && [ -f ~/.docker/config.json ]; then 59 | https_proxy=$(grep httpsProxy ~/.docker/config.json | awk -F'\"[:space:]*:[:space:]*' '{split($2,a,"\""); print a[2]}') 60 | echo "Exported docker config https_proxy=${https_proxy}" 61 | elif [ -n "${https_proxy}" ]; then 62 | echo "Exported docker config https_proxy=${https_proxy}" 63 | fi 64 | echo "https_proxy=${https_proxy}" >>"${ENV_FILE}" 65 | 66 | if [ -z "${NO_PROXY}" ] && [ -f ~/.docker/config.json ]; then 67 | NO_PROXY=$(grep noProxy ~/.docker/config.json | awk -F'\"[:space:]*:[:space:]*' '{split($2,a,"\""); print a[2]}') 68 | echo "Exported docker config NO_PROXY=${NO_PROXY}" 69 | elif [ -n "${NO_PROXY}" ]; then 70 | echo "Exported docker config NO_PROXY=${NO_PROXY}" 71 | fi 72 | echo "NO_PROXY=${NO_PROXY}" >>"${ENV_FILE}" 73 | 74 | cat "${ENV_FILE}" 75 | } 76 | 77 | ENV_FILE=$(mktemp) 78 | ARTIFACT_DIR=$(mktemp -d) 79 | populate_env_file "${ENV_FILE}" 80 | mkdir -p "${SCRIPT_DIR}/../.github/logs/" 81 | unbuffer act -W "${WORKFLOW_FILE}" --env-file="${ENV_FILE}" --secret-file="${SECRETS_FILE}" \ 82 | --artifact-server-path "${ARTIFACT_DIR}" \ 83 | "$@" | tee "${SCRIPT_DIR}/../.github/logs/$(date '+%F-%T').log" 84 | rm "${ENV_FILE}" 85 | -------------------------------------------------------------------------------- /qemu-plugin/src/glib/mod.rs: -------------------------------------------------------------------------------- 1 | //! GLib FFI bindings 2 | 3 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 4 | use crate::sys::{GArray, GByteArray}; 5 | use std::ffi::c_void; 6 | 7 | #[cfg(not(windows))] 8 | unsafe extern "C" { 9 | /// glib g_free is provided by the QEMU program we are being linked into 10 | pub(crate) fn g_free(mem: *mut c_void); 11 | } 12 | 13 | #[cfg(all( 14 | not(windows), 15 | not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")) 16 | ))] 17 | unsafe extern "C" { 18 | /// glib g_byte_array_new is provided by the QEMU program we are being linked into 19 | pub(crate) fn g_byte_array_new() -> *mut GByteArray; 20 | /// glib g_byte_array_free is provided by the QEMU program we are being linked into 21 | pub(crate) fn g_byte_array_free(array: *mut GByteArray, free_segment: bool) -> *mut u8; 22 | /// glib g_array_free is provided byt he QEMU program we are being linked into 23 | pub(crate) fn g_array_free(array: *mut GArray, free_segment: bool) -> *mut u8; 24 | } 25 | 26 | #[cfg(windows)] 27 | lazy_static::lazy_static! { 28 | static ref G_FREE : libloading::os::windows::Symbol = { 29 | let lib = 30 | libloading::os::windows::Library::open_already_loaded("libglib-2.0-0.dll") 31 | .expect("libglib-2.0-0.dll should already be loaded"); 32 | // SAFETY 33 | // "Users of `Library::get` should specify the correct type of the function loaded". 34 | // We are specifying the correct type of g_free above (`void g_free(void*)`) 35 | unsafe{lib.get(b"g_free").expect("find g_free")} 36 | }; 37 | } 38 | 39 | #[cfg(all( 40 | windows, 41 | not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")) 42 | ))] 43 | lazy_static::lazy_static! { 44 | static ref G_BYTE_ARRAY_NEW: libloading::os::windows::Symbol *mut GByteArray> = { 45 | let lib = 46 | libloading::os::windows::Library::open_already_loaded("libglib-2.0-0.dll") 47 | .expect("libglib-2.0-0.dll should already be loaded"); 48 | // SAFETY 49 | // "Users of `Library::get` should specify the correct type of the function loaded". 50 | // We are specifying the correct type of g_free above (`void g_free(void*)`) 51 | unsafe{lib.get(b"g_byte_array_new").expect("find g_byte_array_new")} 52 | }; 53 | } 54 | 55 | #[cfg(all( 56 | windows, 57 | not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")) 58 | ))] 59 | lazy_static::lazy_static! { 60 | static ref G_BYTE_ARRAY_FREE: libloading::os::windows::Symbol *mut u8> = { 61 | let lib = 62 | libloading::os::windows::Library::open_already_loaded("libglib-2.0-0.dll") 63 | .expect("libglib-2.0-0.dll should already be loaded"); 64 | // SAFETY 65 | // "Users of `Library::get` should specify the correct type of the function loaded". 66 | // We are specifying the correct type of g_free above (`void g_free(void*)`) 67 | unsafe{lib.get(b"g_byte_array_free").expect("find g_byte_array_free")} 68 | }; 69 | } 70 | 71 | #[cfg(all( 72 | windows, 73 | not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")) 74 | ))] 75 | lazy_static::lazy_static! { 76 | static ref G_ARRAY_FREE: libloading::os::windows::Symbol *mut u8> = { 77 | let lib = 78 | libloading::os::windows::Library::open_already_loaded("libglib-2.0-0.dll") 79 | .expect("libglib-2.0-0.dll should already be loaded"); 80 | // SAFETY 81 | // "Users of `Library::get` should specify the correct type of the function loaded". 82 | // We are specifying the correct type of g_free above (`void g_free(void*)`) 83 | unsafe{lib.get(b"g_array_free").expect("find g_array_free")} 84 | }; 85 | } 86 | 87 | #[cfg(windows)] 88 | /// Define g_free, because on Windows we cannot delay link it 89 | /// 90 | /// # Safety 91 | /// 92 | /// `g_free` must *only* be used to deallocate values allocated with `g_malloc`, regardless of 93 | /// its documented guarantees about wrapping the system allocator. QEMU plugin APIs which return 94 | /// such values are documented to do so, and it is safe to call `g_free` on these values 95 | /// provided they are not used afterward. 96 | pub(crate) unsafe fn g_free(mem: *mut c_void) { 97 | unsafe { G_FREE(mem) } 98 | } 99 | 100 | #[cfg(all( 101 | windows, 102 | not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")) 103 | ))] 104 | /// Define g_byte_array_new, because on Windows we cannot delay link it 105 | /// 106 | /// # Safety 107 | /// 108 | /// `g_byte_array_new` must be used to allocate a new `GByteArray` which can be used to store 109 | /// arbitrary data. The returned `GByteArray` must be freed with `g_byte_array_free` when it is 110 | /// no longer needed. 111 | pub(crate) unsafe fn g_byte_array_new() -> *mut GByteArray { 112 | unsafe { G_BYTE_ARRAY_NEW() } 113 | } 114 | 115 | #[cfg(all( 116 | windows, 117 | not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")) 118 | ))] 119 | /// Define g_byte_array_free, because on Windows we cannot delay link it 120 | /// 121 | /// # Safety 122 | /// 123 | /// `g_byte_array_free` must be used to free a `GByteArray` allocated with `g_byte_array_new`. 124 | /// The `free_segment` argument should be `true` if the data stored in the `GByteArray` should 125 | /// also be freed. If `false`, the data will not be freed, and the caller is responsible for 126 | /// freeing it with `g_free`. 127 | pub(crate) unsafe fn g_byte_array_free(array: *mut GByteArray, free_segment: bool) -> *mut u8 { 128 | unsafe { G_BYTE_ARRAY_FREE(array as *mut c_void, free_segment) } 129 | } 130 | 131 | #[cfg(all( 132 | windows, 133 | not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")) 134 | ))] 135 | /// Define g_array_free, because on Windows we cannot delay link it 136 | /// 137 | /// # Safety 138 | /// 139 | /// `g_array_free` must be used to free a `GArray` allocated with `g_array_new`. The `free_segment` 140 | /// argument should be `true` if the data stored in the `GArray` should also be freed. If `false`, 141 | /// the data will not be freed, and the caller is responsible for freeing it with `g_free`. 142 | pub(crate) unsafe fn g_array_free(array: *mut GArray, free_segment: bool) -> *mut u8 { 143 | unsafe { G_ARRAY_FREE(array as *mut c_void, free_segment) } 144 | } 145 | -------------------------------------------------------------------------------- /qemu-plugin/src/register/mod.rs: -------------------------------------------------------------------------------- 1 | //! Register-related functionality for QEMU plugins 2 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 3 | use crate::{ 4 | Error, Result, 5 | sys::{qemu_plugin_read_register, qemu_plugin_reg_descriptor, qemu_plugin_register}, 6 | }; 7 | #[cfg(all( 8 | not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")), 9 | feature = "num-traits" 10 | ))] 11 | use num_traits::{FromBytes, PrimInt}; 12 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 13 | use std::{ 14 | ffi::CStr, 15 | fmt::{Debug, Formatter}, 16 | marker::PhantomData, 17 | }; 18 | 19 | #[cfg(not(any( 20 | feature = "plugin-api-v0", 21 | feature = "plugin-api-v1", 22 | feature = "plugin-api-v2", 23 | feature = "plugin-api-v3", 24 | feature = "plugin-api-v4" 25 | )))] 26 | use crate::sys::{GByteArray, qemu_plugin_write_register}; 27 | 28 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 29 | #[derive(Clone)] 30 | /// Wrapper structure for a `qemu_plugin_register_descriptor` 31 | /// 32 | /// # Safety 33 | /// 34 | /// This structure is safe to use as long as the pointer is valid. The pointer is 35 | /// always opaque, and therefore may not be dereferenced. 36 | pub struct RegisterDescriptor<'a> { 37 | /// Opaque handle to the register for retrieving the value with 38 | /// qemu_plugin_read_register 39 | handle: usize, 40 | /// The register name 41 | pub name: String, 42 | /// Optional feature descriptor 43 | pub feature: Option, 44 | marker: PhantomData<&'a ()>, 45 | } 46 | 47 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 48 | impl<'a> From for RegisterDescriptor<'a> { 49 | fn from(descriptor: qemu_plugin_reg_descriptor) -> Self { 50 | let name = unsafe { CStr::from_ptr(descriptor.name) } 51 | .to_str() 52 | .expect("Register name is not valid UTF-8") 53 | .to_string(); 54 | 55 | let feature = if descriptor.feature.is_null() { 56 | None 57 | } else { 58 | Some( 59 | unsafe { CStr::from_ptr(descriptor.feature) } 60 | .to_str() 61 | .expect("Register feature is not valid UTF-8") 62 | .to_string(), 63 | ) 64 | }; 65 | 66 | Self { 67 | handle: descriptor.handle as usize, 68 | name, 69 | feature, 70 | marker: PhantomData, 71 | } 72 | } 73 | } 74 | 75 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 76 | impl<'a> Debug for RegisterDescriptor<'a> { 77 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 78 | f.debug_struct("RegisterDescriptor") 79 | .field("name", &self.name) 80 | .field("feature", &self.feature) 81 | .finish() 82 | } 83 | } 84 | 85 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 86 | impl<'a> RegisterDescriptor<'a> { 87 | /// Read a register value 88 | /// 89 | /// This must only be called in a callback which has been registered with 90 | /// `CallbackFlags::QEMU_PLUGIN_CB_R_REGS` or 91 | /// `CallbackFlags::QEMU_PLUGIN_CB_RW_REGS`, otherwise it will fail. 92 | pub fn read(&self) -> Result> { 93 | use crate::g_byte_array_free; 94 | 95 | let byte_array = unsafe { 96 | use crate::g_byte_array_new; 97 | g_byte_array_new() 98 | }; 99 | 100 | let result = unsafe { 101 | qemu_plugin_read_register(self.handle as *mut qemu_plugin_register, byte_array) 102 | }; 103 | 104 | if result == -1 { 105 | return Err(Error::RegisterReadError { 106 | name: self.name.clone(), 107 | }); 108 | } 109 | 110 | let mut data = Vec::new(); 111 | data.extend_from_slice(unsafe { 112 | std::slice::from_raw_parts((*byte_array).data, (*byte_array).len as usize) 113 | }); 114 | 115 | assert_eq!( 116 | unsafe { g_byte_array_free(byte_array, true) }, 117 | std::ptr::null_mut(), 118 | "g_byte_array_free must return NULL" 119 | ); 120 | 121 | Ok(data) 122 | } 123 | 124 | #[cfg(not(any( 125 | feature = "plugin-api-v0", 126 | feature = "plugin-api-v1", 127 | feature = "plugin-api-v2", 128 | feature = "plugin-api-v3", 129 | feature = "plugin-api-v4" 130 | )))] 131 | /// Read a register value 132 | /// 133 | /// This must only be called in a callback which has been registered with 134 | /// `CallbackFlags::QEMU_PLUGIN_CB_RW_REGS`, otherwise it will fail. 135 | pub fn write(&self, data: &mut [u8]) -> Result<()> { 136 | let mut buf = GByteArray { 137 | data: data.as_mut_ptr(), 138 | len: data.len() as u32, 139 | }; 140 | if unsafe { qemu_plugin_write_register(self.handle as *mut _, &mut buf as *mut GByteArray) } 141 | == 0 142 | { 143 | Err(Error::RegisterWriteError { 144 | name: self.name.clone(), 145 | }) 146 | } else { 147 | Ok(()) 148 | } 149 | } 150 | 151 | #[cfg(feature = "num-traits")] 152 | /// Read a register value into a numeric type in big-endian byte order 153 | /// 154 | /// This must only be called in a callback which has been registered with 155 | /// `CallbackFlags::QEMU_PLUGIN_CB_R_REGS` or 156 | /// `CallbackFlags::QEMU_PLUGIN_CB_RW_REGS`. 157 | pub fn read_be(&self) -> Result 158 | where 159 | T: PrimInt + FromBytes + Sized, 160 | T: FromBytes()]>, 161 | { 162 | let data = self.read()?; 163 | let mut bytes = [0; std::mem::size_of::()]; 164 | bytes.copy_from_slice(&data); 165 | Ok(T::from_be_bytes(&bytes)) 166 | } 167 | 168 | #[cfg(feature = "num-traits")] 169 | /// Read a register value into a numeric type in little-endian byte order 170 | /// 171 | /// This must only be called in a callback which has been registered with 172 | /// `CallbackFlags::QEMU_PLUGIN_CB_R_REGS` or 173 | /// `CallbackFlags::QEMU_PLUGIN_CB_RW_REGS`. 174 | pub fn read_le(&self) -> Result 175 | where 176 | T: PrimInt + FromBytes + Sized, 177 | T: FromBytes()]>, 178 | { 179 | let data = self.read()?; 180 | let mut bytes = [0; std::mem::size_of::()]; 181 | bytes.copy_from_slice(&data); 182 | Ok(T::from_le_bytes(&bytes)) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /qemu-plugin/src/translation_block/mod.rs: -------------------------------------------------------------------------------- 1 | //! Translation Block-related functionality for QEMU plugins 2 | 3 | use crate::{ 4 | CallbackFlags, Error, Instruction, Result, VCPUIndex, 5 | handle_qemu_plugin_register_vcpu_tb_exec_cb, sys::qemu_plugin_tb, 6 | }; 7 | #[cfg(not(any( 8 | feature = "plugin-api-v0", 9 | feature = "plugin-api-v1", 10 | feature = "plugin-api-v2" 11 | )))] 12 | use crate::{PluginCondition, PluginU64}; 13 | use std::{ffi::c_void, marker::PhantomData}; 14 | 15 | #[derive(Debug, Clone)] 16 | /// Wrapper structure for a `qemu_plugin_tb *` 17 | /// 18 | /// # Safety 19 | /// 20 | /// This structure is safe to use as long as the pointer is valid. The pointer is 21 | /// always opaque, and therefore may not be dereferenced. 22 | /// 23 | /// # Example 24 | /// 25 | /// ``` 26 | /// struct MyPlugin; 27 | /// 28 | /// impl qemu_plugin::plugin::Register for MyPlugin {} 29 | /// 30 | /// impl qemu_plugin::plugin::HasCallbacks for MyPlugin { 31 | /// fn on_translation_block_translate( 32 | /// &mut self, 33 | /// id: qemu_plugin::PluginId, 34 | /// tb: qemu_plugin::TranslationBlock, 35 | /// ) -> Result<()> { 36 | /// for insn in tb.instructions() { 37 | /// println!("{:08x}: {}", insn.vaddr(), insn.disas()?); 38 | /// } 39 | /// Ok(()) 40 | /// } 41 | /// } 42 | /// ``` 43 | pub struct TranslationBlock<'a> { 44 | pub(crate) translation_block: usize, 45 | marker: PhantomData<&'a ()>, 46 | } 47 | 48 | impl<'a> From<*mut qemu_plugin_tb> for TranslationBlock<'a> { 49 | fn from(tb: *mut qemu_plugin_tb) -> Self { 50 | Self { 51 | translation_block: tb as usize, 52 | marker: PhantomData, 53 | } 54 | } 55 | } 56 | 57 | impl<'a> TranslationBlock<'a> { 58 | /// Returns the number of instructions in the translation block 59 | pub fn size(&self) -> usize { 60 | unsafe { crate::sys::qemu_plugin_tb_n_insns(self.translation_block as *mut qemu_plugin_tb) } 61 | } 62 | 63 | /// Returns the virtual address for the start of a translation block 64 | pub fn vaddr(&self) -> u64 { 65 | unsafe { crate::sys::qemu_plugin_tb_vaddr(self.translation_block as *mut qemu_plugin_tb) } 66 | } 67 | 68 | /// Returns the instruction in the translation block at `index`. If the index is out of bounds, 69 | /// an error is returned. 70 | /// 71 | /// # Arguments 72 | /// 73 | /// - `index`: The index of the instruction to return 74 | pub fn instruction(&'a self, index: usize) -> Result> { 75 | let size = self.size(); 76 | 77 | if index >= size { 78 | Err(Error::InvalidInstructionIndex { index, size }) 79 | } else { 80 | Ok(Instruction::new(self, unsafe { 81 | crate::sys::qemu_plugin_tb_get_insn( 82 | self.translation_block as *mut qemu_plugin_tb, 83 | index, 84 | ) 85 | })) 86 | } 87 | } 88 | 89 | /// Returns an iterator over the instructions in the translation block 90 | pub fn instructions(&'a self) -> TranslationBlockIterator<'a> { 91 | TranslationBlockIterator { tb: self, index: 0 } 92 | } 93 | 94 | /// Register a callback to be run on execution of this translation block 95 | /// 96 | /// # Arguments 97 | /// 98 | /// - `cb`: The callback to be run 99 | pub fn register_execute_callback(&self, cb: F) 100 | where 101 | F: FnMut(VCPUIndex) + Send + Sync + 'static, 102 | { 103 | self.register_execute_callback_flags(cb, CallbackFlags::QEMU_PLUGIN_CB_NO_REGS); 104 | } 105 | 106 | /// Register a callback to be run on execution of this translation block 107 | /// 108 | /// # Arguments 109 | /// 110 | /// - `cb`: The callback to be run 111 | pub fn register_execute_callback_flags(&self, cb: F, flags: CallbackFlags) 112 | where 113 | F: FnMut(VCPUIndex) + Send + Sync + 'static, 114 | { 115 | let callback = Box::new(cb); 116 | let callback_box = Box::new(callback); 117 | let userdata = Box::into_raw(callback_box) as *mut c_void; 118 | 119 | unsafe { 120 | crate::sys::qemu_plugin_register_vcpu_tb_exec_cb( 121 | self.translation_block as *mut qemu_plugin_tb, 122 | Some(handle_qemu_plugin_register_vcpu_tb_exec_cb::), 123 | flags, 124 | userdata, 125 | ) 126 | }; 127 | } 128 | 129 | #[cfg(not(any( 130 | feature = "plugin-api-v0", 131 | feature = "plugin-api-v1", 132 | feature = "plugin-api-v2" 133 | )))] 134 | /// Register a callback to be conditionally run on execution of this translation 135 | /// block 136 | /// 137 | /// # Arguments 138 | /// 139 | /// - `cb`: The callback to be run 140 | /// - `cond`: The condition for the callback to be run 141 | /// - `entry` The entry to increment the scoreboard for 142 | /// - `immediate`: The immediate value to use for the callback 143 | pub fn register_conditional_execute_callback( 144 | &self, 145 | cb: F, 146 | cond: PluginCondition, 147 | entry: PluginU64, 148 | immediate: u64, 149 | ) where 150 | F: FnMut(VCPUIndex) + Send + Sync + 'static, 151 | { 152 | self.register_conditional_execute_callback_flags( 153 | cb, 154 | CallbackFlags::QEMU_PLUGIN_CB_NO_REGS, 155 | cond, 156 | entry, 157 | immediate, 158 | ) 159 | } 160 | 161 | #[cfg(not(any( 162 | feature = "plugin-api-v0", 163 | feature = "plugin-api-v1", 164 | feature = "plugin-api-v2" 165 | )))] 166 | /// Register a callback to be conditionally run on execution of this translation 167 | /// block 168 | /// 169 | /// # Arguments 170 | /// 171 | /// - `cb`: The callback to be run 172 | /// - `flags`: The flags for the callback 173 | /// - `cond`: The condition for the callback to be run 174 | /// - `entry`: The entry to increment the scoreboard for 175 | /// - `immediate`: The immediate value to use for the callback 176 | pub fn register_conditional_execute_callback_flags( 177 | &self, 178 | cb: F, 179 | flags: CallbackFlags, 180 | cond: PluginCondition, 181 | entry: PluginU64, 182 | immediate: u64, 183 | ) where 184 | F: FnMut(VCPUIndex) + Send + Sync + 'static, 185 | { 186 | let callback = Box::new(cb); 187 | let callback_box = Box::new(callback); 188 | let userdata = Box::into_raw(callback_box) as *mut c_void; 189 | 190 | unsafe { 191 | crate::sys::qemu_plugin_register_vcpu_tb_exec_cond_cb( 192 | self.translation_block as *mut qemu_plugin_tb, 193 | Some(handle_qemu_plugin_register_vcpu_tb_exec_cb::), 194 | flags, 195 | cond, 196 | entry, 197 | immediate, 198 | userdata, 199 | ) 200 | }; 201 | } 202 | } 203 | 204 | /// An iterator over the instructions of a translation block 205 | pub struct TranslationBlockIterator<'a> { 206 | tb: &'a TranslationBlock<'a>, 207 | index: usize, 208 | } 209 | 210 | impl<'a> Iterator for TranslationBlockIterator<'a> { 211 | type Item = Instruction<'a>; 212 | 213 | fn next(&mut self) -> Option { 214 | let size = self.tb.size(); 215 | 216 | if self.index >= size { 217 | None 218 | } else { 219 | let insn = self.tb.instruction(self.index).ok(); 220 | self.index += 1; 221 | insn 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /qemu-plugin/src/install/mod.rs: -------------------------------------------------------------------------------- 1 | //! Installation for the QEMU plugin 2 | 3 | use crate::qemu_plugin_bool_parse; 4 | use qemu_plugin_sys::{ 5 | QEMU_PLUGIN_VERSION, qemu_info_t, qemu_info_t__bindgen_ty_1, 6 | qemu_info_t__bindgen_ty_2__bindgen_ty_1, qemu_plugin_id_t, 7 | }; 8 | use std::{ 9 | collections::HashMap, 10 | ffi::{CStr, c_char, c_int}, 11 | }; 12 | 13 | use crate::{error::Error, plugin::PLUGIN}; 14 | 15 | #[allow(non_upper_case_globals)] 16 | #[unsafe(no_mangle)] 17 | /// The version of the plugin API that this plugin is compatible with 18 | pub static qemu_plugin_version: c_int = QEMU_PLUGIN_VERSION as c_int; 19 | 20 | /// Code returned from `qemu_plugin_install` to indicate successful installation 21 | pub const PLUGIN_INSTALL_SUCCESS: c_int = 0; 22 | 23 | #[derive(Debug, Clone)] 24 | /// A value passed to a QEMU plugin via the command line, either as a boolean, 25 | /// integer, or string. Booleans are parsed using the `qemu_plugin_bool_parse` 26 | /// function, integers are parsed from strings, and strings are taken as-is. 27 | pub enum Value { 28 | /// A boolean argument to a QEMU plugin, for example `val=true` or `val=on` 29 | /// see https://www.qemu.org/docs/master/devel/tcg-plugins.html#c.qemu_plugin_bool_parse 30 | Bool(bool), 31 | /// An integer argument to a QEMU plugin, for example `val=1` 32 | Integer(i64), 33 | /// A string argument to a QEMU plugin, for example `val=foo` 34 | String(String), 35 | } 36 | 37 | impl Value { 38 | fn new(key: &str, value: &str) -> Result { 39 | if let Ok(maybe_bool) = qemu_plugin_bool_parse(key, value) { 40 | Ok(Self::Bool(maybe_bool)) 41 | } else if let Ok(int) = value.parse::() { 42 | Ok(Self::Integer(int)) 43 | } else { 44 | Ok(Self::String(value.to_string())) 45 | } 46 | } 47 | } 48 | 49 | #[derive(Debug, Clone)] 50 | /// Arguments to QEMU as passed to `qemu_plugin_install`. `qemu_plugin_install` 51 | /// takes a comma-separated list of key=value pairs, such as 52 | /// `val1=foo,val2=bar`. 53 | pub struct Args { 54 | /// Arguments to the QEMU plugin as passed in by QEMU. Each entry is a 55 | /// key=value pair where the key is the name of the argument and the value 56 | /// is the value of the argument. 57 | pub raw: Vec, 58 | /// Arguments to the QEMU plugin, parsed into valid argument types and value 59 | /// types. Each key is the name of the argument and the value is a `Value` 60 | /// enum which can be a boolean, integer, or string. 61 | pub parsed: HashMap, 62 | } 63 | 64 | impl Args { 65 | /// Create a new QEMU `Args` container from the raw arguments passed to the plugin on the 66 | /// command line 67 | fn new(argc: c_int, value: *const *const c_char) -> Result { 68 | Ok(Self { 69 | raw: (0..argc) 70 | .map(|i| unsafe { CStr::from_ptr(*value.offset(i as isize)) }) 71 | .map(|cstr| cstr.to_string_lossy().into_owned()) 72 | .collect::>(), 73 | parsed: (0..argc) 74 | .map(|i| unsafe { CStr::from_ptr(*value.offset(i as isize)) }) 75 | .map(|cstr| cstr.to_string_lossy().into_owned()) 76 | .map(|argument| { 77 | let mut split = argument.splitn(2, '='); 78 | let Some(key) = split.next() else { 79 | return Err(Error::MissingArgKey { argument }); 80 | }; 81 | let Some(value) = split.next() else { 82 | return Err(Error::MissingArgValue { argument }); 83 | }; 84 | Ok((key.to_string(), Value::new(key, value)?)) 85 | }) 86 | .collect::, Error>>()? 87 | .into_iter() 88 | .collect::>(), 89 | }) 90 | } 91 | } 92 | 93 | #[derive(Debug, Clone)] 94 | /// The version specification of the QEMU plugin API 95 | pub struct Version { 96 | /// Current plugin API version 97 | pub current: i64, 98 | /// Minimum plugin API version 99 | pub mininum: i64, 100 | } 101 | 102 | impl From<&qemu_info_t__bindgen_ty_1> for Version { 103 | fn from(value: &qemu_info_t__bindgen_ty_1) -> Self { 104 | Self { 105 | current: value.cur as i64, 106 | mininum: value.min as i64, 107 | } 108 | } 109 | } 110 | 111 | #[derive(Debug, Clone)] 112 | /// Information about the virtualized system, present if the emulator is running 113 | /// in full system emulation mode 114 | pub struct System { 115 | /// The maximum number of virtual CPUs supported by the system 116 | pub max_vcpus: i64, 117 | /// The number of virtual CPUs currently configured 118 | pub smp_vcpus: i64, 119 | } 120 | 121 | impl From<&qemu_info_t__bindgen_ty_2__bindgen_ty_1> for System { 122 | fn from(value: &qemu_info_t__bindgen_ty_2__bindgen_ty_1) -> Self { 123 | Self { 124 | max_vcpus: value.max_vcpus as i64, 125 | smp_vcpus: value.smp_vcpus as i64, 126 | } 127 | } 128 | } 129 | 130 | #[derive(Debug, Clone)] 131 | /// Information about the simulation, including the target name, version, and virtual 132 | /// system information 133 | pub struct Info { 134 | /// The target name of the simulation (e.g. `x86_64-softmmu`) 135 | pub target_name: String, 136 | /// The minimum and current plugin API version 137 | pub version: Version, 138 | /// Information about the system, if the emulator is running in full system 139 | /// emulation mode. If `None`, the emulator is running in user mode 140 | pub system: Option, 141 | } 142 | 143 | impl Info { 144 | /// # Safety 145 | /// 146 | /// This method should only called by QEMU inside the `qemu_plugin_install` function 147 | /// when the plugin is loaded. The `value` pointer is a valid pointer to a 148 | /// `qemu_info_t` struct which is live for the duration of the `qemu_plugin_install` 149 | /// function. 150 | unsafe fn try_from(value: *const qemu_info_t) -> Result { 151 | let target_name = unsafe { CStr::from_ptr((*value).target_name) } 152 | .to_str() 153 | .map_err(Error::from)? 154 | .to_string(); 155 | let version = Version::from(unsafe { &(*value).version }); 156 | let system_emulation = unsafe { (*value).system_emulation }; 157 | let system = if system_emulation { 158 | // NOTE: This is safe because `system_emulation` is true, which means the 159 | // `system` field is valid 160 | Some(System::from(unsafe { &(*value).__bindgen_anon_1.system })) 161 | } else { 162 | None 163 | }; 164 | 165 | Ok(Self { 166 | target_name, 167 | version, 168 | system, 169 | }) 170 | } 171 | } 172 | 173 | #[unsafe(no_mangle)] 174 | /// Called by QEMU when the plugin is loaded 175 | /// 176 | /// # Safety 177 | /// 178 | /// This function is called by QEMU when the plugin is loaded, and should not be called 179 | /// by dependent code. The `info` pointer is valid for the duration of the function 180 | /// call, and must not be accessed after the function returns. `argv` remains valid for 181 | /// the duration of the plugin's lifetime. 182 | pub unsafe extern "C" fn qemu_plugin_install( 183 | id: qemu_plugin_id_t, 184 | info: *const qemu_info_t, 185 | argc: c_int, 186 | argv: *const *const c_char, 187 | ) -> c_int { 188 | let args = Args::new(argc, argv).expect("Failed to parse arguments"); 189 | let info = unsafe { Info::try_from(info) }.expect("Failed to convert qemu_info_t"); 190 | 191 | let Some(plugin) = PLUGIN.get() else { 192 | panic!("Plugin not set"); 193 | }; 194 | 195 | let Ok(mut plugin) = plugin.lock() else { 196 | panic!("Failed to lock plugin"); 197 | }; 198 | 199 | plugin 200 | .register_default(id, &args, &info) 201 | .expect("Failed to register plugin"); 202 | 203 | PLUGIN_INSTALL_SUCCESS 204 | } 205 | -------------------------------------------------------------------------------- /qemu-plugin-sys/generate-bindings.rs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S cargo +nightly-gnu -Z script 2 | --- 3 | [package] 4 | edition = "2024" 5 | [dependencies] 6 | anyhow = "*" 7 | bindgen = "*" 8 | cargo_metadata = "*" 9 | reqwest = { version = "*", features = ["blocking"] } 10 | syn = "*" 11 | zip = "*" 12 | [lints.rust] 13 | non_snake_case = "allow" 14 | --- 15 | 16 | use anyhow::{anyhow, Result}; 17 | use bindgen::{ 18 | builder, AliasVariation, EnumVariation, FieldVisibilityKind, MacroTypeVariation, 19 | NonCopyUnionStyle, 20 | }; 21 | use cargo_metadata::MetadataCommand; 22 | use reqwest::blocking::get; 23 | use std::{ 24 | fs::{create_dir_all, read_to_string, write, File, OpenOptions}, 25 | io::copy, 26 | path::{Path, PathBuf}, 27 | }; 28 | use syn::{parse_str, File as RustFile, ForeignItem, ForeignItemFn, Item, ItemForeignMod}; 29 | use zip::ZipArchive; 30 | 31 | const QEMU_GITHUB_URL_BASE: &str = "https://github.com/qemu/qemu/"; 32 | 33 | const QEMU_VERSIONS: &[&str] = &[ 34 | // Plugin V0 is from 4.2.0 35 | "b0ca999a43a22b38158a222233d3f5881648bb4f", 36 | // Plugin V1 is up until 8.2.4 37 | "1332b8dd434674480f0feb2cdf3bbaebb85b4240", 38 | // Plugin V2 is from 9.0.0 39 | "c25df57ae8f9fe1c72eee2dab37d76d904ac382e", 40 | // Plugin V3 is from 9.1.0 41 | "7de77d37880d7267a491cb32a1b2232017d1e545", 42 | // Plugin V4 is from 9.2.0 43 | "595cd9ce2ec9330882c991a647d5bc2a5640f380", 44 | // Plugin V5 is from 10.1.0 45 | "f8b2f64e2336a28bf0d50b6ef8a7d8c013e9bcf3", 46 | ]; 47 | 48 | fn qemu_git_url(hash: &str) -> String { 49 | format!("{}/archive/{}.zip", QEMU_GITHUB_URL_BASE, hash) 50 | } 51 | 52 | /// Download a URL to a destination, using a blocking request 53 | fn download(url: &str, destination: &Path) -> Result<()> { 54 | let mut response = get(url)?; 55 | let mut file = OpenOptions::new() 56 | .write(true) 57 | .create(true) 58 | .truncate(true) 59 | .open(destination)?; 60 | response.copy_to(&mut file)?; 61 | Ok(()) 62 | } 63 | 64 | /// Extract a zip file at a path to a destination (stripping the root) 65 | fn extract_zip(archive: &Path, destination: &Path) -> Result<()> { 66 | let archive = File::open(archive)?; 67 | let mut archive = ZipArchive::new(archive)?; 68 | 69 | for i in 0..archive.len() { 70 | let mut file = archive.by_index(i)?; 71 | let file_path = file.mangled_name(); 72 | 73 | let components: Vec<_> = file_path.components().collect(); 74 | 75 | if components.len() <= 1 { 76 | continue; 77 | } 78 | 79 | let new_path = components[1..].iter().collect::(); 80 | let out_path = destination.join(new_path); 81 | 82 | if file.is_dir() { 83 | create_dir_all(&out_path)?; 84 | } else { 85 | if let Some(parent) = out_path.parent() { 86 | create_dir_all(parent)?; 87 | } 88 | let mut out_file = File::create(&out_path)?; 89 | copy(&mut file, &mut out_file)?; 90 | } 91 | } 92 | Ok(()) 93 | } 94 | 95 | fn generate_bindings( 96 | qemu_plugin_header: &Path, 97 | bindings_path: &Path, 98 | def_path: &Path, 99 | ) -> Result<()> { 100 | let header_contents = read_to_string(qemu_plugin_header)?; 101 | let header_file_name = qemu_plugin_header 102 | .file_name() 103 | .ok_or_else(|| anyhow!("Failed to get file name"))? 104 | .to_str() 105 | .ok_or_else(|| anyhow!("Failed to convert file name to string"))?; 106 | let header_contents = header_contents.replace("#include ", ""); 107 | // Append `typedef GArray void;` and `typedef GByteArray void;` to the header. Otherwise, we 108 | // need to use pkg_config to find the glib-2.0 include paths and our bindings will be 109 | // massive. 110 | let header_contents = format!( 111 | "#include \n{}\n{}\n{}\n", 112 | "typedef struct GArray { char *data; unsigned int len; } GArray;", 113 | "typedef struct GByteArray { unsigned char *data; unsigned int len; } GByteArray;", 114 | header_contents, 115 | ); 116 | 117 | let rust_bindings = builder() 118 | .clang_arg("-fretain-comments-from-system-headers") 119 | .clang_arg("-fparse-all-comments") 120 | .clang_arg("-Wno-everything") 121 | .default_visibility(FieldVisibilityKind::Public) 122 | .default_alias_style(AliasVariation::TypeAlias) 123 | .default_enum_style(EnumVariation::Rust { 124 | non_exhaustive: false, 125 | }) 126 | .default_macro_constant_type(MacroTypeVariation::Unsigned) 127 | .default_non_copy_union_style(NonCopyUnionStyle::BindgenWrapper) 128 | .derive_default(true) 129 | .derive_hash(true) 130 | .derive_partialord(true) 131 | .derive_ord(true) 132 | .derive_eq(true) 133 | .derive_partialeq(true) 134 | .generate_comments(true) 135 | .layout_tests(false) 136 | .header_contents(header_file_name, &header_contents) 137 | // Blocklist because we will define these items 138 | .blocklist_function("qemu_plugin_install") 139 | .blocklist_item("qemu_plugin_version") 140 | // ALlowlist all other qemu_plugin.* items 141 | .allowlist_item("qemu_plugin.*") 142 | .allowlist_item("QEMU_PLUGIN.*") 143 | .allowlist_item("G.*") 144 | .allowlist_item("g_.*") 145 | .generate()?; 146 | 147 | rust_bindings.write_to_file(bindings_path)?; 148 | 149 | let parsed: RustFile = parse_str(&rust_bindings.to_string())?; 150 | 151 | let mut export_names: Vec = parsed 152 | .items 153 | .iter() 154 | .filter_map(|item| { 155 | if let Item::ForeignMod(ItemForeignMod { items, .. }) = item { 156 | Some(items) 157 | } else { 158 | None 159 | } 160 | }) 161 | .flat_map(|items| { 162 | items.iter().filter_map(|item| { 163 | if let ForeignItem::Fn(ForeignItemFn { sig, .. }) = item { 164 | Some(sig.ident.to_string()) 165 | } else { 166 | None 167 | } 168 | }) 169 | }) 170 | .collect(); 171 | 172 | export_names.sort(); 173 | 174 | // Write to file using a single buffer 175 | let mut output = String::from("EXPORTS\n"); 176 | output.extend(export_names.into_iter().map(|name| format!(" {}\n", name))); 177 | 178 | write(&def_path, output)?; 179 | 180 | Ok(()) 181 | } 182 | 183 | fn generate(tmp_dir: &Path, out_dir: &Path, version: usize) -> Result<()> { 184 | println!( 185 | "Generating bindings with tmp={:?} out={:?} version={}", 186 | tmp_dir, out_dir, version 187 | ); 188 | let src_archive = tmp_dir.join(format!("qemu-{}.zip", QEMU_VERSIONS[version])); 189 | let src_dir = tmp_dir.join(format!("qemu-{}", QEMU_VERSIONS[version])); 190 | 191 | if !src_archive.exists() { 192 | let qemu_url = qemu_git_url(QEMU_VERSIONS[version]); 193 | println!("Downloading {} to {:?}", qemu_url, src_archive); 194 | download(&qemu_url, &src_archive)?; 195 | } 196 | 197 | if !src_dir.exists() { 198 | println!("Extracting {:?} to {:?}", src_archive, src_dir); 199 | extract_zip(&src_archive, &src_dir)?; 200 | } 201 | 202 | generate_bindings( 203 | &src_dir.join("include").join("qemu").join("qemu-plugin.h"), 204 | &out_dir.join(&format!("bindings_v{}.rs", version)), 205 | &out_dir.join(&format!("qemu_plugin_api_v{}.def", version)), 206 | )?; 207 | 208 | Ok(()) 209 | } 210 | 211 | fn main() -> Result<()> { 212 | let metadata = MetadataCommand::new().no_deps().exec()?; 213 | 214 | let search_package = "qemu-plugin-sys".parse()?; 215 | let package = metadata 216 | .packages 217 | .iter() 218 | .find(|p| p.name == search_package) 219 | .ok_or_else(|| anyhow!("Failed to find package"))?; 220 | 221 | let out_dir = package 222 | .manifest_path 223 | .parent() 224 | .ok_or_else(|| anyhow!("Failed to get manifest path"))? 225 | .join("src") 226 | .into_std_path_buf(); 227 | 228 | let tmp_dir = metadata.target_directory.join("tmp").into_std_path_buf(); 229 | 230 | if !tmp_dir.exists() { 231 | create_dir_all(&tmp_dir)?; 232 | } 233 | 234 | generate(&tmp_dir, &out_dir, 0)?; 235 | generate(&tmp_dir, &out_dir, 1)?; 236 | generate(&tmp_dir, &out_dir, 2)?; 237 | generate(&tmp_dir, &out_dir, 3)?; 238 | generate(&tmp_dir, &out_dir, 4)?; 239 | generate(&tmp_dir, &out_dir, 5)?; 240 | 241 | Ok(()) 242 | } 243 | -------------------------------------------------------------------------------- /plugins/tracer-driver/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Error, Result, anyhow}; 2 | use clap::Parser; 3 | use rand::{Rng, distr::Alphanumeric, rng}; 4 | use serde_cbor::Deserializer; 5 | use serde_json::to_string; 6 | use std::process::{Command, Stdio}; 7 | use std::{ 8 | fs::OpenOptions, 9 | io::{BufRead, BufReader, Write, stdout}, 10 | os::unix::net::UnixListener, 11 | path::{Path, PathBuf}, 12 | }; 13 | use tokio::{ 14 | fs::{read, remove_file}, 15 | join, main, spawn, 16 | task::spawn_blocking, 17 | }; 18 | use tracer_events::Event; 19 | use tracing::{debug, level_filters::LevelFilter, subscriber::set_global_default}; 20 | use tracing_subscriber::fmt; 21 | 22 | fn tmp(prefix: &str, suffix: &str) -> PathBuf { 23 | PathBuf::from(format!( 24 | "{}{}{}", 25 | prefix, 26 | rng() 27 | .sample_iter(&Alphanumeric) 28 | .take(8) 29 | .map(char::from) 30 | .collect::(), 31 | suffix 32 | )) 33 | } 34 | 35 | #[cfg(any(feature = "plugin-api-v0", feature = "plugin-api-v1"))] 36 | #[derive(Parser, Debug, Clone)] 37 | /// Run QEMU with a plugin that logs events. To pass arguments to QEMU, use the QEMU environment 38 | /// variables. 39 | struct Args { 40 | #[clap(short = 'Q', long, default_value = "qemu-x86_64")] 41 | /// The alternative QEMU binary to use 42 | pub qemu_bin: PathBuf, 43 | #[clap(short = 'P', long)] 44 | /// The path to the plugin to use 45 | pub plugin_path: PathBuf, 46 | #[clap(short = 'i', long)] 47 | /// Whether instructions should be logged 48 | pub log_insns: bool, 49 | #[clap(short = 'm', long)] 50 | /// Whether memory accesses should be logged 51 | pub log_mem: bool, 52 | #[clap(short = 's', long)] 53 | /// Whether syscalls should be logged 54 | pub log_syscalls: bool, 55 | #[clap(short = 'a', long)] 56 | /// Whether all events should be logged 57 | pub log_all: bool, 58 | #[clap(short = 'I', long)] 59 | /// An input file to use as the program's stdin, otherwise the driver's stdin is used 60 | pub input_file: Option, 61 | #[clap(short = 'O', long)] 62 | /// An output file to write the trace to, otherwise stdout is used 63 | pub output_file: Option, 64 | #[clap(short = 'L', long, default_value_t = LevelFilter::INFO)] 65 | /// The log level (error, warn, info, debug, trace) 66 | pub log_level: LevelFilter, 67 | /// The program to run 68 | #[clap()] 69 | pub program: PathBuf, 70 | /// The arguments to the program 71 | #[clap(num_args = 1.., last = true)] 72 | pub args: Vec, 73 | } 74 | 75 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 76 | #[derive(Parser, Debug, Clone)] 77 | /// Run QEMU with a plugin that logs events. To pass arguments to QEMU, use the QEMU environment 78 | /// variables. 79 | struct Args { 80 | #[clap(short = 'Q', long, default_value = "qemu-x86_64")] 81 | /// The alternative QEMU binary to use 82 | pub qemu_bin: PathBuf, 83 | #[clap(short = 'P', long)] 84 | /// The path to the plugin to use 85 | pub plugin_path: PathBuf, 86 | #[clap(short = 'i', long)] 87 | /// Whether instructions should be logged 88 | pub log_insns: bool, 89 | #[clap(short = 'm', long)] 90 | /// Whether memory accesses should be logged 91 | pub log_mem: bool, 92 | #[clap(short = 's', long)] 93 | /// Whether syscalls should be logged 94 | pub log_syscalls: bool, 95 | #[clap(short = 'r', long)] 96 | /// Whether registers should be logged 97 | pub log_registers: bool, 98 | #[clap(short = 'a', long)] 99 | /// Whether all events should be logged 100 | pub log_all: bool, 101 | #[clap(short = 'I', long)] 102 | /// An input file to use as the program's stdin, otherwise the driver's stdin is used 103 | pub input_file: Option, 104 | #[clap(short = 'O', long)] 105 | /// An output file to write the trace to, otherwise stdout is used 106 | pub output_file: Option, 107 | #[clap(short = 'L', long, default_value_t = LevelFilter::INFO)] 108 | /// The log level (error, warn, info, debug, trace) 109 | pub log_level: LevelFilter, 110 | /// The program to run 111 | #[clap()] 112 | pub program: PathBuf, 113 | /// The arguments to the program 114 | #[clap(num_args = 1.., last = true)] 115 | pub args: Vec, 116 | } 117 | 118 | impl Args { 119 | fn to_plugin_args(&self) -> String { 120 | #[cfg(any(feature = "plugin-api-v0", feature = "plugin-api-v1"))] 121 | { 122 | format!( 123 | "log_insns={},log_mem={},log_syscalls={}", 124 | self.log_insns | self.log_all, 125 | self.log_mem | self.log_all, 126 | self.log_syscalls | self.log_all, 127 | ) 128 | } 129 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 130 | { 131 | format!( 132 | "log_insns={},log_mem={},log_syscalls={},log_registers={}", 133 | self.log_insns | self.log_all, 134 | self.log_mem | self.log_all, 135 | self.log_syscalls | self.log_all, 136 | self.log_registers | self.log_all, 137 | ) 138 | } 139 | } 140 | 141 | fn to_qemu_args(&self, socket_path: &Path, plugin_path: &Path) -> Result> { 142 | let mut qemu_args = vec![ 143 | "-plugin".to_string(), 144 | format!( 145 | "{},{},socket_path={}", 146 | plugin_path.display(), 147 | self.to_plugin_args(), 148 | socket_path.display() 149 | ), 150 | "--".to_string(), 151 | self.program 152 | .to_str() 153 | .ok_or_else(|| anyhow!("Failed to convert program path to string"))? 154 | .to_string(), 155 | ]; 156 | 157 | qemu_args.extend(self.args.clone()); 158 | 159 | Ok(qemu_args) 160 | } 161 | } 162 | 163 | async fn run

(qemu: P, input: Option>, args: Vec) -> Result<()> 164 | where 165 | P: AsRef, 166 | { 167 | let mut exe = Command::new(qemu.as_ref()) 168 | .args(args) 169 | .stdin(if input.is_some() { 170 | Stdio::piped() 171 | } else { 172 | Stdio::inherit() 173 | }) 174 | .stdout(Stdio::piped()) 175 | .stderr(Stdio::piped()) 176 | .spawn()?; 177 | 178 | debug!("Started QEMU with PID {}", exe.id()); 179 | 180 | if let Some(input) = input { 181 | let mut stdin = exe.stdin.take().ok_or_else(|| anyhow!("No stdin"))?; 182 | spawn_blocking(move || { 183 | debug!("Writing input to QEMU stdin"); 184 | stdin.write_all(&input) 185 | }); 186 | } 187 | 188 | let stdout = exe.stdout.take().ok_or_else(|| anyhow!("No stdout"))?; 189 | 190 | let out_reader = spawn_blocking(move || { 191 | debug!("Reading output from QEMU stdout"); 192 | 193 | let mut line = String::new(); 194 | let mut out_reader = BufReader::new(stdout); 195 | 196 | loop { 197 | line.clear(); 198 | 199 | if let 0 = out_reader.read_line(&mut line)? { 200 | break; 201 | } 202 | 203 | let line = line.trim(); 204 | 205 | if !line.is_empty() { 206 | println!("{line}"); 207 | } 208 | } 209 | 210 | Ok::<(), Error>(()) 211 | }); 212 | 213 | let stderr = exe.stderr.take().ok_or_else(|| anyhow!("No stderr"))?; 214 | 215 | let err_reader = spawn_blocking(move || { 216 | debug!("Reading output from QEMU stderr"); 217 | 218 | let mut line = String::new(); 219 | let mut err_reader = BufReader::new(stderr); 220 | 221 | loop { 222 | line.clear(); 223 | 224 | if let 0 = err_reader.read_line(&mut line)? { 225 | break; 226 | } 227 | 228 | let line = line.trim(); 229 | 230 | if !line.is_empty() { 231 | eprintln!("{line}"); 232 | } 233 | } 234 | 235 | Ok::<(), Error>(()) 236 | }); 237 | 238 | let waiter = spawn_blocking(move || { 239 | debug!("Waiting for QEMU to exit"); 240 | exe.wait() 241 | }); 242 | 243 | debug!("Waiting for tasks to complete"); 244 | 245 | let (out_res, err_res, waiter_res) = join!(out_reader, err_reader, waiter); 246 | 247 | debug!("All tasks completed"); 248 | 249 | out_res??; 250 | err_res??; 251 | waiter_res??; 252 | 253 | Ok(()) 254 | } 255 | 256 | fn listen

(listen_sock: UnixListener, outfile: Option

) -> Result<()> 257 | where 258 | P: AsRef, 259 | { 260 | let mut outfile_stream = if let Some(outfile) = outfile.as_ref() { 261 | Box::new(OpenOptions::new().create(true).append(true).open(outfile)?) as Box 262 | } else { 263 | Box::new(stdout()) as Box 264 | }; 265 | 266 | let (mut stream, _) = listen_sock.accept()?; 267 | let it = Deserializer::from_reader(&mut stream).into_iter::(); 268 | 269 | for event in it { 270 | outfile_stream.write_all(to_string(&event?)?.as_bytes())?; 271 | outfile_stream.write_all(b"\n")?; 272 | } 273 | 274 | Ok(()) 275 | } 276 | 277 | #[main] 278 | async fn main() -> Result<()> { 279 | let args = Args::parse(); 280 | 281 | let subscriber = fmt() 282 | .with_writer(std::io::stderr) 283 | .with_max_level(args.log_level) 284 | .finish(); 285 | 286 | set_global_default(subscriber)?; 287 | 288 | debug!("{args:?}"); 289 | 290 | let socket_path = tmp("/tmp/qemu-", ".sock"); 291 | 292 | let input = if let Some(input_file) = args.input_file.as_ref() { 293 | let Ok(input_file) = input_file.canonicalize() else { 294 | return Err(anyhow!("Failed to canonicalize input file")); 295 | }; 296 | 297 | Some(read(input_file).await?) 298 | } else { 299 | None 300 | }; 301 | 302 | debug!("Binding to socket {}", socket_path.display()); 303 | 304 | let listen_sock = UnixListener::bind(&socket_path)?; 305 | 306 | let qemu_args = args.to_qemu_args(&socket_path, &args.plugin_path)?; 307 | 308 | let socket_task = spawn_blocking(move || { 309 | debug!("Listening for events on socket"); 310 | listen(listen_sock, args.output_file.as_ref()) 311 | }); 312 | 313 | let qemu_task = spawn(async move { 314 | debug!("Running QEMU with args: {:?}", qemu_args); 315 | run(&args.qemu_bin, input, qemu_args).await 316 | }); 317 | 318 | debug!("Waiting for QEMU and socket tasks to complete"); 319 | 320 | let (qemu_res, socket_res) = join!(socket_task, qemu_task); 321 | 322 | remove_file(&socket_path).await?; 323 | 324 | qemu_res??; 325 | socket_res??; 326 | 327 | Ok(()) 328 | } 329 | -------------------------------------------------------------------------------- /qemu-plugin/src/error/mod.rs: -------------------------------------------------------------------------------- 1 | //! Errors that can occur in the qemu-plugin crate 2 | 3 | #[derive(thiserror::Error, Debug)] 4 | /// An error from the qemu-plugin crate 5 | pub enum Error { 6 | #[error("Missing key for argument {argument}")] 7 | /// Error when an argument is missing a key 8 | MissingArgKey { 9 | /// The argument string a key is missing for 10 | argument: String, 11 | }, 12 | #[error("Missing value for argument {argument}")] 13 | /// Error when an argument is missing a value 14 | MissingArgValue { 15 | /// The argument string a value is missing for 16 | argument: String, 17 | }, 18 | #[error("Invalid boolean value {name} ({val})")] 19 | /// Error when a boolean argument is invalid 20 | InvalidBool { 21 | /// The name of the key-value argument pair which does not correctly parse as boolean 22 | name: String, 23 | /// The value of the key-value argument pair which does not correctly parse as boolean 24 | val: String, 25 | }, 26 | #[error( 27 | "Setting the QEMU plugin uninstall callback was attempted concurrently and this attempt failed." 28 | )] 29 | /// Error when the QEMU plugin uninstall callback is set concurrently 30 | ConcurrentPluginUninstallCallbackSet, 31 | #[error( 32 | "Setting the QEMU plugin reset callback was attempted concurrently and this attempt failed." 33 | )] 34 | /// Error when the QEMU plugin reset callback is set concurrently 35 | ConcurrentPluginResetCallbackSet, 36 | #[error("Invalid state for plugin reset callback")] 37 | /// Error when the plugin reset callback is in an invalid state 38 | PluginResetCallbackState, 39 | #[error("Invalid instruction index {index} for translation block of size {size}")] 40 | /// Error when an instruction index is invalid 41 | InvalidInstructionIndex { 42 | /// The index into the translation block that is invalid 43 | index: usize, 44 | /// The size of the translation block 45 | size: usize, 46 | }, 47 | #[error("No disassembly string available for instruction")] 48 | /// Error when no disassembly string is available for an instruction (i.e. NULL string 49 | NoDisassemblyString, 50 | #[error("Invalid size {size} for read of register {name}")] 51 | /// Error when the size of a register read is invalid 52 | InvalidRegisterReadSize { 53 | /// The register name 54 | name: String, 55 | /// The size of the attempted read 56 | size: usize, 57 | }, 58 | #[error("Error while reading register {name}")] 59 | /// Error when reading a register fails 60 | RegisterReadError { 61 | /// The register name 62 | name: String, 63 | }, 64 | #[error("Error while writing register {name}")] 65 | /// Error when writing a register fails 66 | RegisterWriteError { 67 | /// The register name 68 | name: String, 69 | }, 70 | #[cfg(not(any( 71 | feature = "plugin-api-v0", 72 | feature = "plugin-api-v1", 73 | feature = "plugin-api-v2", 74 | feature = "plugin-api-v3" 75 | )))] 76 | #[error("Error while reading {len} bytes from virtual address {addr:#x}")] 77 | /// Error when reading memory from a virtual address fails 78 | VaddrReadError { 79 | /// The address read from 80 | addr: u64, 81 | /// The number of bytes read 82 | len: u32, 83 | }, 84 | #[cfg(not(any( 85 | feature = "plugin-api-v0", 86 | feature = "plugin-api-v1", 87 | feature = "plugin-api-v2", 88 | feature = "plugin-api-v3", 89 | feature = "plugin-api-v4" 90 | )))] 91 | #[error("Error while writing {len} bytes to virtual address {addr:#x}")] 92 | /// Error when writing memory from a virtual address fails 93 | VaddrWriteError { 94 | /// The address written to 95 | addr: u64, 96 | /// The number of bytes written 97 | len: u32, 98 | }, 99 | #[cfg(not(any( 100 | feature = "plugin-api-v0", 101 | feature = "plugin-api-v1", 102 | feature = "plugin-api-v2", 103 | feature = "plugin-api-v3", 104 | feature = "plugin-api-v4" 105 | )))] 106 | #[error("Error while reading {len} bytes from hardware address {addr:#x}: {result}")] 107 | /// Error when reading memory from a hardware address fails 108 | HwaddrReadError { 109 | /// The address read from 110 | addr: u64, 111 | /// The number of bytes read 112 | len: u32, 113 | /// The operation result 114 | result: crate::HwaddrOperationResult, 115 | }, 116 | #[cfg(not(any( 117 | feature = "plugin-api-v0", 118 | feature = "plugin-api-v1", 119 | feature = "plugin-api-v2", 120 | feature = "plugin-api-v3", 121 | feature = "plugin-api-v4" 122 | )))] 123 | #[error("Error while writing {len} bytes to hardware address {addr:#x}: {result}")] 124 | /// Error when writing memory from a hardware address fails 125 | HwaddrWriteError { 126 | /// The address written to 127 | addr: u64, 128 | /// The number of bytes written 129 | len: u32, 130 | /// The operation result 131 | result: crate::HwaddrOperationResult, 132 | }, 133 | #[cfg(not(any( 134 | feature = "plugin-api-v0", 135 | feature = "plugin-api-v1", 136 | feature = "plugin-api-v2", 137 | feature = "plugin-api-v3", 138 | feature = "plugin-api-v4" 139 | )))] 140 | #[error("Error while translating virtual address {vaddr:#x} to hardware address")] 141 | /// Error when translating a virtual address to a hardware address fails 142 | VaddrTranslateError { 143 | /// The virtual address that failed to translate 144 | vaddr: u64, 145 | }, 146 | #[error("Error while setting global plugin instance")] 147 | /// Error when setting the global plugin instance fails 148 | PluginInstanceSetError, 149 | #[error(transparent)] 150 | /// A transparently wrapped `std::str::Utf8Error` 151 | StdUtf8Error(#[from] std::str::Utf8Error), 152 | #[error(transparent)] 153 | /// A transparently wrapped `core::convert::Infallible` 154 | Infallible(#[from] core::convert::Infallible), 155 | #[error(transparent)] 156 | /// A transparently wrapped `std::alloc::LayoutError` 157 | LayoutError(#[from] std::alloc::LayoutError), 158 | #[error(transparent)] 159 | /// A transparently wrapped `std::array::TryFromSliceError` 160 | TryFromSliceError(#[from] std::array::TryFromSliceError), 161 | #[error(transparent)] 162 | /// A transparently wrapped `std::cell::BorrowError` 163 | BorrowError(#[from] std::cell::BorrowError), 164 | #[error(transparent)] 165 | /// A transparently wrapped `std::cell::BorrowMutError` 166 | BorrowMutError(#[from] std::cell::BorrowMutError), 167 | #[error(transparent)] 168 | /// A transparently wrapped `std::char::CharTryFromError` 169 | CharTryFromError(#[from] std::char::CharTryFromError), 170 | #[error(transparent)] 171 | /// A transparently wrapped `std::char::DecodeUtf16Error` 172 | DecodeUtf16Error(#[from] std::char::DecodeUtf16Error), 173 | #[error(transparent)] 174 | /// A transparently wrapped `std::char::ParseCharError` 175 | ParseCharError(#[from] std::char::ParseCharError), 176 | #[error(transparent)] 177 | /// A transparently wrapped `std::char::TryFromCharError` 178 | TryFromCharError(#[from] std::char::TryFromCharError), 179 | #[error(transparent)] 180 | /// A transparently wrapped `std::collections::TryReserveError` 181 | TryReserveError(#[from] std::collections::TryReserveError), 182 | #[error(transparent)] 183 | /// A transparently wrapped `std::env::JoinPathsError` 184 | JoinPathsError(#[from] std::env::JoinPathsError), 185 | #[error(transparent)] 186 | /// A transparently wrapped `std::env::VarError` 187 | VarError(#[from] std::env::VarError), 188 | #[error(transparent)] 189 | /// A transparently wrapped `std::ffi::FromBytesUntilNulError` 190 | FromBytesUntilNulError(#[from] std::ffi::FromBytesUntilNulError), 191 | #[error(transparent)] 192 | /// A transparently wrapped `std::ffi::FromBytesWithNulError` 193 | FromBytesWithNulError(#[from] std::ffi::FromBytesWithNulError), 194 | #[error(transparent)] 195 | /// A transparently wrapped `std::ffi::FromVecWithNulError` 196 | FromVecWithNulError(#[from] std::ffi::FromVecWithNulError), 197 | #[error(transparent)] 198 | /// A transparently wrapped `std::ffi::IntoStringError` 199 | IntoStringError(#[from] std::ffi::IntoStringError), 200 | #[error(transparent)] 201 | /// A transparently wrapped `std::ffi::NulError` 202 | NulError(#[from] std::ffi::NulError), 203 | #[error(transparent)] 204 | /// A transparently wrapped `std::fmt::Error` 205 | FmtError(#[from] std::fmt::Error), 206 | #[error(transparent)] 207 | /// A transparently wrapped `std::fs::TryLockError` 208 | FsTryLockError(#[from] std::fs::TryLockError), 209 | #[error(transparent)] 210 | /// A transparently wrapped `std::io::Error` 211 | IoError(#[from] std::io::Error), 212 | #[error(transparent)] 213 | /// A transparently wrapped `std::net::AddrParseError` 214 | AddrParseError(#[from] std::net::AddrParseError), 215 | #[error(transparent)] 216 | /// A transparently wrapped `std::num::ParseFloatError` 217 | ParseFloatError(#[from] std::num::ParseFloatError), 218 | #[error(transparent)] 219 | /// A transparently wrapped `std::num::ParseIntError` 220 | ParseIntError(#[from] std::num::ParseIntError), 221 | #[error(transparent)] 222 | /// A transparently wrapped `std::num::TryFromIntError` 223 | TryFromIntError(#[from] std::num::TryFromIntError), 224 | #[error(transparent)] 225 | /// A transparently wrapped `std::path::StripPrefixError` 226 | StripPrefixError(#[from] std::path::StripPrefixError), 227 | #[error(transparent)] 228 | /// A transparently wrapped `std::str::ParseBoolError` 229 | ParseBoolError(#[from] std::str::ParseBoolError), 230 | #[error(transparent)] 231 | /// A transparently wrapped `std::string::FromUtf8Error` 232 | FromUtf8Error(#[from] std::string::FromUtf8Error), 233 | #[error(transparent)] 234 | /// A transparently wrapped `std::string::FromUtf16Error` 235 | FromUtf16Error(#[from] std::string::FromUtf16Error), 236 | #[error(transparent)] 237 | /// A transparently wrapped `std::sync::mpsc::RecvError` 238 | RecvError(#[from] std::sync::mpsc::RecvError), 239 | #[error(transparent)] 240 | /// A transparently wrapped `std::sync::mpsc::RecvTimeoutError` 241 | RecvTimeoutError(#[from] std::sync::mpsc::RecvTimeoutError), 242 | #[error(transparent)] 243 | /// A transparently wrapped `std::sync::mpsc::TryRecvError` 244 | TryRecvError(#[from] std::sync::mpsc::TryRecvError), 245 | #[error(transparent)] 246 | /// A transparently wrapped `std::thread::AccessError` 247 | AccessError(#[from] std::thread::AccessError), 248 | #[error(transparent)] 249 | /// A transparently wrapped `std::time::SystemTimeError` 250 | SystemTimeError(#[from] std::time::SystemTimeError), 251 | #[error(transparent)] 252 | /// A transparently wrapped `std::time::TryFromFloatSecsError` 253 | TryFromFloatSecsError(#[from] std::time::TryFromFloatSecsError), 254 | #[cfg(windows)] 255 | #[error(transparent)] 256 | /// A transparently wrapped `std::os::windows::io::InvalidHandleError` 257 | InvalidHandleError(#[from] std::os::windows::io::InvalidHandleError), 258 | #[cfg(windows)] 259 | #[error(transparent)] 260 | /// A transparently wrapped `std::os::windows::io::NullHandleError` 261 | NullHandleError(#[from] std::os::windows::io::NullHandleError), 262 | #[cfg(feature = "anyhow")] 263 | #[error(transparent)] 264 | /// A transparently wrapped `anyhow::Error` 265 | AnyhowError(#[from] anyhow::Error), 266 | #[error(transparent)] 267 | /// A transparently wrapped `Box` 268 | BoxedError(#[from] Box), 269 | } 270 | 271 | #[allow(dead_code)] 272 | /// Assert that Error is Send + Sync 273 | fn _assert_error_is_send_sync() { 274 | fn assert_send_sync() {} 275 | assert_send_sync::(); 276 | } 277 | 278 | /// Result type for the qemu-plugin crate 279 | pub type Result = std::result::Result; 280 | -------------------------------------------------------------------------------- /qemu-plugin/src/instruction/mod.rs: -------------------------------------------------------------------------------- 1 | //! Instruction-related functionality for QEMU plugins 2 | 3 | use crate::{ 4 | CallbackFlags, Error, MemRW, MemoryInfo, Result, TranslationBlock, VCPUIndex, g_free, 5 | handle_qemu_plugin_register_vcpu_insn_exec_cb, handle_qemu_plugin_register_vcpu_mem_cb, 6 | sys::qemu_plugin_insn, 7 | }; 8 | #[cfg(not(any( 9 | feature = "plugin-api-v0", 10 | feature = "plugin-api-v1", 11 | feature = "plugin-api-v2" 12 | )))] 13 | use crate::{PluginCondition, PluginU64}; 14 | use std::{ 15 | ffi::{CStr, c_void}, 16 | marker::PhantomData, 17 | }; 18 | 19 | #[derive(Debug, Clone)] 20 | /// Wrapper structure for a `qemu_plugin_insn *` 21 | /// 22 | /// # Safety 23 | /// 24 | /// This structure is safe to use as long as the pointer is valid. The pointer is 25 | /// always opaque, and therefore may not be dereferenced. 26 | /// 27 | /// # Example 28 | /// 29 | /// ``` 30 | /// struct MyPlugin; 31 | /// 32 | /// impl qemu_plugin::plugin::Register for MyPlugin {} 33 | /// 34 | /// impl qemu_plugin::plugin::HasCallbacks for MyPlugin { 35 | /// fn on_translation_block_translate( 36 | /// &mut self, 37 | /// id: qemu_plugin::PluginId, 38 | /// tb: qemu_plugin::TranslationBlock, 39 | /// ) -> Result<()> { 40 | /// for insn in tb.instructions() { 41 | /// let vaddr = insn.vaddr(); 42 | /// let disas = insn.disas()?; 43 | /// // Register a callback to be run on execution of this instruction 44 | /// insn.register_execute_callback(move |vcpu_index| { 45 | /// println!("{vcpu_index}@{vaddr:#x}: {disas}"); 46 | /// }); 47 | /// } 48 | /// Ok(()) 49 | /// } 50 | /// } 51 | /// ``` 52 | pub struct Instruction<'a> { 53 | #[allow(unused)] 54 | // NOTE: This field may be useful in the future 55 | translation_block: &'a TranslationBlock<'a>, 56 | pub(crate) instruction: usize, 57 | marker: PhantomData<&'a ()>, 58 | } 59 | 60 | impl<'a> Instruction<'a> { 61 | pub(crate) fn new( 62 | translation_block: &'a TranslationBlock<'a>, 63 | insn: *mut qemu_plugin_insn, 64 | ) -> Self { 65 | Self { 66 | translation_block, 67 | instruction: insn as usize, 68 | marker: PhantomData, 69 | } 70 | } 71 | } 72 | 73 | impl<'a> Instruction<'a> { 74 | #[cfg(any( 75 | feature = "plugin-api-v0", 76 | feature = "plugin-api-v1", 77 | feature = "plugin-api-v2" 78 | ))] 79 | /// Returns the data for this instruction. This method may only be called inside the 80 | /// callback in which the instruction is obtained, but the resulting data is owned. 81 | pub fn data(&self) -> Vec { 82 | let size = self.size(); 83 | let mut data = Vec::with_capacity(size); 84 | 85 | // NOTE: The name of this API doesn't change, but its parameters and return value *do* 86 | let insn_data = 87 | unsafe { crate::sys::qemu_plugin_insn_data(self.instruction as *mut qemu_plugin_insn) } 88 | as *mut u8; 89 | 90 | unsafe { 91 | data.set_len(size); 92 | std::ptr::copy_nonoverlapping(insn_data, data.as_mut_ptr(), size); 93 | } 94 | 95 | data 96 | } 97 | 98 | #[cfg(not(any( 99 | feature = "plugin-api-v0", 100 | feature = "plugin-api-v1", 101 | feature = "plugin-api-v2" 102 | )))] 103 | /// Reads the data for this instruction returning number of bytes read. This method may only be 104 | /// called inside the callback in which the instruction is obtained. 105 | pub fn read_data(&self, data: &mut [u8]) -> usize { 106 | // NOTE: The name of this API doesn't change, but its parameters and return value *do* 107 | unsafe { 108 | crate::sys::qemu_plugin_insn_data( 109 | self.instruction as *mut qemu_plugin_insn, 110 | data.as_mut_ptr() as *mut _, 111 | data.len(), 112 | ) 113 | } 114 | } 115 | 116 | #[cfg(not(any( 117 | feature = "plugin-api-v0", 118 | feature = "plugin-api-v1", 119 | feature = "plugin-api-v2" 120 | )))] 121 | /// Returns the data for this instruction. This method may only be called inside the 122 | /// callback in which the instruction is obtained, but the resulting data is owned. 123 | pub fn data(&self) -> Vec { 124 | let size = self.size(); 125 | let mut data = vec![0; size]; 126 | 127 | let size = self.read_data(&mut data); 128 | 129 | data.truncate(size); 130 | 131 | data 132 | } 133 | 134 | /// Returns the size of the data for this instruction 135 | pub fn size(&self) -> usize { 136 | unsafe { crate::sys::qemu_plugin_insn_size(self.instruction as *mut qemu_plugin_insn) } 137 | } 138 | 139 | /// Returns the virtual address of this instruction 140 | pub fn vaddr(&self) -> u64 { 141 | unsafe { crate::sys::qemu_plugin_insn_vaddr(self.instruction as *mut qemu_plugin_insn) } 142 | } 143 | 144 | /// Returns the hardware (physical) address of this instruction 145 | pub fn haddr(&self) -> u64 { 146 | (unsafe { crate::sys::qemu_plugin_insn_haddr(self.instruction as *mut qemu_plugin_insn) }) 147 | as usize as u64 148 | } 149 | 150 | /// Returns the textual disassembly of this instruction 151 | pub fn disas(&self) -> Result { 152 | let disas = unsafe { 153 | crate::sys::qemu_plugin_insn_disas(self.instruction as *mut qemu_plugin_insn) 154 | }; 155 | if disas.is_null() { 156 | Err(Error::NoDisassemblyString) 157 | } else { 158 | let disas_string = unsafe { CStr::from_ptr(disas) }.to_str()?.to_string(); 159 | 160 | // NOTE: The string is allocated, so we free it 161 | unsafe { g_free(disas as *mut _) }; 162 | 163 | Ok(disas_string) 164 | } 165 | } 166 | 167 | #[cfg(not(feature = "plugin-api-v0"))] 168 | /// Returns the symbol associated with this instruction, if one exists and the 169 | /// binary contains a symbol table 170 | pub fn symbol(&self) -> Result> { 171 | let symbol = unsafe { 172 | crate::sys::qemu_plugin_insn_symbol(self.instruction as *mut qemu_plugin_insn) 173 | }; 174 | if symbol.is_null() { 175 | Ok(None) 176 | } else { 177 | let symbol_string = unsafe { CStr::from_ptr(symbol) }.to_str()?.to_string(); 178 | // NOTE: The string is static, so we do not free it 179 | Ok(Some(symbol_string)) 180 | } 181 | } 182 | 183 | /// Register a callback to be run on execution of this instruction with no 184 | /// capability to inspect registers 185 | /// 186 | /// # Arguments 187 | /// 188 | /// - `cb`: The callback to be run 189 | pub fn register_execute_callback(&self, cb: F) 190 | where 191 | F: FnMut(VCPUIndex) + Send + Sync + 'static, 192 | { 193 | self.register_execute_callback_flags(cb, CallbackFlags::QEMU_PLUGIN_CB_NO_REGS) 194 | } 195 | 196 | /// Register a callback to be run on execution of this instruction with a choice of 197 | /// capability whether to inspect or modify registers or not 198 | /// 199 | /// # Arguments 200 | /// 201 | /// - `cb`: The callback to be run 202 | /// - `flags`: The flags for the callback specifying whether the callback needs 203 | /// permission to read or write registers 204 | pub fn register_execute_callback_flags(&self, cb: F, flags: CallbackFlags) 205 | where 206 | F: FnMut(VCPUIndex) + Send + Sync + 'static, 207 | { 208 | let callback = Box::new(cb); 209 | let callback_box = Box::new(callback); 210 | let userdata = Box::into_raw(callback_box) as *mut c_void; 211 | 212 | unsafe { 213 | crate::sys::qemu_plugin_register_vcpu_insn_exec_cb( 214 | self.instruction as *mut qemu_plugin_insn, 215 | Some(handle_qemu_plugin_register_vcpu_insn_exec_cb::), 216 | flags, 217 | userdata, 218 | ) 219 | }; 220 | } 221 | 222 | /// Register a callback to be conditionally run on execution of this instruction 223 | /// with no capability to inspect registers 224 | /// 225 | /// # Arguments 226 | /// 227 | /// - `cb`: The callback to be run 228 | /// - `cond`: The condition for the callback to be run 229 | /// - `entry`: The entry to increment the scoreboard for 230 | /// - `immediate`: The immediate value to use for the callback 231 | #[cfg(not(any( 232 | feature = "plugin-api-v0", 233 | feature = "plugin-api-v1", 234 | feature = "plugin-api-v2" 235 | )))] 236 | pub fn register_conditional_execute_callback( 237 | &self, 238 | cb: F, 239 | cond: PluginCondition, 240 | entry: PluginU64, 241 | immediate: u64, 242 | ) where 243 | F: FnMut(VCPUIndex) + Send + Sync + 'static, 244 | { 245 | self.register_conditional_execute_callback_flags( 246 | cb, 247 | CallbackFlags::QEMU_PLUGIN_CB_NO_REGS, 248 | cond, 249 | entry, 250 | immediate, 251 | ) 252 | } 253 | 254 | /// Register a callback to be conditionally run on execution of this instruction 255 | /// with a choice of capability whether to inspect or modify registers or not 256 | /// 257 | /// # Arguments 258 | /// 259 | /// - `cb`: The callback to be run 260 | /// - `flags`: The flags for the callback specifying whether the callback needs 261 | /// permission to read or write registers 262 | /// - `cond`: The condition for the callback to be run 263 | /// - `entry`: The entry to increment the scoreboard for 264 | /// - `immediate`: The immediate value to use for the callback 265 | #[cfg(not(any( 266 | feature = "plugin-api-v0", 267 | feature = "plugin-api-v1", 268 | feature = "plugin-api-v2" 269 | )))] 270 | pub fn register_conditional_execute_callback_flags( 271 | &self, 272 | cb: F, 273 | flags: CallbackFlags, 274 | cond: PluginCondition, 275 | entry: PluginU64, 276 | immediate: u64, 277 | ) where 278 | F: FnMut(VCPUIndex) + Send + Sync + 'static, 279 | { 280 | let callback = Box::new(cb); 281 | let callback_box = Box::new(callback); 282 | let userdata = Box::into_raw(callback_box) as *mut c_void; 283 | 284 | unsafe { 285 | crate::sys::qemu_plugin_register_vcpu_insn_exec_cond_cb( 286 | self.instruction as *mut qemu_plugin_insn, 287 | Some(handle_qemu_plugin_register_vcpu_insn_exec_cb::), 288 | flags, 289 | cond, 290 | entry, 291 | immediate, 292 | userdata, 293 | ) 294 | }; 295 | } 296 | 297 | /// Register a callback to be run on memory access of this instruction 298 | /// 299 | /// # Arguments 300 | /// 301 | /// - `cb`: The callback to be run 302 | /// - `rw`: The type of memory access to trigger the callback on 303 | pub fn register_memory_access_callback(&self, cb: F, rw: MemRW) 304 | where 305 | F: for<'b> FnMut(VCPUIndex, MemoryInfo<'b>, u64) + Send + Sync + 'static, 306 | { 307 | self.register_memory_access_callback_flags(cb, rw, CallbackFlags::QEMU_PLUGIN_CB_NO_REGS) 308 | } 309 | 310 | /// Register a callback to be run on memory access of this instruction 311 | /// 312 | /// # Arguments 313 | /// 314 | /// - `cb`: The callback to be run 315 | /// - `rw`: The type of memory access to trigger the callback on 316 | pub fn register_memory_access_callback_flags(&self, cb: F, rw: MemRW, flags: CallbackFlags) 317 | where 318 | F: for<'b> FnMut(VCPUIndex, MemoryInfo<'b>, u64) + Send + Sync + 'static, 319 | { 320 | let callback = Box::new(cb); 321 | let callback_box = Box::new(callback); 322 | let userdata = Box::into_raw(callback_box) as *mut c_void; 323 | 324 | unsafe { 325 | crate::sys::qemu_plugin_register_vcpu_mem_cb( 326 | self.instruction as *mut qemu_plugin_insn, 327 | Some(handle_qemu_plugin_register_vcpu_mem_cb::), 328 | flags, 329 | rw, 330 | userdata, 331 | ) 332 | }; 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | check_linux: 14 | name: Check Build (Linux) 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: dtolnay/rust-toolchain@nightly 18 | with: 19 | components: clippy rustfmt 20 | 21 | - uses: actions/checkout@v4 22 | 23 | - name: Install Cargo-Hack 24 | run: | 25 | cargo install cargo-hack 26 | 27 | - name: Check Code 28 | run: | 29 | ./scripts/check.sh 30 | 31 | check_macos: 32 | name: Check Build (macOS) 33 | runs-on: macos-latest 34 | steps: 35 | - uses: dtolnay/rust-toolchain@nightly 36 | with: 37 | components: clippy rustfmt 38 | 39 | - uses: actions/checkout@v4 40 | 41 | - name: Install Cargo-Hack 42 | run: | 43 | cargo install cargo-hack 44 | 45 | - name: Check Code 46 | run: | 47 | ./scripts/check.sh 48 | 49 | check_windows: 50 | name: Check Build (Windows) 51 | runs-on: windows-latest 52 | steps: 53 | - uses: dtolnay/rust-toolchain@nightly 54 | with: 55 | components: clippy rustfmt 56 | 57 | - uses: actions/checkout@v4 58 | 59 | - name: Install Cargo-Hack 60 | run: | 61 | cargo install cargo-hack 62 | 63 | - name: Check Code 64 | shell: pwsh 65 | run: | 66 | ./scripts/check.ps1 67 | 68 | test_plugins_linux: 69 | name: Build and Test Plugins API v${{ matrix.version }} (Linux) 70 | runs-on: ubuntu-latest 71 | container: ubuntu:24.04 72 | 73 | strategy: 74 | matrix: 75 | include: 76 | - version: 1 77 | commit: 1332b8dd434674480f0feb2cdf3bbaebb85b4240 78 | - version: 2 79 | commit: c25df57ae8f9fe1c72eee2dab37d76d904ac382e 80 | - version: 3 81 | commit: 7de77d37880d7267a491cb32a1b2232017d1e545 82 | - version: 4 83 | commit: 595cd9ce2ec9330882c991a647d5bc2a5640f380 84 | - version: 5 85 | commit: f8b2f64e2336a28bf0d50b6ef8a7d8c013e9bcf3 86 | 87 | env: 88 | QEMU_COMMIT_HASH: ${{ matrix.commit }} 89 | 90 | steps: 91 | - name: Set up Sources List 92 | run: | 93 | cat < /etc/apt/sources.list.d/ubuntu.sources 94 | Types: deb 95 | URIs: http://archive.ubuntu.com/ubuntu/ 96 | Suites: noble noble-updates noble-backports 97 | Components: main universe restricted multiverse 98 | Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg 99 | 100 | Types: deb 101 | URIs: http://security.ubuntu.com/ubuntu/ 102 | Suites: noble-security 103 | Components: main universe restricted multiverse 104 | Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg 105 | 106 | Types: deb-src 107 | URIs: http://archive.ubuntu.com/ubuntu/ 108 | Suites: noble noble-updates noble-backports 109 | Components: main universe restricted multiverse 110 | Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg 111 | 112 | Types: deb-src 113 | URIs: http://security.ubuntu.com/ubuntu/ 114 | Suites: noble-security 115 | Components: main universe restricted multiverse 116 | Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg 117 | EOF 118 | 119 | - name: Create APT Script 120 | run: | 121 | cat < install-apt-packages.sh 122 | apt -y update 123 | apt -y install git curl build-essential 124 | apt -y source qemu 125 | apt -y build-dep qemu 126 | EOF 127 | chmod +x install-apt-packages.sh 128 | 129 | - name: Cache apt packages 130 | uses: actions/cache@v4 131 | with: 132 | path: /var/cache/apt/archives 133 | key: ${{ runner.os }}-apt-${{ hashFiles('install-deps.sh') }} 134 | restore-keys: | 135 | ${{ runner.os }}-apt- 136 | 137 | - name: Install APT Packages 138 | run: | 139 | ./install-apt-packages.sh 140 | 141 | - name: Cache QEMU 142 | id: qemu-cache 143 | uses: actions/cache@v3 144 | with: 145 | path: | 146 | qemu-upstream 147 | key: ${{ runner.os }}-qemu-v${{ matrix.version }}-${{ matrix.commit }} 148 | 149 | - name: Clone QEMU 150 | if: steps.qemu-cache.outputs.cache-hit != 'true' 151 | run: | 152 | git clone https://gitlab.com/qemu/qemu qemu-upstream 153 | git -C qemu-upstream checkout "${QEMU_COMMIT_HASH}" 154 | 155 | - name: Build QEMU 156 | if: steps.qemu-cache.outputs.cache-hit != 'true' 157 | run: | 158 | cd qemu-upstream 159 | ./configure --enable-plugins --target-list="x86_64-linux-user x86_64-softmmu" 160 | make -C build -j$(nproc) 161 | make -C build install 162 | 163 | - uses: dtolnay/rust-toolchain@nightly 164 | 165 | - uses: actions/checkout@v4 166 | 167 | - name: Test QEMU Install 168 | run: | 169 | qemu-x86_64 --help 170 | 171 | - name: Run Tracer 172 | run: | 173 | cargo build --manifest-path=plugins/tracer/Cargo.toml -r --features=plugin-api-v${{ matrix.version }} --no-default-features 174 | cargo run --manifest-path=plugins/tracer-driver/Cargo.toml -r --features=plugin-api-v${{ matrix.version }} --no-default-features -- -P target/release/libtracer.so -a /bin/ls -- -lah 2> err.txt > out.txt 175 | tail -n 100 err.txt 176 | tail -n 100 out.txt 177 | # Ensure out.txt contains at least one line that starts with '{"Instruction":' 178 | grep -E '^\{"Instruction":' out.txt 179 | 180 | - name: Build and Test Tiny 181 | run: | 182 | cargo build --manifest-path=plugins/tiny/Cargo.toml -r --features=plugin-api-v${{ matrix.version }} --no-default-features 183 | qemu-x86_64 -plugin target/release/libtiny.so /bin/ls -lah 2> err.txt > out.txt 184 | tail -n 100 err.txt 185 | tail -n 100 out.txt 186 | # Ensure out.txt contains at least one line of the format : string 187 | grep -E '^[0-9a-fA-F]+:' out.txt 188 | 189 | test_plugins_macos: 190 | name: Build Plugins API v${{ matrix.version }} (macOS) 191 | runs-on: macos-latest 192 | 193 | strategy: 194 | matrix: 195 | include: 196 | # - version: 1 197 | # commit: 1332b8dd434674480f0feb2cdf3bbaebb85b4240 198 | # - version: 2 199 | # commit: c25df57ae8f9fe1c72eee2dab37d76d904ac382e 200 | # - version: 3 201 | # commit: 7de77d37880d7267a491cb32a1b2232017d1e545 202 | # - version: 4 203 | # commit: 595cd9ce2ec9330882c991a647d5bc2a5640f380 204 | - version: 5 205 | commit: f8b2f64e2336a28bf0d50b6ef8a7d8c013e9bcf3 206 | 207 | env: 208 | QEMU_COMMIT_HASH: ${{ matrix.commit }} 209 | FEDORA_CLOUDIMG_URL: "https://download.fedoraproject.org/pub/fedora/linux/releases/42/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-42-1.1.x86_64.qcow2" 210 | 211 | steps: 212 | - uses: dtolnay/rust-toolchain@nightly 213 | 214 | - uses: actions/checkout@v4 215 | 216 | - name: Install QEMU Build Dependencies 217 | run: | 218 | brew update 219 | brew install pkg-config glib pixman gettext meson ninja python coreutils 220 | 221 | - name: Cache QEMU 222 | id: qemu-cache-macos 223 | uses: actions/cache@v3 224 | with: 225 | path: | 226 | qemu-upstream 227 | key: macos-qemu-v${{ matrix.version }}-${{ matrix.commit }} 228 | 229 | - name: Clone QEMU 230 | if: steps.qemu-cache-macos.outputs.cache-hit != 'true' 231 | run: | 232 | git clone https://gitlab.com/qemu/qemu qemu-upstream 233 | git -C qemu-upstream checkout "${QEMU_COMMIT_HASH}" 234 | 235 | - name: Build QEMU from source (softmmu) 236 | if: steps.qemu-cache-macos.outputs.cache-hit != 'true' 237 | run: | 238 | cd qemu-upstream 239 | ./configure --enable-plugins --target-list="x86_64-linux-user x86_64-softmmu" 240 | make -C build -j"$(sysctl -n hw.logicalcpu)" 241 | 242 | - name: Test QEMU Install 243 | run: | 244 | ls qemu-upstream/build 245 | qemu-upstream/build/qemu-system-x86_64-unsigned --version 246 | 247 | - name: Cache Fedora Cloud Image 248 | id: fedora-cache 249 | uses: actions/cache@v3 250 | with: 251 | path: Fedora-Cloud-Base-Generic-42-1.1.x86_64.qcow2 252 | key: fedora-Cloud-42-1.1 253 | 254 | - name: Download Cloud Image 255 | if: steps.fedora-cache.outputs.cache-hit != 'true' 256 | run: | 257 | curl -L -o Fedora-Cloud-Base-Generic-42-1.1.x86_64.qcow2 ${{ env.FEDORA_CLOUDIMG_URL }} 258 | 259 | - name: Build And Test Tiny System 260 | run: | 261 | cargo build -r --manifest-path=plugins/tiny-system/Cargo.toml 262 | timeout --preserve-status 180 qemu-upstream/build/qemu-system-x86_64-unsigned -machine type=q35 -m 2G -nographic -drive if=virtio,format=qcow2,file=Fedora-Cloud-Base-Generic-42-1.1.x86_64.qcow2 -drive if=virtio,format=raw,file=.github/rsrc/seed.img -plugin target/release/libtiny_system.dylib 2> err.txt > out.txt || exit 0 263 | tail -n 100 out.txt 264 | tail -n 100 err.txt 265 | # Make sure out.txt contains "on_vcpu_resume" which shows the plugin was loaded and ran 266 | grep "on_vcpu_resume" out.txt 267 | 268 | test_plugins_windows: 269 | name: Build and Test Plugins (Windows) 270 | runs-on: windows-latest 271 | 272 | env: 273 | QEMU_VERSION: "10.1.0-1" 274 | RUSTUP_URL: "https://win.rustup.rs/x86_64" 275 | FEDORA_CLOUDIMG_URL: "https://download.fedoraproject.org/pub/fedora/linux/releases/42/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-42-1.1.x86_64.qcow2" 276 | 277 | steps: 278 | - name: Set QEMU Version 279 | run: | 280 | echo "QEMU_VERSION=$QEMU_VERSION" >> $GITHUB_ENV 281 | 282 | # Cache MSYS2 283 | - name: Cache MSYS2 packages 284 | id: msys2-cache 285 | uses: actions/cache@v3 286 | with: 287 | path: C:\msys-custom 288 | key: ${{ runner.os }}-msys2-${{ env.QEMU_VERSION }} 289 | 290 | - name: Install MSYS2 and QEMU 291 | uses: msys2/setup-msys2@v2 292 | if: steps.msys2-cache.outputs.cache-hit != 'true' 293 | with: 294 | msystem: UCRT64 295 | update: true 296 | install: git mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-qemu=${{ env.QEMU_VERSION }} 297 | location: C:\msys-custom 298 | 299 | - name: Download and Install Rust 300 | run: | 301 | $ProgressPreference = 'SilentlyContinue' 302 | Invoke-WebRequest -Uri ${{ env.RUSTUP_URL }} -OutFile rustup-init.exe 303 | ./rustup-init.exe --default-toolchain nightly --default-host x86_64-pc-windows-gnu -y 304 | 305 | - name: Test QEMU 306 | run: | 307 | C:\msys-custom\msys64\ucrt64\bin\qemu-system-x86_64.exe --version 308 | 309 | - uses: actions/checkout@v4 310 | 311 | - name: Cache Fedora Cloud Image 312 | id: fedora-cache 313 | uses: actions/cache@v3 314 | with: 315 | path: Fedora-Cloud-Base-Generic-42-1.1.x86_64.qcow2 316 | key: Fedora-Cloud-42-1.1 317 | 318 | - name: Download Cloud Image 319 | if: steps.fedora-cache.outputs.cache-hit != 'true' 320 | run: | 321 | $ProgressPreference = 'SilentlyContinue' 322 | Invoke-WebRequest -Uri ${{ env.FEDORA_CLOUDIMG_URL }} -OutFile Fedora-Cloud-Base-Generic-42-1.1.x86_64.qcow2 323 | 324 | - name: Build and Test Tiny 325 | run: | 326 | cargo build --manifest-path=plugins/tiny-system/Cargo.toml -r 327 | $process = Start-Process PowerShell.exe -NoNewWindow -RedirectStandardOutput out.txt -RedirectStandardError err.txt -PassThru -ArgumentList "-Command", "C:\msys-custom\msys64\ucrt64\bin\qemu-system-x86_64.exe -machine type=q35 -m 2G -nographic -device virtio-net-pci,netdev=net0 -netdev user,id=net0,hostfwd=tcp::2222-:22 -drive if=virtio,format=qcow2,file=Fedora-Cloud-Base-Generic-42-1.1.x86_64.qcow2 -drive if=virtio,format=raw,file=.github/rsrc/seed.img -plugin target/release/tiny_system.dll" 328 | echo "Sleeping 180.0 seconds until booted (boot process took 118s first time)" 329 | Start-Sleep -Seconds 180.0 330 | echo "Stopping process" 331 | Stop-Process -Id $process.id -ErrorAction SilentlyContinue 332 | Get-Content -Tail 100 out.txt 333 | Get-Content -Tail 100 err.txt 334 | # Make sure out.txt contains "on_vcpu_resume" which shows the plugin was loaded and ran 335 | Select-String -Path out.txt -Pattern "on_vcpu_resume" 336 | -------------------------------------------------------------------------------- /qemu-plugin/src/memory/mod.rs: -------------------------------------------------------------------------------- 1 | //! Memory-related functionality for QEMU plugins 2 | 3 | #[cfg(not(any( 4 | feature = "plugin-api-v0", 5 | feature = "plugin-api-v1", 6 | feature = "plugin-api-v2", 7 | feature = "plugin-api-v3", 8 | )))] 9 | use crate::Error; 10 | #[cfg(not(feature = "plugin-api-v0"))] 11 | use crate::Result; 12 | #[cfg(not(any( 13 | feature = "plugin-api-v0", 14 | feature = "plugin-api-v1", 15 | feature = "plugin-api-v2", 16 | feature = "plugin-api-v3", 17 | feature = "plugin-api-v4" 18 | )))] 19 | use crate::sys::qemu_plugin_hwaddr_operation_result; 20 | #[cfg(not(any( 21 | feature = "plugin-api-v0", 22 | feature = "plugin-api-v1", 23 | feature = "plugin-api-v2", 24 | feature = "plugin-api-v3" 25 | )))] 26 | use crate::sys::{GByteArray, qemu_plugin_mem_value, qemu_plugin_mem_value_type}; 27 | use crate::sys::{qemu_plugin_hwaddr, qemu_plugin_meminfo_t}; 28 | use std::marker::PhantomData; 29 | 30 | #[derive(Debug, Clone)] 31 | /// Wrapper structure for a `qemu_plugin_meminfo_t` 32 | /// 33 | /// # Safety 34 | /// 35 | /// This structure is safe to use during the invocation of the callback which receives it as an 36 | /// argument. The structure is always opaque, and therefore may not be accessed directly. 37 | pub struct MemoryInfo<'a> { 38 | memory_info: qemu_plugin_meminfo_t, 39 | marker: PhantomData<&'a ()>, 40 | } 41 | 42 | impl<'a> From for MemoryInfo<'a> { 43 | fn from(info: qemu_plugin_meminfo_t) -> Self { 44 | Self { 45 | memory_info: info, 46 | marker: PhantomData, 47 | } 48 | } 49 | } 50 | 51 | impl<'a> MemoryInfo<'a> { 52 | /// Returns the size of the access in base-2, e.g. 0 for byte, 1 for 16-bit, 2 for 53 | /// 32-bit, etc. 54 | pub fn size_shift(&self) -> usize { 55 | (unsafe { crate::sys::qemu_plugin_mem_size_shift(self.memory_info) }) as usize 56 | } 57 | 58 | /// Returns whether the access was sign extended 59 | pub fn sign_extended(&self) -> bool { 60 | unsafe { crate::sys::qemu_plugin_mem_is_sign_extended(self.memory_info) } 61 | } 62 | 63 | /// Returns whether the access was big-endian 64 | pub fn big_endian(&self) -> bool { 65 | unsafe { crate::sys::qemu_plugin_mem_is_big_endian(self.memory_info) } 66 | } 67 | 68 | /// Returns whether the access was a store 69 | pub fn is_store(&self) -> bool { 70 | unsafe { crate::sys::qemu_plugin_mem_is_store(self.memory_info) } 71 | } 72 | 73 | /// Return a handle to query details about the physical address backing the virtual address 74 | /// in system emulation. In user-mode, this method always returns `None`. 75 | pub fn hwaddr(&'a self, vaddr: u64) -> Option> { 76 | let hwaddr = unsafe { crate::sys::qemu_plugin_get_hwaddr(self.memory_info, vaddr) }; 77 | if hwaddr.is_null() { 78 | None 79 | } else { 80 | Some(HwAddr::from(hwaddr)) 81 | } 82 | } 83 | 84 | /// Return last value loaded/stored 85 | #[cfg(not(any( 86 | feature = "plugin-api-v0", 87 | feature = "plugin-api-v1", 88 | feature = "plugin-api-v2", 89 | feature = "plugin-api-v3" 90 | )))] 91 | pub fn value(&self) -> MemValue { 92 | let qemu_mem_value = unsafe { crate::sys::qemu_plugin_mem_get_value(self.memory_info) }; 93 | MemValue::from(qemu_mem_value) 94 | } 95 | } 96 | 97 | #[cfg(not(any( 98 | feature = "plugin-api-v0", 99 | feature = "plugin-api-v1", 100 | feature = "plugin-api-v2", 101 | feature = "plugin-api-v3" 102 | )))] 103 | #[derive(Clone)] 104 | /// Memory value loaded/stored (in memory callback) 105 | /// 106 | /// Wrapper structure for a `qemu_plugin_mem_value` 107 | pub enum MemValue { 108 | /// 8-bit value 109 | U8(u8), 110 | /// 16-bit value 111 | U16(u16), 112 | /// 32-bit value 113 | U32(u32), 114 | /// 64-bit value 115 | U64(u64), 116 | /// 128-bit value 117 | U128(u128), 118 | } 119 | 120 | #[cfg(not(any( 121 | feature = "plugin-api-v0", 122 | feature = "plugin-api-v1", 123 | feature = "plugin-api-v2", 124 | feature = "plugin-api-v3" 125 | )))] 126 | impl From for MemValue { 127 | fn from(value: qemu_plugin_mem_value) -> Self { 128 | unsafe { 129 | match value.type_ { 130 | qemu_plugin_mem_value_type::QEMU_PLUGIN_MEM_VALUE_U8 => Self::U8(value.data.u8_), 131 | qemu_plugin_mem_value_type::QEMU_PLUGIN_MEM_VALUE_U16 => Self::U16(value.data.u16_), 132 | qemu_plugin_mem_value_type::QEMU_PLUGIN_MEM_VALUE_U32 => Self::U32(value.data.u32_), 133 | qemu_plugin_mem_value_type::QEMU_PLUGIN_MEM_VALUE_U64 => Self::U64(value.data.u64_), 134 | qemu_plugin_mem_value_type::QEMU_PLUGIN_MEM_VALUE_U128 => { 135 | let high = value.data.u128_.high as u128; 136 | let low = value.data.u128_.low as u128; 137 | Self::U128(high << 64 | low) 138 | } 139 | } 140 | } 141 | } 142 | } 143 | 144 | #[derive(Debug, Clone)] 145 | /// Wrapper structure for a `qemu_plugin_hwaddr *` 146 | /// 147 | /// # Safety 148 | /// 149 | /// This structure is safe to use as long as the pointer is valid. The pointer is 150 | /// always opaque, and therefore may not be dereferenced. 151 | pub struct HwAddr<'a> { 152 | hwaddr: usize, 153 | marker: PhantomData<&'a ()>, 154 | } 155 | 156 | impl<'a> From<*mut qemu_plugin_hwaddr> for HwAddr<'a> { 157 | fn from(hwaddr: *mut qemu_plugin_hwaddr) -> Self { 158 | Self { 159 | hwaddr: hwaddr as usize, 160 | marker: PhantomData, 161 | } 162 | } 163 | } 164 | 165 | impl<'a> HwAddr<'a> { 166 | /// Returns whether the memory operation is to MMIO. Returns false if the operation is to 167 | /// RAM. 168 | pub fn is_io(&self) -> bool { 169 | unsafe { crate::sys::qemu_plugin_hwaddr_is_io(self.hwaddr as *mut qemu_plugin_hwaddr) } 170 | } 171 | 172 | #[cfg(not(feature = "plugin-api-v0"))] 173 | /// Returns the physical address for the memory operation 174 | pub fn hwaddr(&self) -> u64 { 175 | unsafe { crate::sys::qemu_plugin_hwaddr_phys_addr(self.hwaddr as *mut qemu_plugin_hwaddr) } 176 | } 177 | 178 | #[cfg(not(feature = "plugin-api-v0"))] 179 | /// Returns a string representing the device 180 | pub fn device_name(&self) -> Result> { 181 | let device_name = unsafe { 182 | crate::sys::qemu_plugin_hwaddr_device_name(self.hwaddr as *mut qemu_plugin_hwaddr) 183 | }; 184 | 185 | if device_name.is_null() { 186 | Ok(None) 187 | } else { 188 | let device_name_string = unsafe { 189 | use std::ffi::CStr; 190 | CStr::from_ptr(device_name) 191 | } 192 | .to_str()? 193 | .to_string(); 194 | // NOTE: The string is static, so we do not free it 195 | Ok(Some(device_name_string)) 196 | } 197 | } 198 | } 199 | 200 | #[cfg(not(any( 201 | feature = "plugin-api-v0", 202 | feature = "plugin-api-v1", 203 | feature = "plugin-api-v2", 204 | feature = "plugin-api-v3" 205 | )))] 206 | /// Read memory from a virtual address. The address must be valid and mapped. 207 | pub fn qemu_plugin_read_memory_vaddr(addr: u64, buf: &mut [u8]) -> Result<()> { 208 | let mut buf = GByteArray { 209 | data: buf.as_mut_ptr(), 210 | len: buf.len() as u32, 211 | }; 212 | 213 | if unsafe { 214 | crate::sys::qemu_plugin_read_memory_vaddr( 215 | addr, 216 | &mut buf as *mut GByteArray, 217 | buf.len as usize, 218 | ) 219 | } { 220 | Ok(()) 221 | } else { 222 | Err(Error::VaddrReadError { addr, len: buf.len }) 223 | } 224 | } 225 | 226 | #[cfg(not(any( 227 | feature = "plugin-api-v0", 228 | feature = "plugin-api-v1", 229 | feature = "plugin-api-v2", 230 | feature = "plugin-api-v3", 231 | feature = "plugin-api-v4" 232 | )))] 233 | /// Write memory to a virtual address. The address must be valid and mapped. 234 | pub fn qemu_plugin_write_memory_vaddr(addr: u64, buf: &mut [u8]) -> Result<()> { 235 | let mut buf = GByteArray { 236 | data: buf.as_mut_ptr(), 237 | len: buf.len() as u32, 238 | }; 239 | 240 | if unsafe { crate::sys::qemu_plugin_write_memory_vaddr(addr, &mut buf as *mut GByteArray) } { 241 | Ok(()) 242 | } else { 243 | Err(Error::VaddrWriteError { addr, len: buf.len }) 244 | } 245 | } 246 | 247 | #[cfg(not(any( 248 | feature = "plugin-api-v0", 249 | feature = "plugin-api-v1", 250 | feature = "plugin-api-v2", 251 | feature = "plugin-api-v3", 252 | feature = "plugin-api-v4" 253 | )))] 254 | #[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)] 255 | /// The result of a hardware operation 256 | pub enum HwaddrOperationResult { 257 | #[error("Operation completed successfully")] 258 | /// Operation completed successfully 259 | Ok = 0, 260 | #[error("Unspecified error")] 261 | /// Unspecified error 262 | Error = 1, 263 | #[error("Device error")] 264 | /// Device error 265 | DeviceError = 2, 266 | #[error("Access denied")] 267 | /// Access denied 268 | AccessDenied = 3, 269 | /// Invalid address 270 | #[error("Invalid address")] 271 | InvalidAddress = 4, 272 | /// Invalid address space 273 | #[error("Invalid address space")] 274 | InvalidAddressSpace = 5, 275 | } 276 | 277 | #[cfg(not(any( 278 | feature = "plugin-api-v0", 279 | feature = "plugin-api-v1", 280 | feature = "plugin-api-v2", 281 | feature = "plugin-api-v3", 282 | feature = "plugin-api-v4" 283 | )))] 284 | impl From for HwaddrOperationResult { 285 | fn from(value: qemu_plugin_hwaddr_operation_result) -> Self { 286 | match value { 287 | qemu_plugin_hwaddr_operation_result::QEMU_PLUGIN_HWADDR_OPERATION_OK => Self::Ok, 288 | qemu_plugin_hwaddr_operation_result::QEMU_PLUGIN_HWADDR_OPERATION_ERROR => Self::Error, 289 | qemu_plugin_hwaddr_operation_result::QEMU_PLUGIN_HWADDR_OPERATION_DEVICE_ERROR => Self::DeviceError, 290 | qemu_plugin_hwaddr_operation_result::QEMU_PLUGIN_HWADDR_OPERATION_ACCESS_DENIED => Self::AccessDenied, 291 | qemu_plugin_hwaddr_operation_result::QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS => Self::InvalidAddress, 292 | qemu_plugin_hwaddr_operation_result::QEMU_PLUGIN_HWADDR_OPERATION_INVALID_ADDRESS_SPACE => Self::InvalidAddressSpace, 293 | } 294 | } 295 | } 296 | 297 | #[cfg(not(any( 298 | feature = "plugin-api-v0", 299 | feature = "plugin-api-v1", 300 | feature = "plugin-api-v2", 301 | feature = "plugin-api-v3", 302 | feature = "plugin-api-v4" 303 | )))] 304 | /// Read memory from a hardware address. The address must be valid and mapped. 305 | pub fn qemu_plugin_read_memory_hwaddr(addr: u64, buf: &mut [u8]) -> Result<()> { 306 | let mut buf = GByteArray { 307 | data: buf.as_mut_ptr(), 308 | len: buf.len() as u32, 309 | }; 310 | 311 | match unsafe { 312 | crate::sys::qemu_plugin_read_memory_hwaddr( 313 | addr, 314 | &mut buf as *mut GByteArray, 315 | buf.len as usize, 316 | ) 317 | } 318 | .into() 319 | { 320 | HwaddrOperationResult::Ok => Ok(()), 321 | error => Err(Error::HwaddrReadError { 322 | addr, 323 | len: buf.len, 324 | result: error, 325 | }), 326 | } 327 | } 328 | 329 | #[cfg(not(any( 330 | feature = "plugin-api-v0", 331 | feature = "plugin-api-v1", 332 | feature = "plugin-api-v2", 333 | feature = "plugin-api-v3", 334 | feature = "plugin-api-v4" 335 | )))] 336 | /// Read memory from a virtual address. The address must be valid and mapped. 337 | pub fn qemu_plugin_write_memory_hwaddr(addr: u64, buf: &mut [u8]) -> Result<()> { 338 | let mut buf = GByteArray { 339 | data: buf.as_mut_ptr(), 340 | len: buf.len() as u32, 341 | }; 342 | 343 | match unsafe { crate::sys::qemu_plugin_write_memory_hwaddr(addr, &mut buf as *mut GByteArray) } 344 | .into() 345 | { 346 | HwaddrOperationResult::Ok => Ok(()), 347 | error => Err(Error::HwaddrWriteError { 348 | addr, 349 | len: buf.len, 350 | result: error, 351 | }), 352 | } 353 | } 354 | 355 | #[cfg(not(any( 356 | feature = "plugin-api-v0", 357 | feature = "plugin-api-v1", 358 | feature = "plugin-api-v2", 359 | feature = "plugin-api-v3", 360 | feature = "plugin-api-v4" 361 | )))] 362 | /// Translate a virtual address to a hardware address. If the address is not 363 | /// mapped, an error is returned. 364 | pub fn qemu_plugin_translate_vaddr(vaddr: u64) -> Result { 365 | let mut hwaddr: u64 = 0; 366 | if unsafe { crate::sys::qemu_plugin_translate_vaddr(vaddr, &mut hwaddr as *mut _) } { 367 | Ok(hwaddr) 368 | } else { 369 | Err(Error::VaddrTranslateError { vaddr }) 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /qemu-plugin-sys/src/bindings_v0.rs: -------------------------------------------------------------------------------- 1 | /* automatically generated by rust-bindgen 0.72.0 */ 2 | 3 | pub const QEMU_PLUGIN_VERSION: u32 = 0; 4 | #[repr(C)] 5 | #[derive(Debug, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] 6 | pub struct GArray { 7 | pub data: *mut ::std::os::raw::c_char, 8 | pub len: ::std::os::raw::c_uint, 9 | } 10 | impl Default for GArray { 11 | fn default() -> Self { 12 | let mut s = ::std::mem::MaybeUninit::::uninit(); 13 | unsafe { 14 | ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); 15 | s.assume_init() 16 | } 17 | } 18 | } 19 | #[repr(C)] 20 | #[derive(Debug, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] 21 | pub struct GByteArray { 22 | pub data: *mut ::std::os::raw::c_uchar, 23 | pub len: ::std::os::raw::c_uint, 24 | } 25 | impl Default for GByteArray { 26 | fn default() -> Self { 27 | let mut s = ::std::mem::MaybeUninit::::uninit(); 28 | unsafe { 29 | ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); 30 | s.assume_init() 31 | } 32 | } 33 | } 34 | pub type qemu_plugin_id_t = u64; 35 | #[repr(C)] 36 | #[derive(Copy, Clone)] 37 | pub struct qemu_info_t { 38 | #[doc = " string describing architecture"] 39 | pub target_name: *const ::std::os::raw::c_char, 40 | pub version: qemu_info_t__bindgen_ty_1, 41 | #[doc = " is this a full system emulation?"] 42 | pub system_emulation: bool, 43 | pub __bindgen_anon_1: qemu_info_t__bindgen_ty_2, 44 | } 45 | #[repr(C)] 46 | #[derive(Debug, Default, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] 47 | pub struct qemu_info_t__bindgen_ty_1 { 48 | pub min: ::std::os::raw::c_int, 49 | pub cur: ::std::os::raw::c_int, 50 | } 51 | #[repr(C)] 52 | #[derive(Copy, Clone)] 53 | pub union qemu_info_t__bindgen_ty_2 { 54 | pub system: qemu_info_t__bindgen_ty_2__bindgen_ty_1, 55 | } 56 | #[doc = " smp_vcpus may change if vCPUs can be hot-plugged, max_vcpus\n is the system-wide limit."] 57 | #[repr(C)] 58 | #[derive(Debug, Default, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] 59 | pub struct qemu_info_t__bindgen_ty_2__bindgen_ty_1 { 60 | pub smp_vcpus: ::std::os::raw::c_int, 61 | pub max_vcpus: ::std::os::raw::c_int, 62 | } 63 | impl Default for qemu_info_t__bindgen_ty_2 { 64 | fn default() -> Self { 65 | let mut s = ::std::mem::MaybeUninit::::uninit(); 66 | unsafe { 67 | ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); 68 | s.assume_init() 69 | } 70 | } 71 | } 72 | impl Default for qemu_info_t { 73 | fn default() -> Self { 74 | let mut s = ::std::mem::MaybeUninit::::uninit(); 75 | unsafe { 76 | ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); 77 | s.assume_init() 78 | } 79 | } 80 | } 81 | #[doc = " Prototypes for the various callback styles we will be registering\n in the following functions."] 82 | pub type qemu_plugin_simple_cb_t = 83 | ::std::option::Option; 84 | pub type qemu_plugin_udata_cb_t = ::std::option::Option< 85 | unsafe extern "C" fn(id: qemu_plugin_id_t, userdata: *mut ::std::os::raw::c_void), 86 | >; 87 | pub type qemu_plugin_vcpu_simple_cb_t = ::std::option::Option< 88 | unsafe extern "C" fn(id: qemu_plugin_id_t, vcpu_index: ::std::os::raw::c_uint), 89 | >; 90 | pub type qemu_plugin_vcpu_udata_cb_t = ::std::option::Option< 91 | unsafe extern "C" fn(vcpu_index: ::std::os::raw::c_uint, userdata: *mut ::std::os::raw::c_void), 92 | >; 93 | unsafe extern "C" { 94 | #[doc = " qemu_plugin_uninstall() - Uninstall a plugin\n @id: this plugin's opaque ID\n @cb: callback to be called once the plugin has been removed\n\n Do NOT assume that the plugin has been uninstalled once this function\n returns. Plugins are uninstalled asynchronously, and therefore the given\n plugin receives callbacks until @cb is called.\n\n Note: Calling this function from qemu_plugin_install() is a bug."] 95 | pub fn qemu_plugin_uninstall(id: qemu_plugin_id_t, cb: qemu_plugin_simple_cb_t); 96 | } 97 | unsafe extern "C" { 98 | #[doc = " qemu_plugin_reset() - Reset a plugin\n @id: this plugin's opaque ID\n @cb: callback to be called once the plugin has been reset\n\n Unregisters all callbacks for the plugin given by @id.\n\n Do NOT assume that the plugin has been reset once this function returns.\n Plugins are reset asynchronously, and therefore the given plugin receives\n callbacks until @cb is called."] 99 | pub fn qemu_plugin_reset(id: qemu_plugin_id_t, cb: qemu_plugin_simple_cb_t); 100 | } 101 | unsafe extern "C" { 102 | #[doc = " qemu_plugin_register_vcpu_init_cb() - register a vCPU initialization callback\n @id: plugin ID\n @cb: callback function\n\n The @cb function is called every time a vCPU is initialized.\n\n See also: qemu_plugin_register_vcpu_exit_cb()"] 103 | pub fn qemu_plugin_register_vcpu_init_cb( 104 | id: qemu_plugin_id_t, 105 | cb: qemu_plugin_vcpu_simple_cb_t, 106 | ); 107 | } 108 | unsafe extern "C" { 109 | #[doc = " qemu_plugin_register_vcpu_exit_cb() - register a vCPU exit callback\n @id: plugin ID\n @cb: callback function\n\n The @cb function is called every time a vCPU exits.\n\n See also: qemu_plugin_register_vcpu_init_cb()"] 110 | pub fn qemu_plugin_register_vcpu_exit_cb( 111 | id: qemu_plugin_id_t, 112 | cb: qemu_plugin_vcpu_simple_cb_t, 113 | ); 114 | } 115 | unsafe extern "C" { 116 | #[doc = " qemu_plugin_register_vcpu_idle_cb() - register a vCPU idle callback\n @id: plugin ID\n @cb: callback function\n\n The @cb function is called every time a vCPU idles."] 117 | pub fn qemu_plugin_register_vcpu_idle_cb( 118 | id: qemu_plugin_id_t, 119 | cb: qemu_plugin_vcpu_simple_cb_t, 120 | ); 121 | } 122 | unsafe extern "C" { 123 | #[doc = " qemu_plugin_register_vcpu_resume_cb() - register a vCPU resume callback\n @id: plugin ID\n @cb: callback function\n\n The @cb function is called every time a vCPU resumes execution."] 124 | pub fn qemu_plugin_register_vcpu_resume_cb( 125 | id: qemu_plugin_id_t, 126 | cb: qemu_plugin_vcpu_simple_cb_t, 127 | ); 128 | } 129 | #[doc = " Opaque types that the plugin is given during the translation and\n instrumentation phase."] 130 | #[repr(C)] 131 | #[derive(Debug, Copy, Clone)] 132 | pub struct qemu_plugin_tb { 133 | _unused: [u8; 0], 134 | } 135 | #[repr(C)] 136 | #[derive(Debug, Copy, Clone)] 137 | pub struct qemu_plugin_insn { 138 | _unused: [u8; 0], 139 | } 140 | #[repr(u32)] 141 | #[derive(Debug, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] 142 | pub enum qemu_plugin_cb_flags { 143 | #[doc = " callback does not access the CPU's regs"] 144 | QEMU_PLUGIN_CB_NO_REGS = 0, 145 | #[doc = " callback reads the CPU's regs"] 146 | QEMU_PLUGIN_CB_R_REGS = 1, 147 | #[doc = " callback reads and writes the CPU's regs"] 148 | QEMU_PLUGIN_CB_RW_REGS = 2, 149 | } 150 | #[repr(u32)] 151 | #[derive(Debug, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] 152 | pub enum qemu_plugin_mem_rw { 153 | QEMU_PLUGIN_MEM_R = 1, 154 | QEMU_PLUGIN_MEM_W = 2, 155 | QEMU_PLUGIN_MEM_RW = 3, 156 | } 157 | #[doc = " qemu_plugin_register_vcpu_tb_trans_cb() - register a translate cb\n @id: plugin ID\n @cb: callback function\n\n The @cb function is called every time a translation occurs. The @cb\n function is passed an opaque qemu_plugin_type which it can query\n for additional information including the list of translated\n instructions. At this point the plugin can register further\n callbacks to be triggered when the block or individual instruction\n executes."] 158 | pub type qemu_plugin_vcpu_tb_trans_cb_t = 159 | ::std::option::Option; 160 | unsafe extern "C" { 161 | pub fn qemu_plugin_register_vcpu_tb_trans_cb( 162 | id: qemu_plugin_id_t, 163 | cb: qemu_plugin_vcpu_tb_trans_cb_t, 164 | ); 165 | } 166 | unsafe extern "C" { 167 | #[doc = " qemu_plugin_register_vcpu_tb_trans_exec_cb() - register execution callback\n @tb: the opaque qemu_plugin_tb handle for the translation\n @cb: callback function\n @flags: does the plugin read or write the CPU's registers?\n @userdata: any plugin data to pass to the @cb?\n\n The @cb function is called every time a translated unit executes."] 168 | pub fn qemu_plugin_register_vcpu_tb_exec_cb( 169 | tb: *mut qemu_plugin_tb, 170 | cb: qemu_plugin_vcpu_udata_cb_t, 171 | flags: qemu_plugin_cb_flags, 172 | userdata: *mut ::std::os::raw::c_void, 173 | ); 174 | } 175 | #[repr(u32)] 176 | #[derive(Debug, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] 177 | pub enum qemu_plugin_op { 178 | QEMU_PLUGIN_INLINE_ADD_U64 = 0, 179 | } 180 | unsafe extern "C" { 181 | #[doc = " qemu_plugin_register_vcpu_tb_trans_exec_inline() - execution inline op\n @tb: the opaque qemu_plugin_tb handle for the translation\n @op: the type of qemu_plugin_op (e.g. ADD_U64)\n @ptr: the target memory location for the op\n @imm: the op data (e.g. 1)\n\n Insert an inline op to every time a translated unit executes.\n Useful if you just want to increment a single counter somewhere in\n memory."] 182 | pub fn qemu_plugin_register_vcpu_tb_exec_inline( 183 | tb: *mut qemu_plugin_tb, 184 | op: qemu_plugin_op, 185 | ptr: *mut ::std::os::raw::c_void, 186 | imm: u64, 187 | ); 188 | } 189 | unsafe extern "C" { 190 | #[doc = " qemu_plugin_register_vcpu_insn_exec_cb() - register insn execution cb\n @insn: the opaque qemu_plugin_insn handle for an instruction\n @cb: callback function\n @flags: does the plugin read or write the CPU's registers?\n @userdata: any plugin data to pass to the @cb?\n\n The @cb function is called every time an instruction is executed"] 191 | pub fn qemu_plugin_register_vcpu_insn_exec_cb( 192 | insn: *mut qemu_plugin_insn, 193 | cb: qemu_plugin_vcpu_udata_cb_t, 194 | flags: qemu_plugin_cb_flags, 195 | userdata: *mut ::std::os::raw::c_void, 196 | ); 197 | } 198 | unsafe extern "C" { 199 | #[doc = " qemu_plugin_register_vcpu_insn_exec_inline() - insn execution inline op\n @insn: the opaque qemu_plugin_insn handle for an instruction\n @cb: callback function\n @op: the type of qemu_plugin_op (e.g. ADD_U64)\n @ptr: the target memory location for the op\n @imm: the op data (e.g. 1)\n\n Insert an inline op to every time an instruction executes. Useful\n if you just want to increment a single counter somewhere in memory."] 200 | pub fn qemu_plugin_register_vcpu_insn_exec_inline( 201 | insn: *mut qemu_plugin_insn, 202 | op: qemu_plugin_op, 203 | ptr: *mut ::std::os::raw::c_void, 204 | imm: u64, 205 | ); 206 | } 207 | unsafe extern "C" { 208 | #[doc = " Helpers to query information about the instructions in a block"] 209 | pub fn qemu_plugin_tb_n_insns(tb: *const qemu_plugin_tb) -> usize; 210 | } 211 | unsafe extern "C" { 212 | pub fn qemu_plugin_tb_vaddr(tb: *const qemu_plugin_tb) -> u64; 213 | } 214 | unsafe extern "C" { 215 | pub fn qemu_plugin_tb_get_insn(tb: *const qemu_plugin_tb, idx: usize) -> *mut qemu_plugin_insn; 216 | } 217 | unsafe extern "C" { 218 | pub fn qemu_plugin_insn_data(insn: *const qemu_plugin_insn) -> *const ::std::os::raw::c_void; 219 | } 220 | unsafe extern "C" { 221 | pub fn qemu_plugin_insn_size(insn: *const qemu_plugin_insn) -> usize; 222 | } 223 | unsafe extern "C" { 224 | pub fn qemu_plugin_insn_vaddr(insn: *const qemu_plugin_insn) -> u64; 225 | } 226 | unsafe extern "C" { 227 | pub fn qemu_plugin_insn_haddr(insn: *const qemu_plugin_insn) -> *mut ::std::os::raw::c_void; 228 | } 229 | #[doc = " Memory Instrumentation\n\n The anonymous qemu_plugin_meminfo_t and qemu_plugin_hwaddr types\n can be used in queries to QEMU to get more information about a\n given memory access."] 230 | pub type qemu_plugin_meminfo_t = u32; 231 | #[repr(C)] 232 | #[derive(Debug, Copy, Clone)] 233 | pub struct qemu_plugin_hwaddr { 234 | _unused: [u8; 0], 235 | } 236 | unsafe extern "C" { 237 | #[doc = " meminfo queries"] 238 | pub fn qemu_plugin_mem_size_shift(info: qemu_plugin_meminfo_t) -> ::std::os::raw::c_uint; 239 | } 240 | unsafe extern "C" { 241 | pub fn qemu_plugin_mem_is_sign_extended(info: qemu_plugin_meminfo_t) -> bool; 242 | } 243 | unsafe extern "C" { 244 | pub fn qemu_plugin_mem_is_big_endian(info: qemu_plugin_meminfo_t) -> bool; 245 | } 246 | unsafe extern "C" { 247 | pub fn qemu_plugin_mem_is_store(info: qemu_plugin_meminfo_t) -> bool; 248 | } 249 | unsafe extern "C" { 250 | #[doc = " qemu_plugin_get_hwaddr():\n @vaddr: the virtual address of the memory operation\n\n For system emulation returns a qemu_plugin_hwaddr handle to query\n details about the actual physical address backing the virtual\n address. For linux-user guests it just returns NULL.\n\n This handle is *only* valid for the duration of the callback. Any\n information about the handle should be recovered before the\n callback returns."] 251 | pub fn qemu_plugin_get_hwaddr( 252 | info: qemu_plugin_meminfo_t, 253 | vaddr: u64, 254 | ) -> *mut qemu_plugin_hwaddr; 255 | } 256 | unsafe extern "C" { 257 | #[doc = " The following additional queries can be run on the hwaddr structure\n to return information about it. For non-IO accesses the device\n offset will be into the appropriate block of RAM."] 258 | pub fn qemu_plugin_hwaddr_is_io(hwaddr: *mut qemu_plugin_hwaddr) -> bool; 259 | } 260 | unsafe extern "C" { 261 | pub fn qemu_plugin_hwaddr_device_offset(haddr: *const qemu_plugin_hwaddr) -> u64; 262 | } 263 | pub type qemu_plugin_vcpu_mem_cb_t = ::std::option::Option< 264 | unsafe extern "C" fn( 265 | vcpu_index: ::std::os::raw::c_uint, 266 | info: qemu_plugin_meminfo_t, 267 | vaddr: u64, 268 | userdata: *mut ::std::os::raw::c_void, 269 | ), 270 | >; 271 | unsafe extern "C" { 272 | pub fn qemu_plugin_register_vcpu_mem_cb( 273 | insn: *mut qemu_plugin_insn, 274 | cb: qemu_plugin_vcpu_mem_cb_t, 275 | flags: qemu_plugin_cb_flags, 276 | rw: qemu_plugin_mem_rw, 277 | userdata: *mut ::std::os::raw::c_void, 278 | ); 279 | } 280 | unsafe extern "C" { 281 | pub fn qemu_plugin_register_vcpu_mem_inline( 282 | insn: *mut qemu_plugin_insn, 283 | rw: qemu_plugin_mem_rw, 284 | op: qemu_plugin_op, 285 | ptr: *mut ::std::os::raw::c_void, 286 | imm: u64, 287 | ); 288 | } 289 | pub type qemu_plugin_vcpu_syscall_cb_t = ::std::option::Option< 290 | unsafe extern "C" fn( 291 | id: qemu_plugin_id_t, 292 | vcpu_index: ::std::os::raw::c_uint, 293 | num: i64, 294 | a1: u64, 295 | a2: u64, 296 | a3: u64, 297 | a4: u64, 298 | a5: u64, 299 | a6: u64, 300 | a7: u64, 301 | a8: u64, 302 | ), 303 | >; 304 | unsafe extern "C" { 305 | pub fn qemu_plugin_register_vcpu_syscall_cb( 306 | id: qemu_plugin_id_t, 307 | cb: qemu_plugin_vcpu_syscall_cb_t, 308 | ); 309 | } 310 | pub type qemu_plugin_vcpu_syscall_ret_cb_t = ::std::option::Option< 311 | unsafe extern "C" fn( 312 | id: qemu_plugin_id_t, 313 | vcpu_idx: ::std::os::raw::c_uint, 314 | num: i64, 315 | ret: i64, 316 | ), 317 | >; 318 | unsafe extern "C" { 319 | pub fn qemu_plugin_register_vcpu_syscall_ret_cb( 320 | id: qemu_plugin_id_t, 321 | cb: qemu_plugin_vcpu_syscall_ret_cb_t, 322 | ); 323 | } 324 | unsafe extern "C" { 325 | #[doc = " qemu_plugin_insn_disas() - return disassembly string for instruction\n @insn: instruction reference\n\n Returns an allocated string containing the disassembly"] 326 | pub fn qemu_plugin_insn_disas(insn: *const qemu_plugin_insn) -> *mut ::std::os::raw::c_char; 327 | } 328 | unsafe extern "C" { 329 | #[doc = " qemu_plugin_vcpu_for_each() - iterate over the existing vCPU\n @id: plugin ID\n @cb: callback function\n\n The @cb function is called once for each existing vCPU.\n\n See also: qemu_plugin_register_vcpu_init_cb()"] 330 | pub fn qemu_plugin_vcpu_for_each(id: qemu_plugin_id_t, cb: qemu_plugin_vcpu_simple_cb_t); 331 | } 332 | unsafe extern "C" { 333 | pub fn qemu_plugin_register_flush_cb(id: qemu_plugin_id_t, cb: qemu_plugin_simple_cb_t); 334 | } 335 | unsafe extern "C" { 336 | pub fn qemu_plugin_register_atexit_cb( 337 | id: qemu_plugin_id_t, 338 | cb: qemu_plugin_udata_cb_t, 339 | userdata: *mut ::std::os::raw::c_void, 340 | ); 341 | } 342 | unsafe extern "C" { 343 | #[doc = " returns -1 in user-mode"] 344 | pub fn qemu_plugin_n_vcpus() -> ::std::os::raw::c_int; 345 | } 346 | unsafe extern "C" { 347 | #[doc = " returns -1 in user-mode"] 348 | pub fn qemu_plugin_n_max_vcpus() -> ::std::os::raw::c_int; 349 | } 350 | unsafe extern "C" { 351 | #[doc = " qemu_plugin_outs() - output string via QEMU's logging system\n @string: a string"] 352 | pub fn qemu_plugin_outs(string: *const ::std::os::raw::c_char); 353 | } 354 | -------------------------------------------------------------------------------- /qemu-plugin/src/plugin/mod.rs: -------------------------------------------------------------------------------- 1 | //! Traits and helpers enabling idiomatic QEMU plugin implementation 2 | 3 | use std::sync::{Mutex, OnceLock}; 4 | 5 | use crate::{ 6 | Args, Error, Info, PluginId, Result, TranslationBlock, VCPUIndex, 7 | qemu_plugin_register_atexit_cb, qemu_plugin_register_flush_cb, 8 | qemu_plugin_register_vcpu_exit_cb, qemu_plugin_register_vcpu_idle_cb, 9 | qemu_plugin_register_vcpu_init_cb, qemu_plugin_register_vcpu_resume_cb, 10 | qemu_plugin_register_vcpu_syscall_cb, qemu_plugin_register_vcpu_syscall_ret_cb, 11 | qemu_plugin_register_vcpu_tb_trans_cb, 12 | }; 13 | 14 | /// Handler for callbacks registered via the `qemu_plugin_register_vcpu_init_cb` 15 | /// function. These callbacks are called when a vCPU is initialized in QEMU (in softmmu 16 | /// mode only) and notify us which vCPU index is newly initialized. 17 | extern "C" fn handle_qemu_plugin_register_vcpu_init_cb(id: PluginId, vcpu_id: VCPUIndex) { 18 | let Some(plugin) = PLUGIN.get() else { 19 | panic!("Plugin not set"); 20 | }; 21 | 22 | let Ok(mut plugin) = plugin.lock() else { 23 | panic!("Failed to lock plugin"); 24 | }; 25 | 26 | plugin 27 | .on_vcpu_init(id, vcpu_id) 28 | .expect("Failed running callback on_vcpu_init"); 29 | } 30 | 31 | /// Handler for callbacks registered via the `qemu_plugin_register_vcpu_exit_cb` 32 | /// function. These callbacks are called when a vCPU exits in QEMU (in softmmu mode 33 | /// only) and notify us which vCPU index is exiting. 34 | extern "C" fn handle_qemu_plugin_register_vcpu_exit_cb(id: PluginId, vcpu_id: VCPUIndex) { 35 | let Some(plugin) = PLUGIN.get() else { 36 | panic!("Plugin not set"); 37 | }; 38 | 39 | let Ok(mut plugin) = plugin.lock() else { 40 | panic!("Failed to lock plugin"); 41 | }; 42 | 43 | plugin 44 | .on_vcpu_exit(id, vcpu_id) 45 | .expect("Failed running callback on_vcpu_exit"); 46 | } 47 | 48 | /// Handler for callbacks registered via the `qemu_plugin_register_vcpu_idle_cb` 49 | /// function. These callbacks are called when a vCPU goes idle in QEMU (in softmmu mode 50 | /// only) and notify us which vCPU index is going idle. 51 | extern "C" fn handle_qemu_plugin_register_vcpu_idle_cb(id: PluginId, vcpu_id: VCPUIndex) { 52 | let Some(plugin) = PLUGIN.get() else { 53 | panic!("Plugin not set"); 54 | }; 55 | 56 | let Ok(mut plugin) = plugin.lock() else { 57 | panic!("Failed to lock plugin"); 58 | }; 59 | 60 | plugin 61 | .on_vcpu_idle(id, vcpu_id) 62 | .expect("Failed running callback on_vcpu_idle"); 63 | } 64 | 65 | /// Handler for callbacks registered via the `qemu_plugin_register_vcpu_resume_cb` 66 | /// function. These callbacks are called when a vCPU resumes in QEMU (in softmmu mode 67 | /// only) and notify us which vCPU index is resuming. 68 | extern "C" fn handle_qemu_plugin_register_vcpu_resume_cb(id: PluginId, vcpu_id: VCPUIndex) { 69 | let Some(plugin) = PLUGIN.get() else { 70 | panic!("Plugin not set"); 71 | }; 72 | 73 | let Ok(mut plugin) = plugin.lock() else { 74 | panic!("Failed to lock plugin"); 75 | }; 76 | 77 | plugin 78 | .on_vcpu_resume(id, vcpu_id) 79 | .expect("Failed running callback on_vcpu_resume"); 80 | } 81 | 82 | /// Handler for callbacks registered via the `qemu_plugin_register_vcpu_tb_trans_cb` 83 | /// function. These callbacks are called when a translation block is translated in QEMU 84 | /// and pass an opaque pointer to the translation block. 85 | extern "C" fn handle_qemu_plugin_register_vcpu_tb_trans_cb( 86 | id: PluginId, 87 | tb: *mut crate::sys::qemu_plugin_tb, 88 | ) { 89 | let Some(plugin) = PLUGIN.get() else { 90 | panic!("Plugin not set"); 91 | }; 92 | 93 | let Ok(mut plugin) = plugin.lock() else { 94 | panic!("Failed to lock plugin"); 95 | }; 96 | 97 | let tb = TranslationBlock::from(tb); 98 | 99 | plugin 100 | .on_translation_block_translate(id, tb) 101 | .expect("Failed running callback on_translation_block_translate"); 102 | } 103 | 104 | /// Handler for callbacks registered via the `qemu_plugin_register_flush_cb` 105 | /// function. These callbacks are called when QEMU flushes all TBs, which is 106 | /// roughly equivalent to a TLB flush to invalidate all cached instructions. 107 | extern "C" fn handle_qemu_plugin_register_flush_cb(id: PluginId) { 108 | let Some(plugin) = PLUGIN.get() else { 109 | panic!("Plugin not set"); 110 | }; 111 | 112 | let Ok(mut plugin) = plugin.lock() else { 113 | panic!("Failed to lock plugin"); 114 | }; 115 | 116 | plugin 117 | .on_flush(id) 118 | .expect("Failed running callback on_flush"); 119 | } 120 | 121 | /// Handler for callbacks registered via the `qemu_plugin_register_atexit_cb` 122 | /// function. These callbacks are called when execution has finished and plugins 123 | /// should free their resources. 124 | extern "C" fn handle_qemu_plugin_register_atexit_cb(id: PluginId, _udata: *mut std::ffi::c_void) { 125 | // We don't actually need the userdata here, and we pass std::ptr::null_mut() when registering. 126 | let Some(plugin) = PLUGIN.get() else { 127 | panic!("Plugin not set"); 128 | }; 129 | 130 | let Ok(mut plugin) = plugin.lock() else { 131 | panic!("Failed to lock plugin"); 132 | }; 133 | 134 | plugin.on_exit(id).expect("Failed running callback on_exit"); 135 | } 136 | 137 | /// Handler for callbacks registered via the `qemu_plugin_register_vcpu_syscall_cb` 138 | /// function. These callbacks are called when a syscall is made in QEMU and pass the 139 | /// syscall number and its arguments. 140 | extern "C" fn handle_qemu_plugin_register_syscall_cb( 141 | id: PluginId, 142 | vcpu_index: VCPUIndex, 143 | num: i64, 144 | a1: u64, 145 | a2: u64, 146 | a3: u64, 147 | a4: u64, 148 | a5: u64, 149 | a6: u64, 150 | a7: u64, 151 | a8: u64, 152 | ) { 153 | let Some(plugin) = PLUGIN.get() else { 154 | panic!("Plugin not set"); 155 | }; 156 | 157 | let Ok(mut plugin) = plugin.lock() else { 158 | panic!("Failed to lock plugin"); 159 | }; 160 | 161 | plugin 162 | .on_syscall(id, vcpu_index, num, a1, a2, a3, a4, a5, a6, a7, a8) 163 | .expect("Failed running callback on_syscall"); 164 | } 165 | 166 | /// Handler for callbacks registered via the `qemu_plugin_register_vcpu_syscall_ret_cb` 167 | /// function. These callbacks are called when a syscall returns in QEMU and pass the 168 | /// syscall number and its return value. 169 | extern "C" fn handle_qemu_plugin_register_syscall_ret_cb( 170 | id: PluginId, 171 | vcpu_index: VCPUIndex, 172 | num: i64, 173 | ret: i64, 174 | ) { 175 | let Some(plugin) = PLUGIN.get() else { 176 | panic!("Plugin not set"); 177 | }; 178 | 179 | let Ok(mut plugin) = plugin.lock() else { 180 | panic!("Failed to lock plugin"); 181 | }; 182 | 183 | plugin 184 | .on_syscall_return(id, vcpu_index, num, ret) 185 | .expect("Failed running callback on_syscall_return"); 186 | } 187 | 188 | /// Trait which implemenents registering the callbacks implemented on a struct which 189 | /// `HasCallbacks` with QEMU 190 | /// 191 | /// # Example 192 | /// 193 | /// Using default registration, you can simply declare an empty `impl` block for 194 | /// `Register` on your type. Then, on plugin load, any callbacks you implement in 195 | /// `HasCallbacks` will be automatically registered with QEMU. Callback events you don't 196 | /// implement will default to no-ops. The only drawback of this approach is a small 197 | /// performance penalty for events even when there is no callback registered for them. 198 | /// 199 | /// ``` 200 | /// struct MyPlugin; 201 | /// 202 | /// impl qemu_plugin::plugin::HasCallbacks for MyPlugin { 203 | /// fn on_translation_block_translate(&mut self, _: qemu_plugin::PluginId, tb: qemu_plugin::TranslationBlock) -> qemu_plugin::Result<()> { 204 | /// println!("Translation block translated"); 205 | /// Ok(()) 206 | /// } 207 | /// } 208 | /// 209 | /// impl qemu_plugin::plugin::Register for MyPlugin {} 210 | /// ``` 211 | /// 212 | /// For more granular control or to register your own callback handlers, you can 213 | /// implement the `register` method yourself. 214 | /// 215 | /// ``` 216 | /// struct MyPlugin; 217 | /// 218 | /// impl qemu_plugin::plugin::HasCallbacks for MyPlugin {} 219 | /// impl qemu_plugin::plugin::Register for MyPlugin { 220 | /// fn register(&mut self, id: qemu_plugin::PluginId, args: &qemu_plugin::install::Args, info: &qemu_plugin::install::Info) -> Result<(), qemu_plugin::Error> { 221 | /// // Custom registration logic here 222 | /// Ok(()) 223 | /// } 224 | /// } 225 | /// ``` 226 | /// 227 | /// Finally, if you want to override the default registration behavior, you can 228 | /// implement `register_default` yourself. This allows you to circumvent any minor 229 | /// performance penalties. 230 | /// 231 | /// ``` 232 | /// struct MyPlugin; 233 | /// 234 | /// impl qemu_plugin::plugin::HasCallbacks for MyPlugin {} 235 | /// impl qemu_plugin::plugin::Register for MyPlugin { 236 | /// fn register_default( 237 | /// &mut self, 238 | /// id: qemu_plugin::PluginId, 239 | /// args: &qemu_plugin::install::Args, 240 | /// info: &qemu_plugin::install::Info, 241 | /// ) -> Result<()> { 242 | /// // Custom registration logic here, maybe registering a different 243 | /// // function as a callback rather than using `HasCallbacks` 244 | /// Ok(()) 245 | /// } 246 | /// } 247 | /// ``` 248 | /// 249 | pub trait Register: HasCallbacks + Send + Sync + 'static { 250 | #[allow(unused)] 251 | /// Called by QEMU when registering the plugin. This method should only be overridden if no 252 | /// default callbacks are desired, and will require re-implementing handlers which is not 253 | /// recommended. 254 | fn register_default(&mut self, id: PluginId, args: &Args, info: &Info) -> Result<()> { 255 | qemu_plugin_register_vcpu_init_cb(id, Some(handle_qemu_plugin_register_vcpu_init_cb))?; 256 | 257 | qemu_plugin_register_vcpu_exit_cb(id, Some(handle_qemu_plugin_register_vcpu_exit_cb))?; 258 | 259 | qemu_plugin_register_vcpu_idle_cb(id, Some(handle_qemu_plugin_register_vcpu_idle_cb))?; 260 | 261 | qemu_plugin_register_vcpu_resume_cb(id, Some(handle_qemu_plugin_register_vcpu_resume_cb))?; 262 | 263 | qemu_plugin_register_vcpu_tb_trans_cb( 264 | id, 265 | Some(handle_qemu_plugin_register_vcpu_tb_trans_cb), 266 | )?; 267 | 268 | qemu_plugin_register_flush_cb(id, Some(handle_qemu_plugin_register_flush_cb)); 269 | 270 | qemu_plugin_register_atexit_cb(id, |id| { 271 | handle_qemu_plugin_register_atexit_cb(id, std::ptr::null_mut()); 272 | }); 273 | 274 | qemu_plugin_register_vcpu_syscall_cb(id, Some(handle_qemu_plugin_register_syscall_cb)); 275 | 276 | qemu_plugin_register_vcpu_syscall_ret_cb( 277 | id, 278 | Some(handle_qemu_plugin_register_syscall_ret_cb), 279 | ); 280 | 281 | self.register(id, args, info)?; 282 | 283 | Ok(()) 284 | } 285 | 286 | #[allow(unused)] 287 | /// Called when registering the plugin. User definition of on-registration behavior should 288 | /// be implemented here. 289 | fn register(&mut self, id: PluginId, args: &Args, info: &Info) -> Result<()> { 290 | Ok(()) 291 | } 292 | } 293 | 294 | /// Trait implemented by structs which have callbacks which should be registered with QEMU. 295 | /// 296 | /// # Example 297 | /// 298 | /// ``` 299 | /// struct MyPlugin; 300 | /// 301 | /// impl qemu_plugin::plugin::HasCallbacks for MyPlugin { 302 | /// // This callback will be registered on plugin load 303 | /// fn on_translation_block_translate(&mut self, _: qemu_plugin::PluginId, tb: qemu_plugin::TranslationBlock) -> qemu_plugin::Result<()> { 304 | /// println!("Translation block translated"); 305 | /// Ok(()) 306 | /// } 307 | /// } 308 | /// 309 | /// impl qemu_plugin::plugin::Register for MyPlugin {} 310 | /// ``` 311 | pub trait HasCallbacks: Send + Sync + 'static { 312 | #[allow(unused)] 313 | /// Callback triggered on vCPU init 314 | /// 315 | /// # Arguments 316 | /// 317 | /// * `id` - The ID of the plugin 318 | /// * `vcpu_id` - The ID of the vCPU 319 | /// 320 | /// # Example 321 | /// 322 | /// ``` 323 | /// struct MyPlugin; 324 | /// 325 | /// impl qemu_plugin::plugin::HasCallbacks for MyPlugin { 326 | /// fn on_vcpu_init(&mut self, id: qemu_plugin::PluginId, vcpu_id: qemu_plugin::VCPUIndex) -> Result<(), qemu_plugin::Error> { 327 | /// println!("vCPU {} initialized for plugin {}", vcpu_id, id); 328 | /// Ok(()) 329 | /// } 330 | /// } 331 | /// ``` 332 | /// struct MyPlugin; 333 | fn on_vcpu_init(&mut self, id: PluginId, vcpu_id: VCPUIndex) -> Result<()> { 334 | Ok(()) 335 | } 336 | 337 | #[allow(unused)] 338 | /// Callback triggered on vCPU exit 339 | /// 340 | /// # Arguments 341 | /// 342 | /// * `id` - The ID of the plugin 343 | /// * `vcpu_id` - The ID of the vCPU 344 | /// 345 | /// # Example 346 | fn on_vcpu_exit(&mut self, id: PluginId, vcpu_id: VCPUIndex) -> Result<()> { 347 | Ok(()) 348 | } 349 | 350 | #[allow(unused)] 351 | /// Callback triggered on vCPU idle 352 | /// 353 | /// # Arguments 354 | /// 355 | /// * `id` - The ID of the plugin 356 | /// * `vcpu_id` - The ID of the vCPU 357 | fn on_vcpu_idle(&mut self, id: PluginId, vcpu_id: VCPUIndex) -> Result<()> { 358 | Ok(()) 359 | } 360 | 361 | #[allow(unused)] 362 | /// Callback triggered on vCPU resume 363 | /// 364 | /// # Arguments 365 | /// 366 | /// * `id` - The ID of the plugin 367 | /// * `vcpu_id` - The ID of the vCPU 368 | fn on_vcpu_resume(&mut self, id: PluginId, vcpu_id: VCPUIndex) -> Result<()> { 369 | Ok(()) 370 | } 371 | 372 | #[allow(unused)] 373 | /// Callback triggered on translation block translation 374 | /// 375 | /// # Arguments 376 | /// 377 | /// * `id` - The ID of the plugin 378 | /// * `tb` - The translation block 379 | fn on_translation_block_translate(&mut self, id: PluginId, tb: TranslationBlock) -> Result<()> { 380 | Ok(()) 381 | } 382 | 383 | #[allow(unused)] 384 | /// Callback triggered on flush 385 | /// 386 | /// # Arguments 387 | /// 388 | /// * `id` - The ID of the plugin 389 | fn on_flush(&mut self, id: PluginId) -> Result<()> { 390 | Ok(()) 391 | } 392 | 393 | #[allow(unused)] 394 | /// Callback triggered on exit 395 | /// 396 | /// # Arguments 397 | /// 398 | /// * `id` - The ID of the plugin 399 | fn on_exit(&mut self, id: PluginId) -> Result<()> { 400 | Ok(()) 401 | } 402 | 403 | #[allow(unused, clippy::too_many_arguments)] 404 | /// Callback triggered on syscall 405 | /// 406 | /// # Arguments 407 | /// 408 | /// * `id` - The ID of the plugin 409 | /// * `vcpu_index` - The ID of the vCPU 410 | /// * `num` - The syscall number 411 | /// * `a1` - The first syscall argument 412 | /// * `a2` - The second syscall argument 413 | /// * `a3` - The third syscall argument 414 | /// * `a4` - The fourth syscall argument 415 | /// * `a5` - The fifth syscall argument 416 | /// * `a6` - The sixth syscall argument 417 | /// * `a7` - The seventh syscall argument 418 | /// * `a8` - The eighth syscall argument 419 | fn on_syscall( 420 | &mut self, 421 | id: PluginId, 422 | vcpu_index: VCPUIndex, 423 | num: i64, 424 | a1: u64, 425 | a2: u64, 426 | a3: u64, 427 | a4: u64, 428 | a5: u64, 429 | a6: u64, 430 | a7: u64, 431 | a8: u64, 432 | ) -> Result<()> { 433 | Ok(()) 434 | } 435 | 436 | #[allow(unused)] 437 | /// Callback triggered on syscall return 438 | /// 439 | /// # Arguments 440 | /// 441 | /// * `id` - The ID of the plugin 442 | /// * `vcpu_index` - The ID of the vCPU 443 | /// * `num` - The syscall number 444 | /// * `ret` - The return value of the syscall 445 | fn on_syscall_return( 446 | &mut self, 447 | id: PluginId, 448 | vcpu_index: VCPUIndex, 449 | num: i64, 450 | ret: i64, 451 | ) -> Result<()> { 452 | Ok(()) 453 | } 454 | } 455 | 456 | /// Trait implemented by structs which are QEMU plugin contexts 457 | pub trait Plugin: Register + HasCallbacks {} 458 | 459 | impl Plugin for T where T: Register + HasCallbacks {} 460 | 461 | #[doc(hidden)] 462 | /// The global plugin item 463 | pub static PLUGIN: OnceLock>> = OnceLock::new(); 464 | 465 | #[doc(hidden)] 466 | #[inline(never)] 467 | pub fn register_plugin(plugin: impl Plugin) { 468 | PLUGIN 469 | .set(Mutex::new(Box::new(plugin))) 470 | .map_err(|_| Error::PluginInstanceSetError) 471 | .expect("Failed to set plugin"); 472 | } 473 | 474 | #[macro_export] 475 | /// Register a plugin 476 | macro_rules! register { 477 | ($plugin:expr) => { 478 | #[cfg_attr(target_os = "linux", unsafe(link_section = ".text.startup"))] 479 | extern "C" fn __plugin_ctor() { 480 | $crate::plugin::register_plugin($plugin); 481 | } 482 | 483 | #[used] 484 | // .init_array.XXXXX sections are processed in lexicographical order 485 | #[cfg_attr(target_os = "linux", unsafe(link_section = ".init_array"))] 486 | // But there is no way to specify such an ordering on MacOS, even with 487 | // __TEXT,__init_offsets 488 | #[cfg_attr( 489 | target_os = "macos", 490 | unsafe(link_section = "__DATA,__mod_init_func,mod_init_funcs") 491 | )] 492 | // On Windows, it's from .CRT$XCA to .CRT$XCZ, where usually XCU = 493 | // early, XCT = middle, XCL = late 494 | #[cfg_attr(windows, unsafe(link_section = ".CRT$XCU"))] 495 | static __PLUGIN_CTOR: unsafe extern "C" fn() = __plugin_ctor; 496 | }; 497 | } 498 | -------------------------------------------------------------------------------- /plugins/tracer/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | #[cfg(not(any( 3 | feature = "plugin-api-v0", 4 | feature = "plugin-api-v1", 5 | feature = "plugin-api-v2", 6 | feature = "plugin-api-v3" 7 | )))] 8 | use qemu_plugin::qemu_plugin_read_memory_vaddr; 9 | use qemu_plugin::{ 10 | Args, Error, HasCallbacks, Info, Instruction, MemRW, MemoryInfo, PluginId, Register, Result, 11 | TranslationBlock, VCPUIndex, Value, register, 12 | }; 13 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 14 | use qemu_plugin::{RegisterDescriptor, qemu_plugin_get_registers}; 15 | use serde_cbor::to_writer; 16 | use std::{ 17 | collections::HashMap, 18 | os::unix::net::UnixStream, 19 | path::PathBuf, 20 | sync::{Arc, Mutex}, 21 | }; 22 | use tracer_events::{Event, InstructionEvent, MemoryEvent, SyscallEvent, SyscallSource}; 23 | use typed_builder::TypedBuilder; 24 | use yaxpeax_x86::amd64::InstDecoder; 25 | 26 | trait FromInstruction { 27 | fn from_instruction(ins: &Instruction) -> Result 28 | where 29 | Self: Sized; 30 | } 31 | 32 | impl FromInstruction for InstructionEvent { 33 | fn from_instruction(value: &Instruction) -> Result { 34 | let data = value.data(); 35 | let decoder = InstDecoder::default(); 36 | let disas = decoder 37 | .decode_slice(&data) 38 | .map(|d| d.to_string()) 39 | .or_else(|_| value.disas())?; 40 | 41 | Ok(Self::builder() 42 | .vaddr(value.vaddr()) 43 | .haddr(value.haddr()) 44 | .disas(disas) 45 | .symbol(value.symbol()?) 46 | .data(data) 47 | .build()) 48 | } 49 | } 50 | 51 | trait FromMemoryInfoVaddr { 52 | fn from_memory_info_vaddr(info: &MemoryInfo, vaddr: u64) -> Result 53 | where 54 | Self: Sized; 55 | } 56 | 57 | impl FromMemoryInfoVaddr for MemoryEvent { 58 | fn from_memory_info_vaddr(value: &MemoryInfo, vaddr: u64) -> Result { 59 | let haddr = value.hwaddr(vaddr); 60 | Ok(Self::builder() 61 | .vaddr(vaddr) 62 | .haddr(haddr.as_ref().map(|h| h.hwaddr())) 63 | .haddr_is_io(haddr.as_ref().map(|h| h.is_io())) 64 | .haddr_device_name(haddr.and_then(|h| h.device_name().ok().flatten())) 65 | .size_shift(value.size_shift()) 66 | .size_bytes(match value.size_shift() { 67 | 0 => 1, 68 | 1 => 2, 69 | 2 => 4, 70 | 3 => 8, 71 | _ => 0, 72 | }) 73 | .sign_extended(value.sign_extended()) 74 | .is_store(value.is_store()) 75 | .big_endian(value.big_endian()) 76 | .build()) 77 | } 78 | } 79 | 80 | #[derive(TypedBuilder, Clone, Debug)] 81 | struct Tracer { 82 | #[builder(default)] 83 | pub target_name: Option, 84 | pub syscalls: Arc>>, 85 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 86 | pub registers: Arc>>>, 87 | #[builder(default)] 88 | pub tx: Arc>>, 89 | #[builder(default)] 90 | pub log_insns: bool, 91 | #[builder(default)] 92 | pub log_mem: bool, 93 | #[builder(default)] 94 | pub log_syscalls: bool, 95 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 96 | #[builder(default)] 97 | pub log_registers: bool, 98 | } 99 | 100 | impl Tracer { 101 | pub fn new() -> Self { 102 | #[cfg(any(feature = "plugin-api-v0", feature = "plugin-api-v1"))] 103 | { 104 | Self::builder() 105 | .syscalls(Arc::new(Mutex::new(HashMap::new()))) 106 | .build() 107 | } 108 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 109 | { 110 | Self::builder() 111 | .syscalls(Arc::new(Mutex::new(HashMap::new()))) 112 | .registers(Arc::new(Mutex::new(Vec::new()))) 113 | .build() 114 | } 115 | } 116 | } 117 | 118 | impl HasCallbacks for Tracer { 119 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 120 | fn on_vcpu_init(&mut self, _id: PluginId, _vcpu_id: VCPUIndex) -> Result<()> { 121 | *self 122 | .registers 123 | .lock() 124 | .map_err(|e| anyhow!("Failed to lock registers: {}", e))? = 125 | qemu_plugin_get_registers()?; 126 | Ok(()) 127 | } 128 | 129 | fn on_translation_block_translate( 130 | &mut self, 131 | _id: PluginId, 132 | tb: TranslationBlock, 133 | ) -> Result<()> { 134 | tb.instructions().try_for_each(|insn| { 135 | let event = InstructionEvent::from_instruction(&insn)?; 136 | 137 | #[cfg(any(feature = "plugin-api-v0", feature = "plugin-api-v1"))] 138 | if self.log_insns { 139 | let tx = self.tx.clone(); 140 | 141 | insn.register_execute_callback(move |_| { 142 | tx.lock() 143 | .map_err(|e| anyhow!("Failed to lock tx: {}", e)) 144 | .and_then(|tx| { 145 | to_writer( 146 | tx.as_ref().ok_or_else(|| anyhow!("No tx"))?, 147 | &Event::Instruction { 148 | event: event.clone(), 149 | registers: Default::default(), 150 | }, 151 | ) 152 | .map_err(|e| anyhow!(e)) 153 | }) 154 | .expect("Failed to send instruction event"); 155 | }); 156 | } 157 | 158 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 159 | if self.log_insns { 160 | use qemu_plugin::CallbackFlags; 161 | 162 | let tx = self.tx.clone(); 163 | let registers = self 164 | .registers 165 | .lock() 166 | .map_err(|e| anyhow!("Failed to lock registers: {}", e))? 167 | .clone(); 168 | 169 | insn.register_execute_callback_flags( 170 | move |_| { 171 | tx.lock() 172 | .map_err(|e| anyhow!("Failed to lock tx: {}", e)) 173 | .and_then(|tx| { 174 | use tracer_events::{Event, Registers}; 175 | 176 | to_writer( 177 | tx.as_ref().ok_or_else(|| anyhow!("No tx"))?, 178 | &Event::Instruction { 179 | event: event.clone(), 180 | registers: Registers( 181 | registers 182 | .iter() 183 | .map(|r| { 184 | let value = r.read().unwrap_or_else(|_| vec![]); 185 | (r.name.clone(), value) 186 | }) 187 | .collect(), 188 | ), 189 | }, 190 | ) 191 | .map_err(|e| anyhow!(e)) 192 | }) 193 | .expect("Failed to send instruction event"); 194 | }, 195 | CallbackFlags::QEMU_PLUGIN_CB_R_REGS, 196 | ); 197 | } 198 | 199 | if self.log_mem { 200 | let tx = self.tx.clone(); 201 | 202 | insn.register_memory_access_callback( 203 | move |_, info, vaddr| { 204 | tx.lock() 205 | .map_err(|e| anyhow!("Failed to lock tx: {}", e)) 206 | .and_then(|tx| { 207 | to_writer( 208 | tx.as_ref().ok_or_else(|| anyhow!("No tx"))?, 209 | &Event::Memory(MemoryEvent::from_memory_info_vaddr( 210 | &info, vaddr, 211 | )?), 212 | ) 213 | .map_err(|e| anyhow!(e)) 214 | }) 215 | .expect("Failed to send memory event"); 216 | }, 217 | MemRW::QEMU_PLUGIN_MEM_RW, 218 | ); 219 | } 220 | 221 | Ok::<(), Error>(()) 222 | })?; 223 | 224 | Ok(()) 225 | } 226 | 227 | fn on_syscall( 228 | &mut self, 229 | id: PluginId, 230 | vcpu_index: VCPUIndex, 231 | num: i64, 232 | a1: u64, 233 | a2: u64, 234 | a3: u64, 235 | a4: u64, 236 | a5: u64, 237 | a6: u64, 238 | a7: u64, 239 | a8: u64, 240 | ) -> Result<()> { 241 | if !self.log_syscalls { 242 | return Ok(()); 243 | } 244 | 245 | #[cfg(any( 246 | feature = "plugin-api-v0", 247 | feature = "plugin-api-v1", 248 | feature = "plugin-api-v2", 249 | feature = "plugin-api-v3" 250 | ))] 251 | let event = SyscallEvent::builder() 252 | .num(num) 253 | .return_value(-1) 254 | .args([a1, a2, a3, a4, a5, a6, a7, a8]) 255 | .build(); 256 | 257 | #[cfg(not(any( 258 | feature = "plugin-api-v0", 259 | feature = "plugin-api-v1", 260 | feature = "plugin-api-v2", 261 | feature = "plugin-api-v3" 262 | )))] 263 | let event = { 264 | let buffers = if let Some(write_sysno) = match self.target_name.as_deref() { 265 | Some("i386") => Some(4), 266 | Some("x86_64") => Some(1), 267 | Some("arm") => Some(4), 268 | Some("aarch64") => Some(64), 269 | _ => None, 270 | } { 271 | if num == write_sysno { 272 | let addr = a2; 273 | let len = a3 as usize; 274 | let mut buffer = vec![0; len]; 275 | qemu_plugin_read_memory_vaddr(addr, &mut buffer)?; 276 | [(1, buffer)].into_iter().collect::>() 277 | } else { 278 | Default::default() 279 | } 280 | } else { 281 | Default::default() 282 | }; 283 | 284 | SyscallEvent::builder() 285 | .num(num) 286 | .return_value(-1) 287 | .args([a1, a2, a3, a4, a5, a6, a7, a8]) 288 | .buffers(buffers) 289 | .build() 290 | }; 291 | 292 | let mut syscalls = self 293 | .syscalls 294 | .lock() 295 | .map_err(|e| anyhow!("Failed to lock syscalls: {e}"))?; 296 | 297 | syscalls.insert( 298 | SyscallSource::builder() 299 | .plugin_id(id) 300 | .vcpu_index(vcpu_index) 301 | .build(), 302 | event, 303 | ); 304 | 305 | Ok(()) 306 | } 307 | 308 | #[allow(unused_variables)] 309 | fn on_syscall_return( 310 | &mut self, 311 | id: PluginId, 312 | vcpu_index: VCPUIndex, 313 | num: i64, 314 | ret: i64, 315 | ) -> Result<()> { 316 | if !self.log_syscalls { 317 | return Ok(()); 318 | } 319 | 320 | let mut syscalls = self 321 | .syscalls 322 | .lock() 323 | .map_err(|e| anyhow!("Failed to lock syscalls: {e}"))?; 324 | 325 | // Remove and return the syscall event 326 | let mut event = syscalls 327 | .remove( 328 | &SyscallSource::builder() 329 | .plugin_id(id) 330 | .vcpu_index(vcpu_index) 331 | .build(), 332 | ) 333 | .ok_or_else(|| anyhow!("No syscall event found"))?; 334 | 335 | #[cfg(not(any( 336 | feature = "plugin-api-v0", 337 | feature = "plugin-api-v1", 338 | feature = "plugin-api-v2", 339 | feature = "plugin-api-v3" 340 | )))] 341 | { 342 | if let Some(read_sysno) = match self.target_name.as_deref() { 343 | Some("i386") => Some(3), 344 | Some("x86_64") => Some(0), 345 | Some("arm") => Some(3), 346 | Some("aarch64") => Some(63), 347 | _ => None, 348 | } && num == read_sysno 349 | { 350 | let addr = event.args[1]; 351 | let len = event.args[2] as usize; 352 | let mut buffer = vec![0; len]; 353 | qemu_plugin_read_memory_vaddr(addr, &mut buffer)?; 354 | event.buffers.insert(1, buffer); 355 | } 356 | } 357 | 358 | // Update the return value 359 | event.return_value = ret; 360 | 361 | // Send the event 362 | let tx = self 363 | .tx 364 | .lock() 365 | .map_err(|e| anyhow!("Failed to lock tx: {e}"))?; 366 | let tx_stream = tx.as_ref().ok_or_else(|| anyhow!("No tx"))?; 367 | 368 | to_writer(tx_stream, &Event::Syscall(event)).map_err(|e| anyhow!(e))?; 369 | 370 | Ok(()) 371 | } 372 | } 373 | 374 | #[derive(TypedBuilder, Clone, Debug)] 375 | pub struct PluginArgs { 376 | pub log_insns: bool, 377 | pub log_mem: bool, 378 | pub log_syscalls: bool, 379 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 380 | pub log_registers: bool, 381 | pub socket_path: PathBuf, 382 | } 383 | 384 | impl TryFrom<&Args> for PluginArgs { 385 | type Error = Error; 386 | 387 | fn try_from(value: &Args) -> Result { 388 | #[cfg(any(feature = "plugin-api-v0", feature = "plugin-api-v1"))] 389 | { 390 | Ok(Self::builder() 391 | .log_insns( 392 | value 393 | .parsed 394 | .get("log_insns") 395 | .map(|li| if let Value::Bool(v) = li { *v } else { false }) 396 | .unwrap_or_default(), 397 | ) 398 | .log_mem( 399 | value 400 | .parsed 401 | .get("log_mem") 402 | .map(|lm| if let Value::Bool(v) = lm { *v } else { false }) 403 | .unwrap_or_default(), 404 | ) 405 | .log_syscalls( 406 | value 407 | .parsed 408 | .get("log_syscalls") 409 | .map(|ls| if let Value::Bool(v) = ls { *v } else { false }) 410 | .unwrap_or_default(), 411 | ) 412 | .socket_path( 413 | value 414 | .parsed 415 | .get("socket_path") 416 | .and_then(|sp| { 417 | if let Value::String(v) = sp { 418 | Some(PathBuf::from(v)) 419 | } else { 420 | None 421 | } 422 | }) 423 | .ok_or_else(|| anyhow!("No socket path provided"))?, 424 | ) 425 | .build()) 426 | } 427 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 428 | { 429 | Ok(Self::builder() 430 | .log_insns( 431 | value 432 | .parsed 433 | .get("log_insns") 434 | .map(|li| if let Value::Bool(v) = li { *v } else { false }) 435 | .unwrap_or_default(), 436 | ) 437 | .log_mem( 438 | value 439 | .parsed 440 | .get("log_mem") 441 | .map(|lm| if let Value::Bool(v) = lm { *v } else { false }) 442 | .unwrap_or_default(), 443 | ) 444 | .log_syscalls( 445 | value 446 | .parsed 447 | .get("log_syscalls") 448 | .map(|ls| if let Value::Bool(v) = ls { *v } else { false }) 449 | .unwrap_or_default(), 450 | ) 451 | .log_registers( 452 | value 453 | .parsed 454 | .get("log_registers") 455 | .map(|lr| if let Value::Bool(v) = lr { *v } else { false }) 456 | .unwrap_or_default(), 457 | ) 458 | .socket_path( 459 | value 460 | .parsed 461 | .get("socket_path") 462 | .and_then(|sp| { 463 | if let Value::String(v) = sp { 464 | Some(PathBuf::from(v)) 465 | } else { 466 | None 467 | } 468 | }) 469 | .ok_or_else(|| anyhow!("No socket path provided"))?, 470 | ) 471 | .build()) 472 | } 473 | } 474 | } 475 | 476 | impl Register for Tracer { 477 | fn register(&mut self, _: PluginId, args: &Args, info: &Info) -> Result<()> { 478 | let plugin_args = PluginArgs::try_from(args)?; 479 | 480 | self.target_name = Some(info.target_name.clone()); 481 | 482 | self.tx = Arc::new(Mutex::new(Some(UnixStream::connect( 483 | plugin_args.socket_path, 484 | )?))); 485 | 486 | self.log_insns = plugin_args.log_insns; 487 | self.log_mem = plugin_args.log_mem; 488 | self.log_syscalls = plugin_args.log_syscalls; 489 | 490 | #[cfg(not(any(feature = "plugin-api-v0", feature = "plugin-api-v1")))] 491 | { 492 | self.log_registers = plugin_args.log_registers; 493 | } 494 | 495 | Ok(()) 496 | } 497 | } 498 | 499 | register!(Tracer::new()); 500 | --------------------------------------------------------------------------------