|
| 1 | +# `*-unknown-uefi` |
| 2 | + |
| 3 | +**Tier: 3** |
| 4 | + |
| 5 | +Unified Extensible Firmware Interface (UEFI) targets for application, driver, |
| 6 | +and core UEFI binaries. |
| 7 | + |
| 8 | +Available targets: |
| 9 | + |
| 10 | +- `aarch64-unknown-uefi` |
| 11 | +- `i686-unknown-uefi` |
| 12 | +- `x86_64-unknown-uefi` |
| 13 | + |
| 14 | +## Target maintainers |
| 15 | + |
| 16 | +- David Rheinsberg ([@dvdhrm](https://github.com/dvdhrm)) |
| 17 | +- Nicholas Bishop ([@nicholasbishop](https://github.com/nicholasbishop)) |
| 18 | + |
| 19 | +## Requirements |
| 20 | + |
| 21 | +All UEFI targets can be used as `no-std` environments via cross-compilation. |
| 22 | +Support for `std` is missing, but actively worked on. `alloc` is supported if |
| 23 | +an allocator is provided by the user. No host tools are supported. |
| 24 | + |
| 25 | +The UEFI environment resembles the environment for Microsoft Windows, with some |
| 26 | +minor differences. Therefore, cross-compiling for UEFI works with the same |
| 27 | +tools as cross-compiling for Windows. The target binaries are PE32+ encoded, |
| 28 | +the calling convention is different for each architecture, but matches what |
| 29 | +Windows uses (if the architecture is supported by Windows). The special |
| 30 | +`efiapi` Rust calling-convention chooses the right ABI for the target platform |
| 31 | +(`extern "C"` is incorrect on Intel targets at least). The specification has an |
| 32 | +elaborate section on the different supported calling-conventions, if more |
| 33 | +details are desired. |
| 34 | + |
| 35 | +MMX, SSE, and other FP-units are disabled by default, to allow for compilation |
| 36 | +of core UEFI code that runs before they are set up. This can be overridden for |
| 37 | +individual compilations via rustc command-line flags. Not all firmwares |
| 38 | +correctly configure those units, though, so careful inspection is required. |
| 39 | + |
| 40 | +As native to PE32+, binaries are position-dependent, but can be relocated at |
| 41 | +runtime if their desired location is unavailable. The code must be statically |
| 42 | +linked. Dynamic linking is not supported. Code is shared via UEFI interfaces, |
| 43 | +rather than dynamic linking. Additionally, UEFI forbids running code on |
| 44 | +anything but the boot CPU/thread, nor is interrupt-usage allowed (apart from |
| 45 | +the timer interrupt). Device drivers are required to use polling methods. |
| 46 | + |
| 47 | +UEFI uses a single address-space to run all code in. Multiple applications can |
| 48 | +be loaded simultaneously and are dispatched via cooperative multitasking on a |
| 49 | +single stack. |
| 50 | + |
| 51 | +By default, the UEFI targets use the `link`-flavor of the LLVM linker `lld` to |
| 52 | +link binaries into the final PE32+ file suffixed with `*.efi`. The PE subsystem |
| 53 | +is set to `EFI_APPLICATION`, but can be modified by passing `/subsystem:<...>` |
| 54 | +to the linker. Similarly, the entry-point is to to `efi_main` but can be |
| 55 | +changed via `/entry:<...>`. The panic-strategy is set to `abort`, |
| 56 | + |
| 57 | +The UEFI specification is available online for free: |
| 58 | +[UEFI Specification Directory](https://uefi.org/specifications) |
| 59 | + |
| 60 | +## Building rust for UEFI targets |
| 61 | + |
| 62 | +Rust can be built for the UEFI targets by enabling them in the `rustc` build |
| 63 | +configuration. Note that you can only build the standard libraries. The |
| 64 | +compiler and host tools currently cannot be compiled for UEFI targets. A sample |
| 65 | +configuration would be: |
| 66 | + |
| 67 | +```toml |
| 68 | +[build] |
| 69 | +build-stage = 1 |
| 70 | +target = ["x86_64-unknown-uefi"] |
| 71 | +``` |
| 72 | + |
| 73 | +## Building Rust programs |
| 74 | + |
| 75 | +Rust does not yet ship pre-compiled artifacts for this target. To compile for |
| 76 | +this target, you will either need to build Rust with the target enabled (see |
| 77 | +"Building rust for UEFI targets" above), or build your own copy of `core` by |
| 78 | +using `build-std`, `cargo-buildx`, or similar. |
| 79 | + |
| 80 | +A native build with the unstable `build-std`-feature can be achieved via: |
| 81 | + |
| 82 | +```sh |
| 83 | +cargo +nightly build \ |
| 84 | + -Zbuild-std=core,compiler_builtins \ |
| 85 | + -Zbuild-std-features=compiler-builtins-mem \ |
| 86 | + --target x86_64-unknown-uefi |
| 87 | +``` |
| 88 | + |
| 89 | +Alternatively, you can install `cargo-xbuild` via |
| 90 | +`cargo install --force cargo-xbuild` and build for the UEFI targets via: |
| 91 | + |
| 92 | +```sh |
| 93 | +cargo \ |
| 94 | + +nightly \ |
| 95 | + xbuild \ |
| 96 | + --target x86_64-unknown-uefi |
| 97 | +``` |
| 98 | + |
| 99 | +## Testing |
| 100 | + |
| 101 | +UEFI applications can be copied into the ESP on any UEFI system and executed |
| 102 | +via the firmware boot menu. The qemu suite allows emulating UEFI systems and |
| 103 | +executing UEFI applications as well. See its documentation for details. |
| 104 | + |
| 105 | +The [uefi-run](https://github.com/Richard-W/uefi-run) rust tool is a simple |
| 106 | +wrapper around `qemu` that can spawn UEFI applications in qemu. You can install |
| 107 | +it via `cargo install uefi-run` and execute qemu applications as |
| 108 | +`uefi-run ./application.efi`. |
| 109 | + |
| 110 | +## Cross-compilation toolchains and C code |
| 111 | + |
| 112 | +There are 3 common ways to compile native C code for UEFI targets: |
| 113 | + |
| 114 | +- Use the official SDK by Intel: |
| 115 | + [Tianocore/EDK2](https://github.com/tianocore/edk2). This supports a |
| 116 | + multitude of platforms, comes with the full specification transposed into C, |
| 117 | + lots of examples and build-system integrations. This is also the only |
| 118 | + officially supported platform by Intel, and is used by many major firmware |
| 119 | + implementations. Any code compiled via the SDK is compatible to rust binaries |
| 120 | + compiled for the UEFI targets. You can link them directly into your rust |
| 121 | + binaries, or call into each other via UEFI protocols. |
| 122 | +- Use the **GNU-EFI** suite. This approach is used by many UEFI applications |
| 123 | + in the Linux/OSS ecosystem. The GCC compiler is used to compile ELF binaries, |
| 124 | + and linked with a pre-loader that converts the ELF binary to PE32+ |
| 125 | + **at runtime**. You can combine such binaries with the rust UEFI targets only |
| 126 | + via UEFI protocols. Linking both into the same executable will fail, since |
| 127 | + one is an ELF executable, and one a PE32+. If linking to **GNU-EFI** |
| 128 | + executables is desired, you must compile your rust code natively for the same |
| 129 | + GNU target as **GNU-EFI** and use their pre-loader. This requires careful |
| 130 | + consideration about which calling-convention to use when calling into native |
| 131 | + UEFI protocols, or calling into linked **GNU-EFI** code (similar to how these |
| 132 | + differences need to be accounted for when writing **GNU-EFI** C code). |
| 133 | +- Use native Windows targets. This means compiling your C code for the Windows |
| 134 | + platform as if it was the UEFI platform. This works for static libraries, but |
| 135 | + needs adjustments when linking into an UEFI executable. You can, however, |
| 136 | + link such static libraries seemlessly into rust code compiled for UEFI |
| 137 | + targets. Be wary of any includes that are not specifically suitable for UEFI |
| 138 | + targets (especially the C standard library includes are not always |
| 139 | + compatible). Freestanding compilations are recommended to avoid |
| 140 | + incompatibilites. |
| 141 | + |
| 142 | +## Ecosystem |
| 143 | + |
| 144 | +The rust language has a long history of supporting UEFI targets. Many crates |
| 145 | +have been developed to provide access to UEFI protocols and make UEFI |
| 146 | +programming more ergonomic in rust. The following list is a short overview (in |
| 147 | +alphabetical ordering): |
| 148 | + |
| 149 | +- **efi**: *Ergonomic Rust bindings for writing UEFI applications*. Provides |
| 150 | + _rustified_ access to UEFI protocols, implements allocators and a safe |
| 151 | + environment to write UEFI applications. |
| 152 | +- **r-efi**: *UEFI Reference Specification Protocol Constants and Definitions*. |
| 153 | + A pure transpose of the UEFI specification into rust. This provides the raw |
| 154 | + definitions from the specification, without any extended helpers or |
| 155 | + _rustification_. It serves as baseline to implement any more elaborate rust |
| 156 | + UEFI layers. |
| 157 | +- **uefi-rs**: *Safe and easy-to-use wrapper for building UEFI apps*. An |
| 158 | + elaborate library providing safe abstractions for UEFI protocols and |
| 159 | + features. It implements allocators and provides an execution environment to |
| 160 | + UEFI applications written in rust. |
| 161 | +- **uefi-run**: *Run UEFI applications*. A small wrapper around _qemu_ to spawn |
| 162 | + UEFI applications in an emulated `x86_64` machine. |
| 163 | + |
| 164 | +## Example: Freestanding |
| 165 | + |
| 166 | +The following code is a valid UEFI application returning immediately upon |
| 167 | +execution with an exit code of 0. A panic handler is provided. This is executed |
| 168 | +by rust on panic. For simplicity, we simply end up in an infinite loop. |
| 169 | + |
| 170 | +Note that as of rust-1.31.0, all features used here are stabilized. No unstable |
| 171 | +features are required, nor do we rely on nightly compilers. However, if you do |
| 172 | +not compile rustc for the UEFI targets, you need a nightly compiler to support |
| 173 | +the `-Z build-std` flag. |
| 174 | + |
| 175 | +This example can be compiled as binary crate via `cargo`: |
| 176 | + |
| 177 | +```sh |
| 178 | +cargo +nightly build \ |
| 179 | + -Zbuild-std=core,compiler_builtins \ |
| 180 | + -Zbuild-std-features=compiler-builtins-mem \ |
| 181 | + --target x86_64-unknown-uefi |
| 182 | +``` |
| 183 | + |
| 184 | +```rust,ignore (platform-specific,eh-personality-is-unstable) |
| 185 | +#![no_main] |
| 186 | +#![no_std] |
| 187 | +
|
| 188 | +#[panic_handler] |
| 189 | +fn panic_handler(_info: &core::panic::PanicInfo) -> ! { |
| 190 | + loop {} |
| 191 | +} |
| 192 | +
|
| 193 | +#[export_name = "efi_main"] |
| 194 | +pub extern "C" fn main(_h: *mut core::ffi::c_void, _st: *mut core::ffi::c_void) -> usize { |
| 195 | + 0 |
| 196 | +} |
| 197 | +``` |
| 198 | + |
| 199 | +## Example: Hello World |
| 200 | + |
| 201 | +This is an example UEFI application that prints "Hello World!", then waits for |
| 202 | +key input before it exits. It serves as base example how to write UEFI |
| 203 | +applications without any helper modules other than the standalone UEFI protocol |
| 204 | +definitions provided by the `r-efi` crate. |
| 205 | + |
| 206 | +This extends the "Freestanding" example and builds upon its setup. See there |
| 207 | +for instruction how to compile this as binary crate. |
| 208 | + |
| 209 | +Note that UEFI uses UTF-16 strings. Since rust literals are UTF-8, we have to |
| 210 | +use an open-coded, zero-terminated, UTF-16 array as argument to |
| 211 | +`output_string()`. Similarly to the panic handler, real applications should |
| 212 | +rather use UTF-16 modules. |
| 213 | + |
| 214 | +```rust,ignore (platform-specific,eh-personality-is-unstable) |
| 215 | +#![no_main] |
| 216 | +#![no_std] |
| 217 | +
|
| 218 | +use r_efi::efi; |
| 219 | +
|
| 220 | +#[panic_handler] |
| 221 | +fn panic_handler(_info: &core::panic::PanicInfo) -> ! { |
| 222 | + loop {} |
| 223 | +} |
| 224 | +
|
| 225 | +#[export_name = "efi_main"] |
| 226 | +pub extern "C" fn main(_h: efi::Handle, st: *mut efi::SystemTable) -> efi::Status { |
| 227 | + let s = [ |
| 228 | + 0x0048u16, 0x0065u16, 0x006cu16, 0x006cu16, 0x006fu16, // "Hello" |
| 229 | + 0x0020u16, // " " |
| 230 | + 0x0057u16, 0x006fu16, 0x0072u16, 0x006cu16, 0x0064u16, // "World" |
| 231 | + 0x0021u16, // "!" |
| 232 | + 0x000au16, // "\n" |
| 233 | + 0x0000u16, // NUL |
| 234 | + ]; |
| 235 | +
|
| 236 | + // Print "Hello World!". |
| 237 | + let r = |
| 238 | + unsafe { ((*(*st).con_out).output_string)((*st).con_out, s.as_ptr() as *mut efi::Char16) }; |
| 239 | + if r.is_error() { |
| 240 | + return r; |
| 241 | + } |
| 242 | +
|
| 243 | + // Wait for key input, by waiting on the `wait_for_key` event hook. |
| 244 | + let r = unsafe { |
| 245 | + let mut x: usize = 0; |
| 246 | + ((*(*st).boot_services).wait_for_event)(1, &mut (*(*st).con_in).wait_for_key, &mut x) |
| 247 | + }; |
| 248 | + if r.is_error() { |
| 249 | + return r; |
| 250 | + } |
| 251 | +
|
| 252 | + efi::Status::SUCCESS |
| 253 | +} |
| 254 | +``` |
0 commit comments