5 releases
0.0.5 | Mar 3, 2022 |
---|---|
0.0.4 | Jan 25, 2022 |
0.0.3 | Apr 24, 2020 |
0.0.2 | Sep 12, 2019 |
0.0.1 | Aug 24, 2019 |
#571 in Testing
2MB
9K
SLoC
x86test custom test runner
x86test is a custom test runner that allows you to write unit tests which use privileged (x86) instructions.
It achieves that as follows: for every unit test it creates a tiny VM (using kvm) which mirrors the address space of the current test process inside the guest VM. Next the VM is initialized and jumps to the unit test function which is now executed in guest ring 0 (and here you can use all your fancy instructions). Finally, once the test returns (or panics), control is transferred back from the VM to our test runner.
Funky? Yes.
Is it hard to use? No! It integrates neatly with rust thanks to the rust custom test framework and procedural macros. See the example below.
Does it work? It has limitations (this is expected you're running on bare-metal x86), so don't expect much infrastructure. For panic and assert you have to use special versions, also you can't use anything that does system calls (like println!, but a custom sprintln! macro is provided).
An example
This is particularly helpful to test the x86 crate. For example say we have a function like this:
/// Read 16 bits from port
#[inline]
pub unsafe fn inw(port: u16) -> u16 {
let ret: u16;
asm!("inw %dx, %ax", in("dx") port, out("ax") ret, options(att_syntax));
ret
}
The problem with inw
is that it needs IO privilege level in E/RFlags to not
cause an exception (and as a result crash the process). A regular Linux process
will not run with this privilege level, however we can now write a x86test:
#[x86test(ioport(0x1, 0xfe))]
fn check_inw_port_read() {
unsafe {
kassert!(
x86::io::inw(0x1) == 0xfe,
"`inw` instruction didn't read the correct value"
);
}
}
A few things are happening here that warrant some explaining:
First, instead of #[test]
we used #[x86test]
to tell the system we don't
want to use regular unit tests. x86test
supports a few arguments (more on
that later), here we just tell the "hypervisor" of the test runner to install
an ioport with port number 1 that shall always return 0xfe when being read.
Next, comes our function declaration -- nothing special here -- followed by
unsafe, just because inw
is unsafe. Finally, we use kassert!
, a custom assert
macro that works in guest ring 0 for our hypervisor, to check that inw
does
the right thing.
You'll find more example tests among the x86 tests.
Note that running a x86test currently works only on Linux and requires some linking magic.
Setting RUSTFLAGS="-C relocation-model=dynamic-no-pic -C code-model=kernel"
should do.
I expect the custom RUSTFLAGS
to not be necessary in the future.
x86test reference
The x86test attribute currently supports the following parameters:
ioport(port, val)
: Reads toport
will returnval
, writes toport
other thanval
will fail the test.ram(from, to)
: Adds physical memory in address rangefrom
--to
should_halt
: To tell the hypervisor that the test will halt (note: use like this#[x86test(should_halt)]
).#[should_panic]
: Can be added if a test is expected to panic.
Code Organization
- x86test_macro: contains a procedural macro implementation of
x86test
. - x86test_types: contains implementations of kassert, kpanic and the X86TestFn struct.
- src: contains the custom test runner implementation.
Updating
Should be done in the following order:
- Release new version of
x86test-types
- Release new version of
x86test-macro
(adjust version dependency of x86test-types) - Release new version of
x86test
(adjust version dependency of x86test-types and x86test-macro) - Tag with
git tag x86test-0.0.x