BGGP23
eight kilos and no swap

a screenshot of the result
While I think that golf should be abolished, I think binary golf is pretty neat. So I participated in this year’s Binary Golf Grand Prix. And I didn’t win.
But at least you’ll learn how to write a small UEFI application in Rust.
what are we going to do?
This year’s BGGP had the following objective:
A valid submission will:
Produce exactly 1 copy of itself.
Name the copy “4”.
Not execute the copied file.
Print, return, or display the number 4.
A program doing this could be implemented in many different file formats1, I chose “UEFI app” because I know the stack quite well by now, I think.
make it work
The first thing we’ll need to do is to create an application that prints 4
:
rustup target add x86_64-unknown-uefi cargo new --bin four cargo add uefi cargo add uefi-services
Put the following in .cargo/config.toml
:
[build] target = "x86_64-unknown-uefi"
and the following in src/main.rs
#![no_std] #![no_main] use uefi::prelude::*; use uefi_services::println; #[entry] fn efi_main(image: Handle, mut systab: SystemTable<Boot>) -> Status { uefi_services::init(&mut systab).unwrap(); println!("4"); Status::SUCCESS }
That was easy, right? Let’s see whether this actually works:
cargo build cargo install uefi-run uefi-run target/x86_64-unknown-uefi/debug/four.efi
Cool, right?
Now let’s copy the file:
cargo add uefi --features alloc
#![no_std] #![no_main] use uefi::{ prelude::*, proto::{ device_path::text::{AllowShortcuts, DisplayOnly}, loaded_image::LoadedImage, }, }; use uefi_services::println; #[entry] fn efi_main(image: Handle, mut systab: SystemTable<Boot>) -> Status { uefi_services::init(&mut systab).unwrap(); let mut fs = systab.boot_services().get_image_file_system(image).unwrap(); let loaded_image = systab .boot_services() .open_protocol_exclusive::<LoadedImage>(image) .unwrap(); let src_path = loaded_image .file_path() .unwrap() .to_string( systab.boot_services(), DisplayOnly(false), AllowShortcuts(false), ) .unwrap() .unwrap(); fs.copy(&*src_path, cstr16!("4")).unwrap(); println!("4"); Status::SUCCESS }
So, this works. This is the MVP. And it is 50kB.
making it smaller
The easiest thing to do is to simply ask the Rust compiler to produce small
binaries. Put the following into .config/cargo.toml
:
[build] target = "x86_64-unknown-uefi" # see https://github.com/johnthagen/min-sized-rust [profile.release] opt-level = "z" # Optimize for size. lto = true codegen-units = 1 panic = "abort"
Now we’re down to 40kB (and we have worse error handling).
Why is this not getting smaller? Mostly because of the standard library. It comes prebuilt, it is not affected by our build-configuration. We could rebuild it, but we can’t.
who needs features anyway?
Let’s take a look at all dependencies and the features we’re using:
$ cargo tree --edges features four v0.1.0 ├── uefi feature "alloc" │ └── uefi v0.24.0 │ ├── log v0.4.19 │ ├── ptr_meta v0.2.0 │ │ └── ptr_meta_derive feature "default" │ │ └── ptr_meta_derive v0.2.0 (proc-macro) │ │ ├── proc-macro2 feature "default" │ │ │ ├── proc-macro2 v1.0.60 │ │ │ │ └── unicode-ident feature "default" │ │ │ │ └── unicode-ident v1.0.9 │ │ │ └── proc-macro2 feature "proc-macro" │ │ │ └── proc-macro2 v1.0.60 (*) │ │ ├── quote feature "default" │ │ │ ├── quote v1.0.28 │ │ │ │ └── proc-macro2 v1.0.60 (*) │ │ │ └── quote feature "proc-macro" │ │ │ ├── quote v1.0.28 (*) │ │ │ └── proc-macro2 feature "proc-macro" (*) │ │ ├── syn feature "default" │ │ │ ├── syn v1.0.109 │ │ │ │ ├── proc-macro2 v1.0.60 (*) │ │ │ │ ├── quote v1.0.28 (*) │ │ │ │ └── unicode-ident feature "default" (*) │ │ │ ├── syn feature "clone-impls" │ │ │ │ └── syn v1.0.109 (*) │ │ │ ├── syn feature "derive" │ │ │ │ └── syn v1.0.109 (*) │ │ │ ├── syn feature "parsing" │ │ │ │ └── syn v1.0.109 (*) │ │ │ ├── syn feature "printing" │ │ │ │ ├── syn v1.0.109 (*) │ │ │ │ └── syn feature "quote" │ │ │ │ └── syn v1.0.109 (*) │ │ │ └── syn feature "proc-macro" │ │ │ ├── syn v1.0.109 (*) │ │ │ ├── proc-macro2 feature "proc-macro" (*) │ │ │ ├── quote feature "proc-macro" (*) │ │ │ └── syn feature "quote" (*) │ │ └── syn feature "full" │ │ └── syn v1.0.109 (*) │ ├── bitflags feature "default" │ │ └── bitflags v2.3.2 │ ├── ucs2 feature "default" │ │ └── ucs2 v0.3.2 │ │ └── bit_field feature "default" │ │ └── bit_field v0.10.2 │ ├── uefi-macros feature "default" │ │ └── uefi-macros v0.12.0 (proc-macro) │ │ ├── proc-macro2 feature "default" (*) │ │ ├── quote feature "default" (*) │ │ ├── syn feature "default" │ │ │ ├── syn v2.0.20 │ │ │ │ ├── proc-macro2 v1.0.60 (*) │ │ │ │ ├── quote v1.0.28 (*) │ │ │ │ └── unicode-ident feature "default" (*) │ │ │ ├── syn feature "clone-impls" │ │ │ │ └── syn v2.0.20 (*) │ │ │ ├── syn feature "derive" │ │ │ │ └── syn v2.0.20 (*) │ │ │ ├── syn feature "parsing" │ │ │ │ └── syn v2.0.20 (*) │ │ │ ├── syn feature "printing" │ │ │ │ ├── syn v2.0.20 (*) │ │ │ │ └── syn feature "quote" │ │ │ │ └── syn v2.0.20 (*) │ │ │ └── syn feature "proc-macro" │ │ │ ├── syn v2.0.20 (*) │ │ │ ├── proc-macro2 feature "proc-macro" (*) │ │ │ ├── quote feature "proc-macro" (*) │ │ │ └── syn feature "quote" (*) │ │ └── syn feature "full" │ │ └── syn v2.0.20 (*) │ ├── uefi-raw feature "default" │ │ └── uefi-raw v0.3.0 │ │ ├── ptr_meta v0.2.0 (*) │ │ ├── bitflags feature "default" (*) │ │ └── uguid feature "default" │ │ └── uguid v2.0.0 │ └── uguid feature "default" (*) ├── uefi feature "default" │ ├── uefi v0.24.0 (*) │ └── uefi feature "panic-on-logger-errors" │ └── uefi v0.24.0 (*) └── uefi-services feature "default" ├── uefi-services v0.21.0 │ ├── log v0.4.19 │ ├── uefi feature "default" (*) │ ├── uefi feature "global_allocator" │ │ └── uefi v0.24.0 (*) │ └── cfg-if feature "default" │ └── cfg-if v1.0.0 ├── uefi-services feature "logger" │ ├── uefi-services v0.21.0 (*) │ └── uefi feature "logger" │ └── uefi v0.24.0 (*) └── uefi-services feature "panic_handler" └── uefi-services v0.21.0 (*)
Do we actually need all of them?
No. We can get rid of the alloc
feature and of uefi-services
if we assume
that everything is in the top-level directory, don’t use println!
and write a
panic handler:
cargo remove uefi-services cargo remove uefi cargo add uefi --no-default-features
#![no_std] #![no_main] #![feature(panic_info_message)] use core::{mem::MaybeUninit, panic::PanicInfo}; use uefi::{ prelude::*, proto::{ device_path::media::FilePath, loaded_image::LoadedImage, media::{ file::{File, FileAttribute, FileMode}, fs::SimpleFileSystem, }, }, }; #[entry] fn efi_main(image: Handle, mut systab: SystemTable<Boot>) -> Status { copy(image, &systab); systab.stdout().output_string(cstr16!("4\n")).unwrap(); Status::SUCCESS } fn copy(image: Handle, systab: &SystemTable<Boot>) { let loaded_image = systab .boot_services() .open_protocol_exclusive::<LoadedImage>(image) .unwrap(); let mut fs = systab .boot_services() .open_protocol_exclusive::<SimpleFileSystem>(loaded_image.device()) .unwrap(); let src_path = loaded_image.file_path().unwrap(); let mut dir = fs.open_volume().unwrap(); let first_path_entry: &FilePath = src_path.node_iter().next().unwrap().try_into().unwrap(); let mut file_name_buffer: [MaybeUninit<u16>; 256] = [MaybeUninit::uninit(); 256]; let mut src_file = dir .open( first_path_entry .path_name() .to_cstr16(&mut file_name_buffer) .unwrap(), FileMode::Read, FileAttribute::empty(), ) .unwrap() .into_regular_file() .unwrap(); let mut dst_file = dir .open( cstr16!("4"), FileMode::CreateReadWrite, FileAttribute::empty(), ) .unwrap() .into_regular_file() .unwrap(); let mut data_buffer: [u8; 4] = [0; 4]; loop { let read_count = src_file.read(&mut data_buffer).unwrap(); if read_count == 0 { break; } dst_file.write(&data_buffer).unwrap(); } } #[panic_handler] fn panic(_info: &PanicInfo) -> ! { loop {} }
Sure, this is worse code, but it has less dependencies:
$ cargo tree --edges features four v0.1.0 └── uefi v0.24.0 ├── log v0.4.19 ├── ptr_meta v0.2.0 │ └── ptr_meta_derive feature "default" │ └── ptr_meta_derive v0.2.0 (proc-macro) │ ├── proc-macro2 feature "default" │ │ ├── proc-macro2 v1.0.60 │ │ │ └── unicode-ident feature "default" │ │ │ └── unicode-ident v1.0.9 │ │ └── proc-macro2 feature "proc-macro" │ │ └── proc-macro2 v1.0.60 (*) │ ├── quote feature "default" │ │ ├── quote v1.0.28 │ │ │ └── proc-macro2 v1.0.60 (*) │ │ └── quote feature "proc-macro" │ │ ├── quote v1.0.28 (*) │ │ └── proc-macro2 feature "proc-macro" (*) │ ├── syn feature "default" │ │ ├── syn v1.0.109 │ │ │ ├── proc-macro2 v1.0.60 (*) │ │ │ ├── quote v1.0.28 (*) │ │ │ └── unicode-ident feature "default" (*) │ │ ├── syn feature "clone-impls" │ │ │ └── syn v1.0.109 (*) │ │ ├── syn feature "derive" │ │ │ └── syn v1.0.109 (*) │ │ ├── syn feature "parsing" │ │ │ └── syn v1.0.109 (*) │ │ ├── syn feature "printing" │ │ │ ├── syn v1.0.109 (*) │ │ │ └── syn feature "quote" │ │ │ └── syn v1.0.109 (*) │ │ └── syn feature "proc-macro" │ │ ├── syn v1.0.109 (*) │ │ ├── proc-macro2 feature "proc-macro" (*) │ │ ├── quote feature "proc-macro" (*) │ │ └── syn feature "quote" (*) │ └── syn feature "full" │ └── syn v1.0.109 (*) ├── bitflags feature "default" │ └── bitflags v2.3.2 ├── ucs2 feature "default" │ └── ucs2 v0.3.2 │ └── bit_field feature "default" │ └── bit_field v0.10.2 ├── uefi-macros feature "default" │ └── uefi-macros v0.12.0 (proc-macro) │ ├── proc-macro2 feature "default" (*) │ ├── quote feature "default" (*) │ ├── syn feature "default" │ │ ├── syn v2.0.20 │ │ │ ├── proc-macro2 v1.0.60 (*) │ │ │ ├── quote v1.0.28 (*) │ │ │ └── unicode-ident feature "default" (*) │ │ ├── syn feature "clone-impls" │ │ │ └── syn v2.0.20 (*) │ │ ├── syn feature "derive" │ │ │ └── syn v2.0.20 (*) │ │ ├── syn feature "parsing" │ │ │ └── syn v2.0.20 (*) │ │ ├── syn feature "printing" │ │ │ ├── syn v2.0.20 (*) │ │ │ └── syn feature "quote" │ │ │ └── syn v2.0.20 (*) │ │ └── syn feature "proc-macro" │ │ ├── syn v2.0.20 (*) │ │ ├── proc-macro2 feature "proc-macro" (*) │ │ ├── quote feature "proc-macro" (*) │ │ └── syn feature "quote" (*) │ └── syn feature "full" │ └── syn v2.0.20 (*) ├── uefi-raw feature "default" │ └── uefi-raw v0.3.0 │ ├── ptr_meta v0.2.0 (*) │ ├── bitflags feature "default" (*) │ └── uguid feature "default" │ └── uguid v2.0.0 └── uguid feature "default" (*)
And we’re down to 13kB. What is there left to do? Well, we can pack it:
$ upx --best target/x86_64-unknown-uefi/release/four.efi Ultimate Packer for eXecutables Copyright (C) 1996 - 2023 UPX 4.0.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 30th 2023 File size Ratio Format Name -------------------- ------ ----------- ----------- 12800 -> 7168 56.00% win64/pe four.efi Packed 1 file.
And this is it: We’re down to roughly 7kB.
-
Some of them aren’t even executables. ↩