diff --git a/Cargo.lock b/Cargo.lock index 5fa0cdac..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=d0a64067a03282dc3c8f1aa1f663af3ae437a8e5#d0a64067a03282dc3c8f1aa1f663af3ae437a8e5" +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 df8b1142..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 = "d0a64067a03282dc3c8f1aa1f663af3ae437a8e5", 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/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..7df97517 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 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, 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 {