Skip to content

Architecture ​

paludarium is split into crates by responsibility. Dependencies only point downwards; there are no cycles.

LayerUses
Entry points (the paludarium command, the Rust API, the WebAssembly launcher)runtime
runtimecpu, jit, kernel, loader, host
cpu, jitdecoder, mmu
kernelmmu, vfs, loader, host
loadermmu, vfs
vfshost

Every crate uses the shared types of paludarium-types.

Crates ​

CrateResponsibility
paludarium-typesShared types (GuestAddr, Errno, ExitReason, ExitStatus, Error)
paludarium-decoderA decoder wrapping iced-x86; only its own instruction types are visible outside
paludarium-mmuA software MMU: two-level page tables of 4 KiB pages
paludarium-hostThe host boundary (the Host trait) and its Linux, macOS, Windows and WebAssembly implementations
paludarium-vfsA safe in-memory filesystem and host directory mounts
paludarium-loaderLoading static ELF files and building the initial stack (argv, envp, auxiliary vector)
paludarium-cpuThe x86-64 interpreter
paludarium-jitThe code cache interface and the WebAssembly JIT
paludarium-kernelLinux system calls; owns the process and thread state
paludarium-runtimeSession and the per-thread execution loop
paludariumThe public API and the paludarium command

Two crates are not published: paludarium-harness, the differential test tool, and paludarium-wasm, the WebAssembly entry point that becomes paludarium.wasm in the npm package.

How a program runs ​

  1. The loader reads the ELF file, maps it into memory and builds the initial stack.
  2. The execution loop makes the CPU run instructions. With the JIT enabled, it first asks the code cache for a translated block and falls back to the interpreter otherwise.
  3. On a system call or a guest exception, the CPU stops and returns the reason. The CPU does not know the kernel.
  4. The execution loop calls the kernel to handle the system call, or delivers a signal to the guest.
  5. Every 100,000 instructions, the loop checks whether it has been asked to stop.

The kernel owns the register state of each thread; the CPU only runs the state it is given.

The host boundary ​

All differences between native hosts and WebAssembly stay inside paludarium-host. No other crate calls std::thread, std::fs or std::time directly. The Host trait provides starting threads, waiting and waking, clocks, waiting with a deadline, terminal information, standard input and output, secure randomness, and mounting host directories.

Guest system call numbers, guest pointers and guest ioctl numbers never cross this boundary.

Three kinds of errors ​

Types keep them apart:

KindTypeGoes to
A failed system callErrnothe guest, as an errno
A guest exceptionExitReasona signal to the guest (SIGSEGV, SIGBUS, SIGILL, SIGFPE, …)
A problem of the emulator itselfErrora stop, with the instruction address, bytes and system call number

A guest's exit (ExitStatus) is never mixed with an emulator error.

Safety rules ​

  • unsafe is allowed only in the host connection and the JIT; every other crate is #![forbid(unsafe_code)] (and the JIT does not use unsafe today).
  • unwrap, expect and panic are denied by clippy in product code.
  • Guest addresses (GuestAddr(u64)) and host addresses are different types. In WebAssembly usize is 32 bits wide, so every conversion is checked.

Released under the Apache-2.0 License.