• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..--

src/25-Apr-2025-3,9252,321

Android.bpD25-Apr-20253.1 KiB121112

README.mdD25-Apr-20255.3 KiB166128

TEST_MAPPINGD25-Apr-2025238 109

common.hD25-Apr-20251 KiB4321

entry.SD25-Apr-20255 KiB17784

exceptions.SD25-Apr-20254.6 KiB17997

exceptions_panic.SD25-Apr-20251.7 KiB9252

sections.ldD25-Apr-20253.5 KiB142126

README.md

1# vmbase
2
3This directory contains a Rust crate and static library which can be used to write `no_std` Rust
4binaries to run in an aarch64 VM under crosvm (via the VirtualizationService), such as for pVM
5firmware, a VM bootloader or kernel.
6
7In particular it provides:
8
9- An [entry point](entry.S) that initializes the MMU with a hard-coded identity mapping, enables the
10  cache, prepares the image and allocates a stack.
11- An [exception vector](exceptions.S) to call your exception handlers.
12- A UART driver and `println!` macro for early console logging.
13- Functions to shutdown or reboot the VM.
14
15Libraries are also available for heap allocation, page table manipulation and PSCI calls.
16
17## Usage
18
19The [example](example/) subdirectory contains an example of how to use it for a VM bootloader.
20
21### Build file
22
23Start by creating a `rust_ffi_static` rule containing your main module:
24
25```soong
26rust_ffi_static {
27    name: "libvmbase_example",
28    defaults: ["vmbase_ffi_defaults"],
29    crate_name: "vmbase_example",
30    srcs: ["src/main.rs"],
31    rustlibs: [
32        "libvmbase",
33    ],
34}
35```
36
37`vmbase_ffi_defaults`, among other things, specifies the stdlibs including the `compiler_builtins`
38and `core` crate. These must be explicitly specified as we don't want the normal set of libraries
39used for a C++ binary intended to run in Android userspace.
40
41### Entry point
42
43Your main module needs to specify a couple of special attributes:
44
45```rust
46#![no_main]
47#![no_std]
48```
49
50This tells rustc that it doesn't depend on `std`, and won't have the usual `main` function as an
51entry point. Instead, `vmbase` provides a macro to specify your main function:
52
53```rust
54use vmbase::{logger, main};
55use log::{info, LevelFilter};
56
57main!(main);
58
59pub fn main(arg0: u64, arg1: u64, arg2: u64, arg3: u64) {
60    logger::init(LevelFilter::Info).unwrap();
61    info!("Hello world");
62}
63```
64
65vmbase adds a wrapper around your main function to initialize the console driver first (with the
66UART at base address `0x3f8`, the first UART allocated by crosvm), and make a PSCI `SYSTEM_OFF` call
67to shutdown the VM if your main function ever returns.
68
69You can also shutdown the VM by calling `vmbase::power::shutdown` or 'reboot' by calling
70`vmbase::power::reboot`. Either will cause crosvm to terminate the VM, but by convention we use
71shutdown to indicate that the VM has finished cleanly, and reboot to indicate an error condition.
72
73### Exception handlers
74
75You must provide handlers for each of the 8 types of exceptions which can occur on aarch64. These
76must use the C ABI, and have the expected names. For example, to log sync exceptions and reboot:
77
78```rust
79use vmbase::power::reboot;
80
81extern "C" fn sync_exception_current() {
82    eprintln!("sync_exception_current");
83
84    let mut esr: u64;
85    unsafe {
86        asm!("mrs {esr}, esr_el1", esr = out(reg) esr);
87    }
88    eprintln!("esr={:#08x}", esr);
89
90    reboot();
91}
92```
93
94The `println!` macro shouldn't be used in exception handlers, because it relies on a global instance
95of the UART driver which might be locked when the exception happens, which would result in deadlock.
96Instead you can use `eprintln!`, which will re-initialize the UART every time to ensure that it can
97be used. This should still be used with care, as it may interfere with whatever the rest of the
98program is doing with the UART.
99
100See [example/src/exceptions.rs](examples/src/exceptions.rs) for a complete example.
101
102### Linker script and initial idmap
103
104The [entry point](entry.S) code expects to be provided a hardcoded identity-mapped page table to use
105initially. This must contain at least the region where the image itself is loaded, some writable
106DRAM to use for the `.bss` and `.data` sections and stack, and a device mapping for the UART MMIO
107region. See the [example/idmap.S](example/idmap.S) for an example of how this can be constructed.
108
109The addresses in the pagetable must map the addresses the image is linked at, as we don't support
110relocation. This can be achieved with a linker script, like the one in
111[example/image.ld](example/image.ld). The key part is the regions provided to be used for the image
112and writable data:
113
114```ld
115MEMORY
116{
117	image		: ORIGIN = 0x80200000, LENGTH = 2M
118	writable_data	: ORIGIN = 0x80400000, LENGTH = 2M
119}
120```
121
122### Building a binary
123
124To link your Rust code together with the entry point code and idmap into a static binary, you need
125to use a `cc_binary` rule:
126
127```soong
128cc_binary {
129    name: "vmbase_example",
130    defaults: ["vmbase_elf_defaults"],
131    srcs: [
132        "idmap.S",
133    ],
134    static_libs: [
135        "libvmbase_example",
136    ],
137    linker_scripts: [
138        "image.ld",
139        ":vmbase_sections",
140    ],
141}
142```
143
144This takes your Rust library (`libvmbase_example`), the vmbase library entry point and exception
145vector (`libvmbase_entry`) and your initial idmap (`idmap.S`) and builds a static binary with your
146linker script (`image.ld`) and the one provided by vmbase ([`sections.ld`](sections.ld)). This is an
147ELF binary, but to run it as a VM bootloader you need to `objcopy` it to a raw binary image instead,
148which you can do with a `raw_binary` rule:
149
150```soong
151raw_binary {
152    name: "vmbase_example_bin",
153    stem: "vmbase_example.bin",
154    src: ":vmbase_example",
155    enabled: false,
156    target: {
157        android_arm64: {
158            enabled: true,
159        },
160    },
161}
162```
163
164The resulting binary can then be used to start a VM by passing it as the bootloader in a
165`VirtualMachineRawConfig`.
166