Writing UEFI application in Rust from scratch

This is an English version of my blog post. The original version is here (Japanese).

Recently, x86_64-unknown-uefi target was added into Rust mainline (https://github.com/rust-lang/rust/pull/56769).

So, I tried to write UEFI application with this update.

There exists an awesome crate, uefi-rs, which provides Rust interface for UEFI application.

However, this is my first time to write UEFI application, so to understand what happens in it, I didn’t use any existing crate.

What is UEFI?

UEFI is a software interface for platform firmware and replaces BIOS. For more detail, please refer Wikipedia, or there are a lot of existing examples developing in C.

The latest specification is available here: Unified Extensible Firmware Interface Forum
I followed version 2.7.

Preparation

The target architecture here is x86_64. First, we need to use nigtly Rust compiler including x86_64-unknown-uefi target. Also, to include sysroot libraries, cargo-xbuild is necessary.

To run your UEFI application in your local machine, you need to get qemu-system-x86_64 and its UEFI firmware OVMF.

To get OVMF, it is better to download the built image from https://www.kraxel.org/repos/. (You can build it from source code as well, but it will take a long time). You need to get OVMF_VARS-pure-efi.fd and OVMF_CODE-pure-efi.fd from the rpm for x64 in jenkins/edk2 directory.

Result

Here is my final result for hello world application on UEFI.

To run this application on qemu, you need to put OVMF_VARS.fd and OVMF_CODE.fd on the project root directory. Then, you can start the UEFI shell by following commands:

And, you can start the application by following commands:

Now, you will see “Hello World”.

How it works

efi_main function is the entry point. It correspond to EFI_IMAGE_ENTRY_POINT in the UEFI specification. This function takes two arguments and return EFI_STATUS. Please refer to the specification for more detail.

There are code snippets written in C to declare the interface types. We need to write down them in Rust. However, Rust and C ABI are different. So, we need to attach repr attribute to make sure the compatiblity. Rustonomicon provides the detail for it in Data Layout section.

My goal was to display “hello world”, so I use EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL to put text. This protocol includes reset ant output_string functions, so I used these. However, this protocol and other structs include more interface, actually. I declared only what I needed, and declared as usize for unused functions. It is hard to write down all interfaces.

In UEFI, we need to encode all string as UCS-2, however, in Rust the default string is encoded as UFT-8 and there is no support to convert it to UCS-2. Each character in UCS-2 is 16bit, so I prepared some u16 array buffer and convert the string on it (It is the same way as uefi-rs).

Finally, we need to provide panic_handler on no_std setting.

Conclusion

It is too hard to implement all UEFI specification. So, let’s use uefi-rs.

garasubo

Programmer.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store