Zero-cost and safe interface to UEFI firmware

Overview

ZFI – Zero-cost and safe interface to UEFI firmware

CI Crates.io

ZFI is a Rust crate for writing a UEFI application with the following goals:

  • Provides base APIs that are almost identical to the UEFI specifications.
  • Provides additional APIs that build on top of the base APIs.
  • Base APIs are zero-cost abstraction over UEFI API.
  • Safe and easy to use.
  • Work on stable Rust.

ZFI supports only single-thread environment, which is the same as UEFI specifications.

Example

#![no_std]
#![no_main]

use alloc::boxed::Box;
use zfi::{pause, println, DebugFile, Image, Status, SystemTable};

extern crate alloc;

#[no_mangle]
extern "efiapi" fn efi_main(image: &'static Image, st: &'static SystemTable) -> Status {
    // This is the only place you need to use unsafe. This must be done immediately after landing
    // here.
    unsafe {
        zfi::init(
            image,
            st,
            Some(|| Box::new(DebugFile::next_to_image("log").unwrap())),
        )
    };

    // Any EFI_HANDLE will be represents by a reference to a Rust type (e.g. image here is a type of
    // Image). Each type that represents EFI_HANDLE provides the methods to access any protocols it
    // is capable for (e.g. you can do image.proto() here to get an EFI_LOADED_IMAGE_PROTOCOL from
    // it). You can download the UEFI specifications for free here: https://uefi.org/specifications
    println!("Hello, world!");
    pause();

    Status::SUCCESS
}

#[cfg(not(test))]
#[panic_handler]
fn panic_handler(info: &core::panic::PanicInfo) -> ! {
    zfi::eprintln!("{info}");
    loop {}
}

#[cfg(not(test))]
#[global_allocator]
static ALLOCATOR: zfi::PoolAllocator = zfi:PoolAllocator;

You can use zfi::main macro if you prefer a less boilerplate:

#![no_std]
#![no_main]

use zfi::{pause, println, Status};

// zfi::main will not enable the debug writer by default. See its documentation to see how to enable
// the debug writer.
#[zfi::main]
fn main() -> Status {
    // Use Image::current() to get the image handle.
    println!("Hello, world!");
    pause();

    Status::SUCCESS
}

To build the above example you need to add a UEFI target to Rust:

rustup target add x86_64-unknown-uefi

Then build with the following command:

cargo build --target x86_64-unknown-uefi

You can grab the EFI file in target/x86_64-unknown-uefi/debug and boot it on a compatible machine.

Integration Testing

ZFI provide zfi-testing crate to help you write the integration tests. This crate must be added as a development dependency, not a standard dependency. You need to install the following tools before you can run the integration tests that use zfi-testing:

Once ready create zfi.toml in the root of your package (the same location as Cargo.toml) with the following content:

[qemu.RUST_TARGET]
bin = "QEMU_BIN"
firmware = "OVMF_CODE"
nvram = "OVMF_VARS"

This file should not commit to the version control because it is specific to your machine. Replace the following placeholders with the appropriate value:

  • RUST_TARGET: name of Rust target you want to run on the QEMU (e.g. x86_64-unknown-uefi).
  • QEMU_BIN: path to the QEMU binary to run your tests. The binary must have the same CPU type as RUST_TARGET. You don't need to specify a full path if the binary can be found in the PATH environment variable (e.g. qemu-system-x86_64).
  • OVMF_CODE: path to OVMF_CODE.fd from OVMF. File must have the same CPU type as RUST_TARGET (e.g. /usr/share/edk2/x64/OVMF_CODE.fd).
  • OVMF_VARS: path to OVMF_VARS.fd from OVMF. File must have the same CPU type as RUST_TARGET (e.g. /usr/share/edk2/x64/OVMF_VARS.fd).

Example:

[qemu.aarch64-unknown-uefi]
bin = "qemu-system-aarch64"
firmware = "/usr/share/AAVMF/AAVMF_CODE.fd"
nvram = "/usr/share/AAVMF/AAVMF_VARS.fd"

[qemu.i686-unknown-uefi]
bin = "qemu-system-i386"
firmware = "/usr/share/edk2/ia32/OVMF_CODE.fd"
nvram = "/usr/share/edk2/ia32/OVMF_VARS.fd"

[qemu.x86_64-unknown-uefi]
bin = "qemu-system-x86_64"
firmware = "/usr/share/edk2/x64/OVMF_CODE.fd"
nvram = "/usr/share/edk2/x64/OVMF_VARS.fd"

Writing Tests

To write an integration test to run on QEMU, put zfi_testing::qemu attribute to your integration test:

use zfi_testing::qemu;

#[test]
#[qemu]
fn proto() {
    use zfi::{str, Image, PathBuf};

    let proto = Image::current().proto();
    let mut path = PathBuf::new();

    if cfg!(target_arch = "x86_64") {
        path.push_media_file_path(str!(r"\EFI\BOOT\BOOTX64.EFI"));
    } else {
        todo!("path for non-x86-64");
    }

    assert_eq!(proto.device().file_system().is_some(), true);
    assert_eq!(*proto.file_path(), path);
}

The code in the function that has zfi_testing::qemu attribute will run on the QEMU. This test can be run in the same way as normal integration tests:

cargo test

Keep in mind that you need to put everything your test needed in the same function because what qemu attribute does is moving your function body into efi_main and run it on QEMU.

Known Issues

  • Any panic (including assertion failed) in your integration test will be show as src/main.rs:L:C. This is a limitation on stable Rust for now.
  • rust-analyzer not report any syntax error. The reason is because qemu attribute replace the whole function body, which mean what rust-analyzer see when running syntax check is the replaced function, not the origial function. Right now there is no way to check if our proc macro being run by rust-analyzer until this issue has been resolved.

Breaking Changes

0.1 to 0.2

  • Path is changed from sized type to unsized type. Any code that cast Path to a raw pointer need to update otherwise you will got a fat pointer, which is Rust specific. You can get a pointer to EFI_DEVICE_PATH_PROTOCOL via Path::as_bytes().
  • FileInfo is changed from sized type to unsized type in the same way as Path.
  • File::info() now return FileInfoBuf instead of Owned<FileInfo> when success.
  • The second parameter of Owned::new() is changed to Dtor.

Example Projects

Commercial Support

Please contact [email protected] if you need commercial support.

License

MIT

You might also like...
Abstreet - Transportation planning and traffic simulation software for creating cities friendlier to walking, biking, and public transit
Abstreet - Transportation planning and traffic simulation software for creating cities friendlier to walking, biking, and public transit

A/B Street Ever been stuck in traffic on a bus, wondering why is there legal street parking instead of a dedicated bus lane? A/B Street is a project t

"putzen" is German and means cleaning. It helps keeping your disk clean of build and dependency artifacts safely.

Putzen "putzen" is German and means cleaning. It helps keeping your disk clean of build and dependency artifacts safely. About In short, putzen solves

Plugins and helpful methods for using sepax2d with Bevy for 2d overlap detection and collision resolution.

bevy_sepax2d Plugins and helpful methods for using sepax2d with Bevy for 2d overlap detection and collision resolution. Compatible Versions bevy bevy_

This is the core library with the full abstraction and implementation of the Minecraft protocol and logic. (Currently WIP)

MineRust (WIP) This is the core library with the full abstraction and implementation of the Minecraft protocol and logic. This project is currently WI

A Rust wrapper and bindings of Allegro 5 game programming library

RustAllegro A thin Rust wrapper of Allegro 5. Game loop example extern crate allegro; extern crate allegro_font; use allegro::*; use allegro_font::*;

RTS game/engine in Rust and WebGPU
RTS game/engine in Rust and WebGPU

What is this? A real time strategy game/engine written with Rust and WebGPU. Eventually it will be able to run in a web browser thanks to WebGPU. This

A curated list of wgpu code and resources.

Awesome wgpu A curated list of wgpu code and resources. PRs welcome. About wgpu https://github.com/gfx-rs/wgpu-rs matrix chat https://matrix.to/#/#wgp

grr and rust-gpu pbr rendering
grr and rust-gpu pbr rendering

grr-gltf Barebone gltf viewer using grr and rust-gpu. Currently only supports a single gltf model! Assets These files need to be downloaded and placed

A no-frills Tetris implementation written in Rust with the Piston game engine, and Rodio for music.
A no-frills Tetris implementation written in Rust with the Piston game engine, and Rodio for music.

rustris A no-frills Tetris implementation written in Rust with the Piston game engine, and Rodio for music. (C) 2020 Ben Cantrick. This code is distri

Comments
  • hide the remaining unsafe part under the macro

    hide the remaining unsafe part under the macro

    Maybe we can hide the remaining unsafe part under the macro, use the macro to generate the real efi_main function, and then call main after processing the unsafe part like _start in C?

    enhancement 
    opened by Aalivexy 4
  • Adds entry proc macro

    Adds entry proc macro

    refer to https://github.com/ultimicro/zfi/issues/1

    I don't quite understand how a crate with this structure is published to the crate, but it does work for now. A simple example:

    #![no_std]
    #![no_main]
    
    use zfi::*;
    
    #[zfi::main]
    fn main(_image: &'static Image, _st: &'static SystemTable) -> Status {
         println!("Hello, world!");
         Status::SUCCESS
    }
    
    #[panic_handler]
    fn panic_handler(info: &core::panic::PanicInfo) -> ! {
         zfi::eprintln!("{info}");
         loop {}
    }
    
    opened by Aalivexy 1
Owner
Ultima Microsystems
Official GitHub organization of Ultima Microsystems
Ultima Microsystems
Quick example of displaying a BMP file with uefi-rs

uefi-bmp Quick example of drawing a bitmap using uefi-rs and tinybmp. Not necessarily the most efficient :) Build and run (may need some modification

Nicholas Bishop 1 Jan 16, 2022
Scuffed UEFI video(bad apple) player

Bad UEFI Another day, another Bad Apple project. Video and audio are loaded from \video.uefiv and \audio.uefia respectively. (when running in QEMU esp

Matic Babnik 4 Nov 8, 2022
UEFI command-line tool for read/write access of variables

UEFI Command Line Tool for Reading/Writing UEFI Variables This tool is a rewritten version of the modded grub with setup_var commands, enhanced with m

null 29 Jan 9, 2023
Rusty Bootkit - UEFI Bootkit in Rust

A UEFI Bootkit in Rust Note: This project is incomplete and work is in progress (W.I.P). A lot of things could be incorrect until this is complete. Wh

null 76 May 8, 2023
A safe, fast and cross-platform 2D component-based game framework written in rust

shura shura is a safe, fast and cross-platform 2D component-based game framework written in rust. shura helps you to manage big games with a component

Andri 28 Jan 17, 2023
Rollback-safe implementations and utilities for Bevy Engine

bevy_roll_safe Rollback-safe implementations and utilities for Bevy Engine. Motivation Some of Bevy's features can't be used in a rollback context (wi

Johan Klokkhammer Helsing 3 Sep 17, 2023
Experimental type-safe geometric algebra for Rust

Projective Geometric Algebra This library is a Rust code generator, generating the mathematics you need for a geometric algebra library. I made it mos

Emil Ernerfeldt 33 Dec 4, 2022
Safe, fully-featured bindings to the Tracy profiler

Complete Rust bindings for the Tracy profiler. Getting Started Just add the following to your Cargo.toml: [dependencies.tracy] package = "tracy_full"

Shaye Garg 12 May 6, 2023
The official command-line interface for the makedeb Package Repository

Mist This is the repository for Mist, the official helper for the makedeb Package Repository. Installation Users have a few options for installing Mis

makedeb 5 Aug 4, 2022
Victorem - easy UDP game server and client framework for creating simple 2D and 3D online game prototype in Rust.

Victorem Easy UDP game server and client framework for creating simple 2D and 3D online game prototype in Rust. Example Cargo.toml [dependencies] vict

Victor Winbringer 27 Jan 7, 2023