From 2c2bcb88412e22fde2b8138c46a9bf90e0ac922d Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Mon, 12 Oct 2026 07:21:08 +0000 Subject: [PATCH 1/2] virtualbox: VBoxVMI method pauses, resumes and counts vCPUs pause and resume go through the VBoxVMI PAUSE and RESUME commands: VirtualBox's standard pause, so the VM shows as Paused in VBoxManage and the GUI, synchronous (every vCPU stopped when pause returns) and idempotent (one resume undoes any number of pauses, by anyone). The vCPU count comes from VCPU_COUNT at connect, like the max physical address. rs-vboxvmi is bumped to 622c077 for the new commands. mem-dump.py no longer requires --no-pause for VBoxVMI. The driver and test docs list pause/resume and vCPU count as supported, and the VBoxVMI test filters cover the pause, resume and vCPU tests. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 2 +- Cargo.toml | 2 +- doc/src/developer/tests.md | 11 ++++++++--- doc/src/reference/drivers/virtualbox.md | 13 ++++++++----- python/examples/mem-dump.py | 10 ++-------- src/driver/virtualbox_vmi.rs | 14 ++++++++++---- 6 files changed, 30 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5fa0cdac..71845e75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2080,7 +2080,7 @@ dependencies = [ [[package]] name = "vboxvmi" version = "0.1.0" -source = "git+https://github.com/Wenzel/rs-vboxvmi?rev=d0a64067a03282dc3c8f1aa1f663af3ae437a8e5#d0a64067a03282dc3c8f1aa1f663af3ae437a8e5" +source = "git+https://github.com/Wenzel/rs-vboxvmi?rev=622c07700c2c0a60139758b597718731db063e80#622c07700c2c0a60139758b597718731db063e80" dependencies = [ "libc", "thiserror 1.0.69", diff --git a/Cargo.toml b/Cargo.toml index df8b1142..eacda71f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ xenvmevent-sys = { version = "=0.2.0", optional = true } kvmi = { version = "=0.5.0", optional = true } fdp = { version = "=0.3.0", optional = true } vboxapi = { git = "https://github.com/Wenzel/rs-vboxapi", rev = "5ba8d86d1cc58a5935208588580782a31fb2d27c", optional = true } -vboxvmi = { git = "https://github.com/Wenzel/rs-vboxvmi", rev = "d0a64067a03282dc3c8f1aa1f663af3ae437a8e5", optional = true } +vboxvmi = { git = "https://github.com/Wenzel/rs-vboxvmi", rev = "622c07700c2c0a60139758b597718731db063e80", optional = true } winapi = { version = "0.3", features = [ "tlhelp32", "winnt", diff --git a/doc/src/developer/tests.md b/doc/src/developer/tests.md index 749ca77b..10a84df0 100644 --- a/doc/src/developer/tests.md +++ b/doc/src/developer/tests.md @@ -75,11 +75,16 @@ Run the generic tests the method supports, or the VirtualBox ones: MICROVMI_TEST_FILTER=virtualbox_ cargo test --features virtualbox-api --test integration MICROVMI_TEST_FILTER=virtualbox_ VBOX_TEST_MEMORY_MB=5120 cargo test --features virtualbox-api --test integration MICROVMI_TEST_FILTER=vcpu VBOX_TEST_CPUS=2 cargo test --features virtualbox-api --test integration -MICROVMI_TEST_FILTER=virtualbox_read_ VBOX_TEST_METHOD=vboxvmi cargo test --features virtualbox-vmi --test integration +for f in init vcpu_count double_pause pause_ double_resume virtualbox_read_; do MICROVMI_TEST_FILTER=$f VBOX_TEST_METHOD=vboxvmi cargo test --features virtualbox-vmi --test integration; done +MICROVMI_TEST_FILTER=vcpu VBOX_TEST_CPUS=2 VBOX_TEST_METHOD=vboxvmi cargo test --features virtualbox-vmi --test integration ~~~ -The VBoxVMI method only reads memory so far (no pause, registers, vCPU count or writes): filter its -runs to the `virtualbox_read_` tests. +The VBoxVMI method reads memory, pauses and resumes, and counts vCPUs so far, but has no registers or +writes ([#284](https://github.com/Wenzel/libmicrovmi/issues/284), [#285](https://github.com/Wenzel/libmicrovmi/issues/285)): +the `virtualbox_registers_*`, `virtualbox_write_registers_*` and `virtualbox_write_physical_*` tests are +MachineDebugger-only. The filters above run every test VBoxVMI supports, each once: `pause_` covers +`pause_resume`, `multiple_pause_resume` and `virtualbox_pause_*`. A plain `pause` filter would also run +`virtualbox_registers_unchanged_while_paused`; the `pause` test itself is covered by `double_pause`. If cargo runs in a container without VirtualBox, build the test binary there and run it on the host: diff --git a/doc/src/reference/drivers/virtualbox.md b/doc/src/reference/drivers/virtualbox.md index 91b93577..9dcc27c4 100644 --- a/doc/src/reference/drivers/virtualbox.md +++ b/doc/src/reference/drivers/virtualbox.md @@ -14,12 +14,15 @@ MachineDebugger and FDP name the VM with `common.vm_name`; VBoxVMI names it by i Speaks VBoxVMI, a protocol served from inside the VM process of a patched VirtualBox over a per-VM Unix socket. It is the fast path, and the way towards VMI events. -Supported: reading guest physical memory, max physical address (end of the highest guest RAM range, so RAM above 4 GiB is included). +Supported: reading guest physical memory, max physical address (end of the highest guest RAM range, so RAM above 4 GiB is included), pause/resume, vCPU count. Reads are served inside the VM process without waiting for a vCPU, in batches of up to 15 pages per request. A read that hits an unreadable page (MMIO, including the local APIC page, or no memory) fails, with `bytes_read` set to the bytes read before that page; devices are never accessed. +Pause is VirtualBox's own pause (the VM shows as Paused in the GUI and `VBoxManage`). It is synchronous, all vCPUs are stopped when it returns, and idempotent: pausing a paused VM succeeds, and one resume resumes it. +Pauses are not counted, also across clients: resume resumes the VM even if the GUI, `VBoxManage` or another tool paused it too. +Pause and resume fail while the VM is neither running nor paused (powering on or off, saving or restoring its state, live snapshot, teleport), and resume fails while the host's power management has the VM paused. +A VM paused through VBoxVMI stays paused if the tool exits or disconnects without resuming it ([#287](https://github.com/Wenzel/libmicrovmi/issues/287)). -Not implemented yet: registers, writes, pause/resume and vCPU count ([#283](https://github.com/Wenzel/libmicrovmi/issues/283)–[#285](https://github.com/Wenzel/libmicrovmi/issues/285)): they return a "not implemented yet" error. -Until pause is implemented, tools must not pause the VM (`mem-dump --no-pause`), or pause it with `VBoxManage controlvm pause`. +Not implemented yet: registers, writes ([#284](https://github.com/Wenzel/libmicrovmi/issues/284)–[#285](https://github.com/Wenzel/libmicrovmi/issues/285)): they return a "not implemented yet" error. ### Requirements @@ -41,12 +44,12 @@ Until pause is implemented, tools must not pause the VM (`mem-dump --no-pause`), The examples take `--vbox_vmi `: ~~~ $ cargo build --features virtualbox-vmi --examples -$ ./target/debug/examples/mem-dump --vbox_vmi ~/.config/VirtualBox/vmi/.sock --no-pause +$ ./target/debug/examples/mem-dump --vbox_vmi ~/.config/VirtualBox/vmi/.sock ~~~ Python (bindings built with the `virtualbox-vmi` feature): ~~~ -$ python3 python/examples/mem-dump.py --vbox-vmi ~/.config/VirtualBox/vmi/.sock --no-pause +$ python3 python/examples/mem-dump.py --vbox-vmi ~/.config/VirtualBox/vmi/.sock ~~~ The client comes from the [vboxvmi](https://github.com/Wenzel/rs-vboxvmi) crate. To build against a local checkout of it: diff --git a/python/examples/mem-dump.py b/python/examples/mem-dump.py index da9d8fd1..a67d8c7c 100755 --- a/python/examples/mem-dump.py +++ b/python/examples/mem-dump.py @@ -35,17 +35,11 @@ def parse_args(): "--vbox-vmi", type=str, metavar="SOCKET", - help="Use VBoxVMI (VM name defaults to the socket filename; requires --no-pause)", - ) - parser.add_argument( - "--no-pause", - action="store_true", - help="Do not pause the VM while dumping (required for VBoxVMI, which has no pause command yet)", + help="Use VBoxVMI (VM name defaults to the socket filename)", ) + parser.add_argument("--no-pause", action="store_true", help="Do not pause the VM while dumping") parser.add_argument("-o", "--output-file", help="Dump file path", default="{vm_name}.dump") args = parser.parse_args() - if args.vbox_vmi and not args.no_pause: - parser.error("--vbox-vmi requires --no-pause") if args.vbox_machinedebugger and args.vbox_vmi: parser.error("--vbox-machinedebugger and --vbox-vmi are mutually exclusive") if args.vm_name is None and args.vbox_vmi: diff --git a/src/driver/virtualbox_vmi.rs b/src/driver/virtualbox_vmi.rs index 230054df..af67d2c3 100644 --- a/src/driver/virtualbox_vmi.rs +++ b/src/driver/virtualbox_vmi.rs @@ -1,5 +1,6 @@ //! VirtualBox driver, VBoxVMI access method: the VBoxVMI protocol of a patched VirtualBox. use std::cell::RefCell; +use std::convert::TryFrom; use std::error::Error; use vboxvmi::Client; @@ -24,6 +25,8 @@ pub struct VBoxVmi { client: RefCell, /// End of the highest RAM range, fixed for the VM's lifetime. max_addr: u64, + /// Fixed for the VM's lifetime. + vcpu_count: u16, } impl VBoxVmi { @@ -35,17 +38,19 @@ impl VBoxVmi { .last() .expect("vboxvmi::Client::ram_ranges is never empty") .end; + let vcpu_count = u16::try_from(client.vcpu_count()?)?; Ok(VBoxVmi { client: RefCell::new(client), max_addr, + vcpu_count, }) } } -// shortcut: VM state access errors until the protocol has the commands (#283-#285). +// shortcut: VM state access errors until the protocol has the commands (#284-#285). impl Introspectable for VBoxVmi { fn get_vcpu_count(&self) -> Result> { - not_implemented("vCPU count") + Ok(self.vcpu_count) } /// On error, `bytes_read` is the length of the readable prefix, up to the first unreadable page. @@ -95,12 +100,13 @@ impl Introspectable for VBoxVmi { not_implemented("register write") } + /// Synchronous and idempotent: one `resume` undoes any number of `pause`s. fn pause(&mut self) -> Result<(), Box> { - not_implemented("pause") + Ok(self.client.get_mut().pause()?) } fn resume(&mut self) -> Result<(), Box> { - not_implemented("resume") + Ok(self.client.get_mut().resume()?) } fn get_driver_type(&self) -> DriverType { From 8323a3c9f326f2894c7ea49c70c1a95f52cff10d Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Mon, 12 Oct 2026 07:45:47 +0000 Subject: [PATCH 2/2] virtualbox: document every state in which VBoxVMI refuses pause or resume The server also refuses during a configuration change and, for resume, on a teleport source or target. rs-vboxvmi is bumped to 2b71bb8 for the matching error message. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 2 +- Cargo.toml | 2 +- doc/src/reference/drivers/virtualbox.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 71845e75..0072dc98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2080,7 +2080,7 @@ dependencies = [ [[package]] name = "vboxvmi" version = "0.1.0" -source = "git+https://github.com/Wenzel/rs-vboxvmi?rev=622c07700c2c0a60139758b597718731db063e80#622c07700c2c0a60139758b597718731db063e80" +source = "git+https://github.com/Wenzel/rs-vboxvmi?rev=2b71bb814ac7dc8948e1ca90bec88755f362c69c#2b71bb814ac7dc8948e1ca90bec88755f362c69c" dependencies = [ "libc", "thiserror 1.0.69", diff --git a/Cargo.toml b/Cargo.toml index eacda71f..a91d45d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ xenvmevent-sys = { version = "=0.2.0", optional = true } kvmi = { version = "=0.5.0", optional = true } fdp = { version = "=0.3.0", optional = true } vboxapi = { git = "https://github.com/Wenzel/rs-vboxapi", rev = "5ba8d86d1cc58a5935208588580782a31fb2d27c", optional = true } -vboxvmi = { git = "https://github.com/Wenzel/rs-vboxvmi", rev = "622c07700c2c0a60139758b597718731db063e80", optional = true } +vboxvmi = { git = "https://github.com/Wenzel/rs-vboxvmi", rev = "2b71bb814ac7dc8948e1ca90bec88755f362c69c", optional = true } winapi = { version = "0.3", features = [ "tlhelp32", "winnt", diff --git a/doc/src/reference/drivers/virtualbox.md b/doc/src/reference/drivers/virtualbox.md index 9dcc27c4..7df97517 100644 --- a/doc/src/reference/drivers/virtualbox.md +++ b/doc/src/reference/drivers/virtualbox.md @@ -19,7 +19,7 @@ Reads are served inside the VM process without waiting for a vCPU, in batches of A read that hits an unreadable page (MMIO, including the local APIC page, or no memory) fails, with `bytes_read` set to the bytes read before that page; devices are never accessed. Pause is VirtualBox's own pause (the VM shows as Paused in the GUI and `VBoxManage`). It is synchronous, all vCPUs are stopped when it returns, and idempotent: pausing a paused VM succeeds, and one resume resumes it. Pauses are not counted, also across clients: resume resumes the VM even if the GUI, `VBoxManage` or another tool paused it too. -Pause and resume fail while the VM is neither running nor paused (powering on or off, saving or restoring its state, live snapshot, teleport), and resume fails while the host's power management has the VM paused. +Pause and resume fail while VirtualBox controls the VM's run state: powering on or off, saving or restoring its state, live snapshot, teleport, or a configuration change (medium or network attachment). Resume also fails while the host's power management has the VM paused, and after a failed teleport of a paused VM until it is resumed through VirtualBox. A VM paused through VBoxVMI stays paused if the tool exits or disconnects without resuming it ([#287](https://github.com/Wenzel/libmicrovmi/issues/287)). Not implemented yet: registers, writes ([#284](https://github.com/Wenzel/libmicrovmi/issues/284)–[#285](https://github.com/Wenzel/libmicrovmi/issues/285)): they return a "not implemented yet" error.