uemulator is an x86_64 PC emulator written in Rust, built to be embedded. It boots unmodified distribution kernels (Alpine, Debian, Fedora, and Xen with a Linux dom0) on an emulated AMD Zen 5 machine, through its own UEFI firmware. The design follows QEMU's TCG: guest code is translated into cached blocks and run by an interpreter or by a JIT for the host. The same library runs as a command-line tool, in a browser tab, or as a UEFI application. Its public API is small and centred on MachineConfig and EmulatedMachine, and it can be built with no dependencies at all.
Try it in the browser: Alpine Linux boots in a Web Worker.
- Real guests. Boots stock Alpine, Debian installer and Fedora kernels, Xen 4.21 with a dom0 and domU, and Linux KVM running nested inside the guest. Xen can also be handed to the machine directly (
--hypervisor xen.gzorxen.efi), and the firmware boots it with the kernel as dom0, no disk or GRUB needed. - Modern CPU model. AMD Zen 5 (EPYC Turin) with AVX2 and AVX-512, AES-NI and SHA, and AMD SVM with nested paging. Linux needs no software mitigations and runs with KASLR. Real mode, protected mode and compatibility mode are all supported.
- Your choice of firmware. The built-in UEFI firmware (the default), a built-in BIOS for legacy disk boot, or direct kernel entry with no firmware.
- SMP. Up to 64 vCPUs. Each one runs on its own host thread, or all of them share one thread and run deterministically.
- A full PC platform. ACPI, local APIC and x2APIC, I/O APIC, HPET, PCIe with MSI-X and an AMD IOMMU.
- virtio devices. Block, network (with built-in user-mode NAT), console, entropy, and a GPU with keyboard and tablet.
- A screen in your browser.
--display framebuffergives the machine a graphical console, and the CLI serves a KVM-style viewer for its screen, keyboard and mouse. - Its own disk format. Images are sparse and copy-on-write, and can be streamed over HTTP.
- Three JITs. aarch64, x86_64 and WebAssembly hosts get a JIT, and every JIT is checked against the interpreter. The interpreter runs everywhere else.
- Embeddable. No global state, and no stdio or process exit inside the library. Built
no_stdfor freestanding hosts. Withdefault-features = falseit has zero dependencies.
You need a recent stable Rust toolchain (edition 2024) and the target the built-in firmware is compiled for:
rustup target add x86_64-unknown-none
git clone https://github.com/edera-dev/uemulator.git
cd uemulator
cargo build --release -p uemulator-cli # produces target/release/uemulatorCreate a named machine and boot it. quick create downloads the distribution's netboot kernel and initramfs, so it needs network access. It also gives the machine a sparse disk and a NAT network card.
cargo run --release -p uemulator-cli -- quick create test --install alpine # or debian, fedora
cargo run --release -p uemulator-cli -- quick boot test --cpus 4
cargo run --release -p uemulator-cli -- quick boot test --display framebuffer # prints the viewer's URLThe first boot runs the distribution's installer. Once something is installed on the disk, quick boot boots the disk through the machine's firmware. Press Ctrl-] to stop the machine. Machines live in ~/.uemulator/machines/<name>/, and setting UEMULATOR_HOME moves that directory.
Other everyday commands, shown using the built binary (target/release/uemulator):
uemulator quick list # list machines
uemulator quick show test # print a machine's configuration
uemulator quick edit test --memory 4096 # change it
uemulator quick reinstall test --install debian # new distro, same disk
uemulator quick delete test --yes
uemulator advanced boot --kernel bzImage --initramfs initrd.img # your own kernel, through UEFI
uemulator advanced boot --kernel bzImage --firmware none # ... or entered directly (no EFI stub needed)
uemulator advanced boot --kernel bzImage --initramfs initrd.img --hypervisor xen.gz # Xen (xen.gz or xen.efi) with the kernel as dom0
uemulator quick create xen --install alpine --hypervisor xen.gz # a machine whose installer boots as Xen's dom0
uemulator disk create disk.udisk --size 8192 # create an empty disk image
uemulator disk info disk.udiskEvery command takes --help, which lists all of its options.
rustup target add wasm32-unknown-unknown x86_64-unknown-none
./hack/web.sh serve # http://127.0.0.1:8080/Setting WEB_DEMO=1 also builds ready-to-boot machines and puts the page in demo mode: Alpine Linux, and Edera's Xen 4.22 with the same Alpine as its dom0 (the Xen image is pinned by digest in hack/web/demo-xen.ref). The page also takes a hypervisor of your own next to the kernel. Because the page needs cross-origin isolation, serve it with hack/web/serve.py (which web.sh serve uses) or with a server that sends the COOP/COEP headers.
Everything public is re-exported from crates/uemulator/src/lib.rs: build a MachineConfig, create an EmulatedMachine, start it, and wait for it to exit.
[dependencies]
uemulator = { git = "https://github.com/edera-dev/uemulator" }use std::io::Write;
use uemulator::{EmulatedMachine, MachineConfig};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = MachineConfig::new()
.kernel(std::fs::read("bzImage")?)
.initrd(std::fs::read("initramfs")?)
.memory_mib(1024)
.cpus(2);
let mut machine = EmulatedMachine::new(config)?;
// Guest console output (hvc0). Keystrokes go in with `machine.console().write(..)`.
machine.console().on_output(|bytes| {
let mut stdout = std::io::stdout();
let _ = stdout.write_all(bytes);
let _ = stdout.flush();
});
// Call `stop()` on this from any thread to end the run.
let _stop = machine.stop_handle();
machine.start()?;
let reason = machine.wait(); // Shutdown, Reset, TripleFault, Stopped or Internal
println!("\nmachine exited: {reason:?}");
Ok(())
}If no firmware is configured, the library enters the kernel directly through the Linux boot protocol. To boot the kernel through UEFI instead, pass the firmware image built from crates/uemulator-firmware to MachineConfig::uefi_firmware. The builder also covers disks (disk, which can wrap your own BlockBackend), network cards (net), the IOMMU, the clock and the JIT. On a freestanding host, the poll and host_clock hooks connect the machine to the outside world. On macOS, a hardened-runtime binary needs the com.apple.security.cs.allow-jit entitlement to use the JIT.
docs/is the documentation index, grouped by topic.docs/plans/00-overview.mddescribes the architecture and the key decisions behind it.docs/plans/10-milestones.mdrecords what works and how each piece was verified.docs/spec/uemulator-disk-format.mdspecifies the udisk image format.
cargo test --release --workspace
cargo test --release -p uemulator --no-default-features # interpreter only, no dependencies
cargo clippy --release --all-targets # must print zero warnings
./hack/autofix.sh # clippy --fix and rustfmt; run before every commitTests are self-contained: they need no network and no downloaded kernels. CLAUDE.md holds the project's conventions, including commenting, naming, the rule that the interpreter is the oracle for every JIT, and the real-guest checks to run after touching particular subsystems. docs/plans/08-testing.md covers the test strategy.
GPL-2.0. See LICENSE.