Skip to content

About

Rust-based multi-platform bootloader

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Rustrapper

License: MIT

A hybrid BIOS/UEFI bootloader written entirely in Rust. Scans storage devices and network adapters from a boot menu, PXE-boots over TFTP, chain-loads an installed OS (FreeDOS verified) from the hard disk, and includes a built-in no_std Lua interpreter that can run interactive shells and execute .lua scripts fetched over the network.

Produces legacy BIOS (MBR+stage2) binaries, x86_64 UEFI and ARM64 EFI applications, ARM64 bare-metal binaries, PCI expansion ROMs, and a native Linux x86_64 binary. The only non-Rust code is a tiny 16-bit MBR and protected-mode entry stub in NASM (~1.6 KB total). The firmware crates use no external crates; the native target links only the shared common and lua crates.

Features

  • BIOS — 16‑bit MBR + 32-bit Rust stage2: menu, PCI storage scan, E820 memory map, e1000 MMIO DHCP + DNS lookup, PXE boot
  • x86_64 UEFI — Pure Rust PE/COFF: SNP protocol, DHCP client, ARP resolve, DNS lookup, storage scan (GPT partitions with GUID/size/label), PXE boot
  • UEFI option ROM — PCI expansion ROM with direct e1000 MMIO driver (no UEFI protocols needed during DXE)
  • ARM64 UEFI — Same Rust code compiled for aarch64-unknown-uefi (large custom stack for the Lua interpreter)
  • ARM64 bare‑metal — No firmware: PL011 UART, PCI ECAM walk, AHCI probe, PXE boot
  • BIOS option ROM — Legacy PCI expansion ROM from rust_payload.bin via romwrap --bios
  • ROM wrapper — Rust CLI tool wraps PE/COFF into UEFI PCI option ROM (--bios for BIOS option ROM)
  • Native Linux — Same menu + Lua interpreter as a host binary; the kernel handles networking (make run-native). make lua builds a standalone bin/lua shell / script runner that skips the menu
  • Lua interpreter — no_std, no heap, fixed static buffers. A [3] Lua Shell menu entry and PXE .lua scripts run the same interpreter (dhcp, fetch(), global, local, tables, functions, loops)
  • PXE Boot — DHCP options 66/67, TFTP client (RFC 1350), executes PE/COFF/ELF32/ELF64/Multiboot and .lua scripts
  • OS chain-loading — [4] Chain to other bootloader boots an installed OS: on BIOS, the entry stub chain-loads the active partition's VBR with a BIOS-IPL-style real-mode handoff (FreeDOS 1.4 verified); on UEFI, it enumerates the firmware's boot options and hands off to the selected entry (Debian 13 (trixie) network installer verified). make freedos-install installs FreeDOS onto disks/bios.img from the host in one step; make debian-netinst-iso fetches the Debian netinst ISO for UEFI validation

Quick Start

# Install Rust targets
rustup target add x86_64-unknown-uefi aarch64-unknown-uefi aarch64-unknown-none

# Install system dependencies
# Arch:  pacman -S nasm edk2-ovmf qemu-system-x86 qemu-system-aarch64
# Debian: apt install nasm ovmf qemu-efi-aarch64 qemu-system-x86 qemu-system-arm
# Fedora: dnf install nasm edk2-ovmf edk2-aarch64 qemu-system-x86 qemu-system-arm

make all                          # Build everything (firmware + native)
make run-x86_64-uefi              # x86_64 UEFI in QEMU (e1000 NIC, full DHCP, PXE boot)
make run-aarch64-bare             # ARM64 bare-metal + AHCI drive
make run-native                   # Run the native Linux binary (no QEMU)
make run-lua                      # Standalone Lua shell on the host (no QEMU)

# Boot an OS from the virtual hard disk (FreeDOS):
make freedos-iso                  # One-time: download the FreeDOS 1.4 LiveCD ISO
make freedos-install              # Install FreeDOS onto disks/bios.img (host-side, scripted)
make debian-netinst-iso                 # One-time: fetch Debian 13 (trixie) network installer (UEFI validation)
make run-i386-bios                # then choose menu [4] Chain to other bootloader

The firmware run-* targets automatically start an OpenWrt VM as the DHCP/TFTP PXE server and populate tftp-root/ with bootloader binaries, the Lua demo (test.lua), and a test file.

Build Targets

Target Binary Description
make i386-bios bin/rust_payload.bin, bin/stage2_entry.bin 32‑bit BIOS stage2
make x86_64-uefi bin/rustrapper.efi x86_64 UEFI application
make aarch64-uefi bin/rustrapper_arm64.efi ARM64 UEFI application
make aarch64-bare bin/rustrapper_arm64_bare.elf ARM64 bare‑metal
make x86_64-uefi-rom bin/rustrapper_efi.rom PCI expansion ROM (UEFI option ROM)
make i386-bios-rom bin/rustrapper_bios.rom PCI expansion ROM (BIOS option ROM)
make native bin/rustrapper_native Native Linux x86_64 binary
make lua bin/lua Standalone Lua shell / script runner
make debian-netinst-iso debian-13.7.0-amd64-netinst.iso Fetch Debian 13 (trixie) network installer (UEFI validation)
make freedos-install disks/bios.img (+ FreeDOS) Install FreeDOS 1.4 onto the BIOS disk image

Run in QEMU

make run-i386-bios                # Legacy BIOS stage2 (serial, Ctrl-A X to exit)
make run-i386-bios-rom            # Legacy BIOS with PCI expansion ROM (rust_payload.bin)
make run-i386-bios-install        # Boot the FreeDOS 1.4 LiveCD to install onto disks/bios.img
make run-x86_64-uefi              # x86_64 UEFI (e1000 NIC, SNP protocol, full DHCP; boots rustrapper first)
make run-x86_64-uefi-debian       # x86_64 UEFI with Debian 13 netinst attached (installs to a separate disk)
make run-x86_64-uefi-rom          # x86_64 UEFI with custom option ROM (direct e1000 MMIO)
make run-x86_64-uefi-rom-debian   # same, with Debian 13 netinst + separate disk attached
make run-aarch64-uefi             # ARM64 UEFI (e1000 NIC via direct PCI scan; boots rustrapper first)
make run-aarch64-uefi-debian       # ARM64 UEFI with Debian 13 arm64 netinst + separate disk
make run-aarch64-bare             # ARM64 bare‑metal with AHCI drive
make run-native                   # Native Linux binary (runs on the host, no QEMU)
make run-lua                      # Standalone Lua shell (runs on the host, no QEMU)
make run-lua SCRIPT=lua/demo/test.lua   # Run a Lua script on the host

All firmware run targets except the x86_64 UEFI ones use -nographic (Ctrl-A X to exit); the x86_64 UEFI targets open a QEMU window. All need the OpenWrt PXE server (started automatically via pxe-start), except run-x86_64-uefi-debian, which uses user-mode networking for the installer. The native target runs directly on the host; type dhcp in the Lua shell to set up networking.

Booting an OS (FreeDOS)

Menu choice [4] Chain to other bootloader chain-loads the active partition's boot sector the way a BIOS IPL would (LBA reads via INT 13h AH=42h, real-mode handoff with DL = boot drive). FreeDOS 1.4 is the verified guest:

make i386-bios                    # builds disks/bios.img (rustrapper MBR + stage2,
                                  # pre-partitioned: active FAT16 at LBA 2048)
make freedos-iso                  # one-time: fetch FD14LIVE.iso
make freedos-install              # format + install KERNEL.SYS/COMMAND.COM (host-side)
make run-i386-bios                # menu [4] -> FreeDOS kernel -> C:\> shell

The installed image includes a minimal AUTOEXEC.BAT that runs CTTY COM1, so the FreeDOS shell is fully interactive over the -nographic serial console.

Two details matter for coexistence with the bootloader (see AGENTS.md gotcha 65):

  • The FAT16 partition starts at LBA 2048, past the MBR's 450‑sector stage2 load area — an install whose filesystem begins at LBA 63 overwrites the bootloader on disk and the next boot triple-faults.
  • make freedos-install is scripted (install-freedos.sh + the FreeDOS 1.4 boot-sector code in freedos/bootsect.bin); the interactive alternative (make run-i386-bios-install) works too, but skip the FDISK step so the pre-made LBA-2048 partition is kept, and run make restore-mbr afterwards (it restores rustrapper's MBR code and warns if the partition overlaps the stage2 area).

Rebuilding the bootloader (e.g. make i386-bios) updates disks/bios.img's bootloader area in place — the FreeDOS install survives. Disk images live in disks/, which make clean never touches; make wipe-disks recreates them blank.

Booting an OS on UEFI (Debian netinst)

Menu choice [4] Chain to other bootloader on the UEFI target enumerates the firmware's boot options (from NVRAM BootOrder/Boot#### variables) and presents them as a submenu, then hands off to the selected entry:

make debian-netinst-iso                  # one-time: fetch debian-13.7.0-amd64-netinst.iso
make run-x86_64-uefi-debian      # menu [4] -> pick "UEFI QEMU DVD-ROM" -> Debian installer

The handoff uses LoadImage/StartImage when the option carries a file path, and falls back to setting BootNext plus a cold reset for device-only entries such as the El Torito CD-ROM image, so any firmware-listed boot target works.

Lua Shell

Every target presents a menu:

Menu:
  [1] List storage devices
  [2] Boot from network
  [3] Lua Shell
  [4] Chain to other bootloader
Choose:

Choosing [3] opens an interactive Lua shell running the built-in interpreter. The shell starts without networking — run the dhcp command to set it up (establishes the network and enables fetch()):

> dhcp
  local IP: 10.0.0.35
  TFTP server: 10.0.0.1
true
> print(fetch("test.txt"))
21
> global booted = true
> if booted then
>> print("network OK")
>> end
network OK

The shell supports full line editing (Left/Right/Home/End/Delete), Up/Down command history with draft restore, Tab completion of builtin/global names, Ctrl-C to cancel the current input, Ctrl-L / clear to clear the screen, and multiline continuation (>> ) for incomplete Lua input.

Supported: integers and floats (IEEE-754 doubles, e.g. 5.5, .5, 1e3), strings, booleans, nil, local/global variables, + - * / %, comparisons, and/or/not, .. concat, if/elseif/else, while, repeat ... until, break, goto/labels, numeric for, generic for k, v in table (or for k, v in pairs(table)), named function/return, tables, print(), comments. int / int stays integer (10/3 == 3); mixing in a float promotes to float, and floats print like Lua's %.14g (5.0, 1/3.0 -> 0.33333333333333). Calls can return multiple values (select, next, pairs), so k, v = next(t) walks a table (assigning t[k] = nil removes the field) and local a, b, c = select(1, x, y, z) binds several.

Builtins:

  • _VERSION — the interpreter version string, "Lua 5.5"
  • next(t [, k]) — return the next key/value pair of a table (or nil at the end); next(t) == nil tests for an empty table
  • pairs(t) — the next function plus the table, for for k, v in pairs(t) do ... end; a __pairs metamethod is dispatched
  • print(v1, v2, ...) — print values separated by tabs, followed by a newline
  • string.byte(s [, i [, j]]) — return the numeric byte codes of characters s[i..j]; default i=1, default j=i; negative indices count from the end; returns nothing if the range is empty
  • string.char(b1, b2, ...) — return a string made from the given byte values (each 0..255); with no arguments returns ""
  • string.dump(f) — return a binary string containing the function's bytecode and metadata (interpreter-specific format; no load is provided)
  • string.find(s, pattern [, init [, plain]]) — find the first match of pattern in s; returns the 1-based start/end indices or nil; plain=true does a literal substring search; supports a subset of Lua patterns (., %a/%d/%l/%s/%u/%w/%x/%p/%c, [set], [^set], *, +, -, ?, ^, $)
  • string.format(formatstring, ...) — format arguments according to formatstring; supports %% %s %q %c %d %i %u %o %x %X %f %e %E %g %G with flags - + ' ' 0 #, width, and precision; %g/%G use %.14g precision; results over 1024 bytes error
  • string.gmatch(s, pattern [, init]) — return an iterator over the matches of pattern in s; yields each match's captures (or the whole match when there are no (...) captures); works in a for loop or by direct calls until it returns nil; init defaults to 1
  • string.gsub(s, pattern, repl [, n]) — replace up to n matches of pattern in s with repl (string, table, or function) and return (result, count); in a string repl, %0..%9 are captures and %% is %
  • string.len(s) — return the number of bytes in s (same as #s)
  • string.lower(s) — return a copy of s with ASCII uppercase letters converted to lowercase
  • string.match(s, pattern [, init]) — return the captures of the first match of pattern in s (or the whole match), or nil; init defaults to 1
  • string.pack(fmt, v1, ...) / string.packsize(fmt) / string.unpack(fmt, s [, pos]) — pack/unpack binary data according to a format string (see help format-strings)
  • string.rep(s, n [, sep]) — return s repeated n times, optionally separated by sep
  • string.reverse(s) — return a reversed copy of s
  • string.sub(s, i [, j]) — return the substring s[i..j] (1-based, negative indices count from the end)
  • string.upper(s) — return a copy of s with ASCII lowercase letters converted to uppercase
  • table.concat(list [, sep [, i [, j]]]) / table.create(n [, m]) / table.insert(list, [pos,] value) / table.move(a1, f, e, t [, a2]) / table.pack(...) / table.remove(list [, pos]) / table.sort(list [, comp]) / table.unpack(list [, i [, j]]) — the table library (concatenate, create, insert, move, pack, remove, sort, unpack)
  • math.ceil(x) / math.floor(x) / math.maxinteger / math.mininteger / math.random([m [, n]]) / math.randomseed(x) / math.tointeger(x) / math.type(x) — the math library (ceil, floor, max/min integer, random, randomseed, tointeger, type)
  • io.close([f]) / io.flush([f]) / io.input([f]) / io.lines([name]) / io.open(name [, mode]) / io.output([f]) / io.read(...) / io.tmpfile() / io.type(obj) / io.write(...) — the io library, operating on the pseudo-filesystem of fetched files (read/write to in-memory files and stdout)
  • Embedded files (native target) — bake files into the pseudo-filesystem at build time so io.open() / dofile() can read them without networking: set RUSTRAPPER_EMBED="name=path,..." (comma-separated name=path pairs; a bare path uses its basename as the name) or RUSTRAPPER_EMBED_DIR=/path/to/dir (embeds every file in that directory) when building, e.g. RUSTRAPPER_EMBED_DIR=./assets cargo build -p native or make native RUSTRAPPER_EMBED_DIR=./assets. Embedded files appear in ls alongside fetched ones.
  • os.exit([code [, close]]) / os.remove(filename) / os.rename(oldname, newname) — the os library (exit the interpreter; remove/rename files in the pseudo-filesystem on the native target)
  • setmetatable(table, metatable|nil) — store/remove a table's metatable and return the table; dispatched metamethods: __index, __newindex, __eq, __lt, __le, __concat, __add/__sub/__mul/__div/__mod, __unm, __call, __tostring, __pairs; a __metatable field protects it
  • rawequal(v1, v2) — primitive equality, never __eq
  • rawget(table, index) — the real table[index] without __index (agrees with table[index])
  • rawlen(v) — length of a table or string without __len (a table's length is the run of integer keys from 1)
  • rawset(table, index, value) — the real table[index] = value without __newindex; returns the table (nil/NaN indexes error)
  • select(index, ...) — arguments after index (-1 is the last), or the argument count when index is "#"
  • tonumber(e [, base]) — convert a number or numeric string to a number (or nil); with base 2..36, parse a string as an integer in that base
  • tostring(v) — the human-readable string form of any value (same rendering as print)
  • type(v) — the Lua type name of a value: "nil", "boolean", "number", "string", "table", or "function" (native builtins count as functions)
  • warn(msg, ...) — concatenate string/number arguments and print Lua warning: <msg>; the control messages "@off"/"@on" toggle warnings
  • pcall(f, ...) — protected call: true plus the results on success, or false plus the error object on failure (a coroutine can yield through it)
  • xpcall(f, err, ...) — like pcall, but on failure calls err(error_object) and returns false plus the handler's results; the handler runs unprotected
  • error(v [, level]) — raise v as an error object; level is validated but ignored (no source positions)
  • assert(v [, message, ...]) — returns all arguments when v is truthy; otherwise raises an error with message (default "assertion failed!")
  • coroutine.create(f) / coroutine.resume(co, ...) / coroutine.yield(...) / coroutine.status(co) / coroutine.wrap(co) / coroutine.isyieldable() / coroutine.running() / coroutine.close(co) — real coroutines on the bytecode VM (a pool of 3; type(co) is "thread")
  • dhcp / dhcp() — set up the network (e1000 + DHCP); prints the negotiated MAC/IP/subnet/gateway/TFTP server/bootfile, sets the mac, ip, subnet, gateway, server, bootfile, tftp_port (default 69) globals, and enables fetch() and dofile()
  • fetch("file"[, "dest"]) — download a file from the TFTP/HTTP/HTTPS server (optional tftp:///http:///https:// prefix; defaults to tftp://), saving it under the optional local dest name, and return its byte count (or nil) (HTTP/HTTPS on native; HTTPS on UEFI via firmware stack)
  • dofile("file.lua") — load a Lua chunk from the TFTP server, run it, and return its value
  • ls / ls() — list the files downloaded with fetch(), as name (N bytes)
  • exit — leave the shell; shell() — nested shell (not supported)

On the native target, dhcp discovers the local IP and TFTP server from the kernel instead of running DHCP itself.

The same shell runs on the host without the boot menu: make lua builds bin/lua. ./bin/lua starts the shell directly, and ./bin/lua script.lua runs a script (exit status 0 on success, 1 on error, with the error message on stderr) — handy for testing Lua scripts outside QEMU.

Network Support

Target NIC Method DHCP PXE
BIOS Rust stage2 (disk) e1000 PCI I/O ports + MMIO Full DHCP (DISCOVER→OFFER) TFTP download + execute (ELF32/Multiboot/.lua)
BIOS (option ROM) e1000 PCI option ROM with PCIR header e1000 I/O BAR driver (real hardware only) TFTP download + execute (ELF32/Multiboot)
x86_64 UEFI (disk) e1000 SNP protocol Full DHCP (DISCOVER/OFFER/REQUEST/ACK) TFTP download + execute (PE/COFF/.lua)
x86_64 UEFI (option ROM) e1000 Direct MMIO + I/O port PCI scan Full DHCP (DISCOVER→OFFER) TFTP download + execute (PE/COFF)
ARM64 UEFI e1000 Direct PCI scan + MMIO Full DHCP (DISCOVER→OFFER) TFTP download + execute (PE/COFF/.lua)
ARM64 bare-metal e1000 PCI ECAM + MMIO Full DHCP (DISCOVER→OFFER) TFTP download + execute (ELF64/.lua)
Native Linux kernel std UDP sockets Kernel handles it (dhcp reads routing) TFTP download + run .lua / display

PXE Boot

All targets support PXE boot via DHCP options 66 (TFTP server) and 67 (bootfile name). When these options are present in the DHCP response, the bootloader automatically:

  1. Downloads the specified file via TFTP (RFC 1350; 512-byte blocks by default, server may shrink the block size via an OACK)
  2. Detects the file format by magic number (PE/COFF, ELF32, ELF64, Multiboot, Multiboot2, text, or binary)
  3. Executes the file if it's a recognized executable format, runs .lua files through the built-in Lua interpreter, or displays it if it's text

.lua scripts can call fetch("file") to pull additional files (e.g. a kernel and initrd) from the same TFTP server; downloaded files are kept in host memory. lua/demo/test.lua is the PXE demo script.

The run-* targets start an OpenWrt VM as the DHCP/TFTP PXE server on 10.0.0.1 (QEMU socket networking), serving the auto-generated tftp-root/ directory. This needs no root privileges and no external TFTP server.

Project Structure

├── common/             # no_std Rust library (print, scan, menu, e1000, dhcp, arp, dns, netio, tftp, loader)
│   └── src/
│       ├── menu.rs     # Shared [1]/[2]/[3] menu logic
│       ├── print.rs    # Callback-based print (putc/puts/print_hex/print_dec/print_ip)
│       ├── scan.rs     # Generic device-scan loop
│       ├── e1000.rs    # Direct MMIO e1000 driver (init/send/recv) — shared by all targets
│       ├── arp.rs      # ARP request build + reply parse
│       ├── dns.rs      # DNS query build + response parse + unicast UDP frame build
│       ├── netio.rs    # e1000 glue: ARP resolve + DNS lookup + dns_resolve_and_print
│       ├── dhcp.rs     # DHCP frame build/parse, IP checksum, DhcpConfig (incl. DNS server, PXE options)
│       ├── tftp.rs     # TFTP client (RFC 1350) with block size negotiation (RFC 2348), streaming transfer
│       └── loader.rs   # File format detection (PE/COFF, ELF32, ELF64, Multiboot, text, binary)
├── lua/                  # Minimal no_std Lua interpreter (no heap, fixed static buffers)
│   ├── demo/test.lua   # PXE demo script (fib, tables, fetch())
│   └── src/
│       ├── lib.rs      # LuaState (~104 KB), values, errors, closures, builtins, host tests
│       ├── lex.rs      # Tokenizer (ints, floats, strings, comments, symbols)
│       ├── parse.rs    # Recursive-descent parser → AST
│       ├── vm.rs       # Bytecode compiler + stack-machine interpreter, coroutines
│       ├── eval.rs     # Runtime helpers + native builtin dispatch (print, pcall, ...)
│       └── repl.rs     # Interactive shell driver (shared by all targets)
├── bios/                 # Rust 32-bit BIOS stage2
│   ├── Cargo.toml
│   ├── link.ld         # Link at 0x100000
│   ├── targets/i386-unknown-none.json
│   └── src/
│       ├── mbr.asm     # 512‑byte MBR stage‑1
│       ├── stage2_entry.nasm # Entry stub for Rust stage2 (A20, protected mode, payload copy)
│       ├── main.rs     # _start entry, menu dispatch
│       ├── serial.rs   # COM1 driver (putc, getc, flush)
│       ├── vga.rs      # VGA text-mode driver with scrolling
│       ├── pci.rs      # PCI scan via I/O ports 0xCF8/0xCFC
│       ├── net.rs      # PCI + e1000 scan, DHCP, PXE boot (thin wrapper over common)
│       ├── mem.rs      # E820 map + TFTP buffer region selection (captured by the stub)
│       ├── fetch.rs    # Lua `dhcp`/`fetch()`/`dofile()` host callbacks (static slots)
│       └── loader.rs   # ELF32/Multiboot execution
├── uefi/               # Rust UEFI binary (x86_64 + ARM64)
│   └── src/
│       ├── efi.rs      # Hand-typed EFI types, GUIDs, function offsets
│       ├── scan.rs     # Storage device enumeration
│       ├── net.rs      # SNP + direct e1000 DHCP client, PXE boot (uses common e1000/dhcp/tftp)
│       ├── mem.rs      # UEFI memory allocation (AllocatePool/FreePool)
│       ├── loader.rs   # PE/COFF execution (LoadImage/StartImage) + `.lua` scripts
│       ├── fetch.rs    # Lua `fetch()` host callback (AllocatePool slots)
│       └── main.rs     # efi_main entry point
├── arm64-bare/         # Rust ARM64 bare‑metal binary
│   └── src/
│       ├── pci.rs      # PCI ECAM walk, BAR sizing, AHCI probe
│       ├── uart.rs     # PL011 UART driver
│       ├── net.rs      # PCI + e1000 scan, DHCP, PXE boot (thin wrapper over common)
│       ├── mem.rs      # Fixed RAM region allocation
│       ├── loader.rs   # ELF64 execution
│       ├── fetch.rs    # Lua `fetch()` host callback (static BSS slots)
│       └── main.rs     # global_asm! entry, UART/PCI init
├── native/             # Native Linux x86_64 binaries (std; kernel handles networking)
│   └── src/
│       ├── lib.rs      # Shared lib (term/net/fetch/cli) backing both binaries
│       ├── term.rs     # Raw-terminal I/O + signal-safe cleanup
│       ├── cli.rs      # Standalone `bin/lua` entry: REPL or run a .lua file
│       ├── bin/lua.rs  # Standalone Lua shell / script runner
│       ├── main.rs     # Menu loop, storage scan via /sys/block
│       ├── net.rs      # Kernel networking: gateway/IP from /proc + std UDP TFTP client
│       └── fetch.rs    # Lua `dhcp` + `fetch()` host callbacks (std Mutex)
├── romwrap/            # CLI tool: wraps PE/COFF into PCI option ROM
├── freedos/            # FreeDOS install resources (bootsect.bin: FAT16 VBR code)
├── install-freedos.sh  # Host-side FreeDOS install into disks/bios.img (make freedos-install)
├── tftp-root/          # Files served via the OpenWrt PXE server (auto-generated)
├── Makefile            # Build orchestration
└── AGENTS.md           # Full development reference & gotchas

Tests

All crates are host‑testable — platform‑specific code is guarded with #[cfg(not(test))].

cargo test --workspace   # 319 tests across all crates
Crate Tests What's Tested
common 98 Hex/decimal formatting, device info, scan loop with mocks, DHCP build/parse (incl. PXE options), ARP build/parse, DNS build/parse, subnet check, TFTP protocol, file format detection
lua 128 Lexer, parser, bytecode compiler + VM, integer & float arithmetic and formatting, next/pairs/rawequal/rawget/rawlen/rawset/select/setmetatable/tonumber/tostring/type/warn/pcall/error/coroutine.*, closures/upvalues (local function, anonymous functions, shared capture, per-iteration loop capture), metamethod dispatch (__index/__newindex/__eq/__lt/__le/__concat/arithmetic/__unm/__call/__tostring/__pairs) + _VERSION, multiple values/assignment, global keyword, string.find, string.format, string.gmatch, string.gsub, string.len, string.lower, string.match, string.pack/unpack, string.rep/reverse/sub/upper, table.*, math.*, io.*, dhcp builtin, demo script output, REPL (echo, fetch, help), state-size guard
uefi 39 EFI type sizes, GUID values, SNP mode layout, constants, PCI IO protocol, boot-option parsers
arm64-bare 21 PCI offset encoding, storage subclass naming
romwrap 24 PCIR layout, BIOS/UEFI code types, entry routine, 512-byte alignment, edge cases
native 2 /proc/net/route gateway hex decode, TFTP RRQ build
bios 7 E820 region selection for the TFTP buffer (min/4 GB bounds, filtering, largest region)

Requirements

  • Rust with targets: x86_64-unknown-uefi, aarch64-unknown-uefi, aarch64-unknown-none, i386-bios (needs nightly -Zjson-target-spec and -Zbuild-std=core)
  • BIOS: nasm and objcopy (for assembling the MBR/entry stub and stripping the ELF to a flat binary)
  • Testing: qemu-system-x86_64 (with OVMF), qemu-system-aarch64 (with QEMU_EFI.fd)
  • FreeDOS install (make freedos-install): mtools (mformat/mcopy) and bsdtar (libarchive) to extract files from the LiveCD ISO
  • Native target: no extra dependencies (std only); an OpenWrt VM image is fetched by setup-openwrt.sh for the run-* PXE server

About

Rust-based multi-platform bootloader

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages