Testing UEFI apps in the U-Boot sandbox

  1. Building the U-Boot sandbox
  2. Running an UEFI binary
  3. Sources

This is a neat trick I learnt recently while experimenting with building a small EFI bootloader shim. Most guides will recommend testing using qemu and EDK2, but there actually is an even simpler way to test (some) EFI applications entirely without qemu, dedicated test hardware or constant rebooting.

U-Boot can be compiled as a simple user-space Linux executable using the "sandbox" pseudo-architecture. This is mostly intended for the U-Boot test suite but we can also use it as a simple user-space UEFI test environment.

Building the U-Boot sandbox

Building the sandbox binary is as easy as fetching the source code from https://github.com/u-boot/u-boot , selecting the Sandbox configuration and building the binary via:

$ make sandbox_defconfig all

The resulting u-boot file is a regular ELF Linux binary. Running it with -h gives us a list of supported command line options.

$ ./u-boot -h
U-Boot 2024.07-rc2 (May 19 2024 - 17:27:46 +0200)

u-boot, a command line test interface to U-Boot

Usage: u-boot [options]
Options:
      --autoboot_keyed           Allow keyed autoboot
  -b, --boot                     Run distro boot commands
  -c, --command          <arg>   Execute U-Boot command
  -d, --fdt              <arg>   Specify U-Boot's control FDT
  -D, --default_fdt              Use the default u-boot.dtb control FDT in U-Boot directory
  -h, --help                     Display help
  -i, --interactive              Enter interactive mode
  -j, --jump             <arg>   Jumped from previous U-Boot
  -k, --select_unittests <arg>   Select unit tests to run
  -K, --double_lcd               Double the LCD display size in each direction
  -l, --show_lcd                 Show the sandbox LCD display
  -L, --log_level        <arg>   Set log level (0=panic, 7=debug)
  -m, --memory           <arg>   Read/write ram_buf memory contents from file
  -n, --ignore_missing           Ignore missing state on read
  -p, --program          <arg>   U-Boot program name
  -r, --read                     Read the state FDT on startup
      --rm_memory                Remove memory file after reading
  -s, --state            <arg>   Specify the sandbox state FDT
  -S, --signals                  Handle signals (such as SIGSEGV) in sandbox
  -t, --terminal         <arg>   Set terminal to raw/cooked mode
  -T, --test_fdt                 Use the test.dtb control FDT in U-Boot directory
  -u, --unittests                Run unit tests
  -v, --verbose                  Show test output
  -w, --write                    Write state FDT on exit

More info on sandbox mode and other interesting configuration options can be found in the official U-Boot documentation at [1].

Running an UEFI binary

Running the binary without arguments allows us to access a regular U-Boot shell, load files and execute them via UEFI. The last missing puzzle piece is getting the binary from the host file system into U-Boot. For this we can use the host interface documented at [2].

host load allows as to load a file from the host file system directly into U-Boot's address space. The easiest way to load and execute a binary is loading it to the pre-defined kernel_addr_r address and then executing it via bootefi.

For simplicity we use the helloworld.efi application that ships with U-Boot:

=> host load hostfs - $kernel_addr_r lib/efi_loader/helloworld.efi
5632 bytes read in 0 ms
=> bootefi $kernel_addr_r
No EFI system partition
No EFI system partition
Failed to persist EFI variables
Missing TPMv2 device for EFI_TCG_PROTOCOL
Missing RNG device for EFI_RNG_PROTOCOL
Booting /lib\efi_loader\helloworld.efi
Hello, world!
Running on UEFI 2.10
Have ACPI 2.0 table
Load options: 
File path: /lib\efi_loader\helloworld.efi
Missing device handle
=>

And there we have our "Hello, world!". Of course we can also run more complex applications as long as they work in U-Boot. Even graphical EFI apps should not be a problem thanks to built-in SDL support.

Finally, to make things even easier we can use the -c flag to pass those commands directly to u-boot and skip the interactive session entirely:

$ ./u-boot -c "host load hostfs - \$kernel_addr_r lib/efi_loader/helloworld.efi; bootefi \$kernel_addr_r"

Sources

[1] U-Boot - Sandbox Documentation
[2] U-Boot - host command