Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
11 changes: 8 additions & 3 deletions doc/src/developer/tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
13 changes: 8 additions & 5 deletions doc/src/reference/drivers/virtualbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <vm> 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

Expand All @@ -41,12 +44,12 @@ Until pause is implemented, tools must not pause the VM (`mem-dump --no-pause`),
The examples take `--vbox_vmi <socket>`:
~~~
$ cargo build --features virtualbox-vmi --examples
$ ./target/debug/examples/mem-dump --vbox_vmi ~/.config/VirtualBox/vmi/<vm uuid>.sock --no-pause
$ ./target/debug/examples/mem-dump --vbox_vmi ~/.config/VirtualBox/vmi/<vm uuid>.sock
~~~

Python (bindings built with the `virtualbox-vmi` feature):
~~~
$ python3 python/examples/mem-dump.py --vbox-vmi ~/.config/VirtualBox/vmi/<vm uuid>.sock --no-pause
$ python3 python/examples/mem-dump.py --vbox-vmi ~/.config/VirtualBox/vmi/<vm uuid>.sock
~~~

The client comes from the [vboxvmi](https://github.com/Wenzel/rs-vboxvmi) crate. To build against a local checkout of it:
Expand Down
10 changes: 2 additions & 8 deletions python/examples/mem-dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
14 changes: 10 additions & 4 deletions src/driver/virtualbox_vmi.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -24,6 +25,8 @@ pub struct VBoxVmi {
client: RefCell<Client>,
/// 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 {
Expand All @@ -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<u16, Box<dyn Error>> {
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.
Expand Down Expand Up @@ -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<dyn Error>> {
not_implemented("pause")
Ok(self.client.get_mut().pause()?)
}

fn resume(&mut self) -> Result<(), Box<dyn Error>> {
not_implemented("resume")
Ok(self.client.get_mut().resume()?)
}

fn get_driver_type(&self) -> DriverType {
Expand Down
Loading