├── .gitignore ├── .gdbinit ├── .cargo └── config ├── create_disk.sh ├── Cargo.toml ├── src ├── utils.rs ├── logger.rs └── main.rs ├── LICENSE ├── .github └── workflows │ └── build.yml ├── Cargo.lock ├── README.md └── load-symbols.py /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.gdbinit: -------------------------------------------------------------------------------- 1 | define dbg 2 | source ./load-symbols.py 3 | file 4 | load-symbols $rip "./target/x86_64-unknown-uefi/debug/rust-efi-runtime-driver.efi" 5 | set GDB_ATTACHED = 1 6 | end -------------------------------------------------------------------------------- /.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "x86_64-unknown-uefi" 3 | rustflags = ["-Z", "pre-link-args=/subsystem:efi_runtime_driver"] 4 | 5 | [unstable] 6 | build-std = ["core", "compiler_builtins", "alloc"] 7 | build-std-features = ["compiler-builtins-mem"] -------------------------------------------------------------------------------- /create_disk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ -z "$1" ]; then 3 | profile="debug" 4 | else 5 | profile="release" 6 | fi; 7 | 8 | cp target/x86_64-unknown-uefi/$profile/rust-efi-runtime-driver.efi _efi/EFI/Boot/Bootx64.efi 9 | virt-make-fs --type=vfat --size=24M _efi efi.raw 10 | qemu-img convert -O vmdk efi.raw efi.vmdk -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-efi-runtime-driver" 3 | version = "0.1.0" 4 | authors = ["xitan "] 5 | edition = "2018" 6 | license = "MIT" 7 | categories = [ 8 | "no-std", 9 | "os", 10 | ] 11 | keywords = [ 12 | "efi", 13 | "uefi", 14 | "runtime", 15 | "driver", 16 | ] 17 | readme = "README.md" 18 | homepage = "https://xitan.me" 19 | repository = "https://github.com/x1tan/rust-efi-runtime-driver" 20 | 21 | 22 | [dependencies] 23 | r-efi = "3.1.0" 24 | x86_64 = "0.12.2" 25 | atomic_refcell = "0.1.6" 26 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::error; 2 | 3 | static mut GDB_ATTACHED: bool = false; 4 | 5 | pub fn wait_for_debugger() { 6 | unsafe { 7 | while !GDB_ATTACHED { 8 | asm!("pause"); 9 | } 10 | } 11 | } 12 | 13 | #[lang = "eh_personality"] 14 | fn eh_personality() {} 15 | 16 | #[panic_handler] 17 | fn panic_handler(info: &core::panic::PanicInfo) -> ! { 18 | if let Some(location) = info.location() { 19 | error!( 20 | "[-] Panic in {} at ({}, {}):", 21 | location.file(), 22 | location.line(), 23 | location.column() 24 | ); 25 | if let Some(message) = info.message() { 26 | error!("[-] {}", message); 27 | } 28 | } 29 | 30 | loop {} 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Marcel Meuter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [ push, pull_request ] 3 | jobs: 4 | check: 5 | name: Check 6 | runs-on: ${{ matrix.os }} 7 | strategy: 8 | matrix: 9 | os: [ ubuntu-latest, windows-latest ] 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions-rs/toolchain@v1 13 | with: 14 | profile: minimal 15 | toolchain: nightly 16 | override: true 17 | - run: rustup component add rust-src 18 | - uses: actions-rs/cargo@v1 19 | with: 20 | command: check 21 | clippy: 22 | name: Clippy 23 | runs-on: ${{ matrix.os }} 24 | strategy: 25 | matrix: 26 | os: [ ubuntu-latest, windows-latest ] 27 | steps: 28 | - uses: actions/checkout@v2 29 | - uses: actions-rs/toolchain@v1 30 | with: 31 | profile: minimal 32 | toolchain: nightly 33 | override: true 34 | components: rustfmt, clippy 35 | - run: rustup component add rust-src 36 | - uses: actions-rs/cargo@v1 37 | with: 38 | command: fmt 39 | args: --all -- --check 40 | - uses: actions-rs/cargo@v1 41 | with: 42 | command: clippy 43 | args: -- -D warnings -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "atomic_refcell" 5 | version = "0.1.6" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "3bc31dce067eab974c815a9deb95f6217806de7b53685d7fc31f8ccf3fb2539f" 8 | 9 | [[package]] 10 | name = "bit_field" 11 | version = "0.9.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "ed8765909f9009617974ab6b7d332625b320b33c326b1e9321382ef1999b5d56" 14 | 15 | [[package]] 16 | name = "bitflags" 17 | version = "1.2.1" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 20 | 21 | [[package]] 22 | name = "r-efi" 23 | version = "3.1.0" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "6eaab81f2b90c518541382de3e3a0689a0426d0798d0fdef56f3df69636e83dd" 26 | 27 | [[package]] 28 | name = "rust-efi-runtime-driver" 29 | version = "0.1.0" 30 | dependencies = [ 31 | "atomic_refcell", 32 | "r-efi", 33 | "x86_64", 34 | ] 35 | 36 | [[package]] 37 | name = "x86_64" 38 | version = "0.12.2" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "d5b4b42dbabe13b69023e1a1407d395f1a1a33df76e9a9efdbe303acc907e292" 41 | dependencies = [ 42 | "bit_field", 43 | "bitflags", 44 | ] 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust UEFI Runtime Driver 2 | 3 | ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/x1tan/rust-uefi-runtime-driver/build) 4 | ![GitHub](https://img.shields.io/github/license/x1tan/rust-uefi-runtime-driver) 5 | ![Twitter Follow](https://img.shields.io/twitter/follow/x1tan) 6 | 7 | > Template for UEFI runtime drivers written in Rust with serial logging and debugging support. 8 | 9 | ## Overview 10 | 11 | This projects serves as a foundation for UEFI runtime driver development in Rust. Its equipped with various features to help you get started: 12 | 13 | * Up-to-date Cargo configuration to built a `x86_64-unknown-uefi` target on Rust nightly and link it as a runtime driver 14 | * Logging to serial output 15 | * Debugging support (wait loop until debugger is attached as well as a Python GDB script to locate the PE file in memory) 16 | * Continuous integration with GitHub Actions, minimal dependencies, custom panic handler and more 17 | 18 | For more details regarding the implementation and debugging the driver have a look at my [blog post](https://xitan.me/posts/rust-uefi-runtime-driver/). If you are interested in creating a Rust UEFI application e.g. as your bootloader instead of a runtime driver, I recommend reading ["An EFI App a bit rusty"](https://gil0mendes.io/blog/an-efi-app-a-bit-rusty/) for more information about [uefi-rs](https://github.com/rust-osdev/uefi-rs). 19 | 20 | ## References 21 | 22 | - [UEFI Specification](https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf): Unified Extensible Firmware Interface Specification (v2.8). 23 | - [r-efi](https://github.com/r-efi/r-efis): UEFI Reference Specification Protocol Constants and Definitions. 24 | - [uefi-rs](https://github.com/rust-osdev/uefi-rs): Rust wrapper for UEFI. 25 | 26 | ## License 27 | 28 | [MIT](https://github.com/x1tan/rust-uefi-runtime-driver/blob/master/LICENSE) [@x1tan](https://twitter.com/x1tan) -------------------------------------------------------------------------------- /load-symbols.py: -------------------------------------------------------------------------------- 1 | import gdb 2 | import pefile 3 | import sys 4 | import mmap 5 | import struct 6 | 7 | __all__ = ['LoadSymbols'] 8 | 9 | integer = type(0xffffffffffffffff) 10 | get_string = lambda value: str(gdb.parse_and_eval(value)) if value.startswith('$') else value 11 | get_number = lambda value: integer(gdb.parse_and_eval(value)) 12 | pe_magic = 0x785A4D 13 | 14 | 15 | class LoadSymbols(gdb.Command): 16 | """ 17 | load-symbols
18 | """ 19 | 20 | def __init__(self): 21 | super(LoadSymbols, self).__init__("load-symbols", gdb.COMMAND_USER) 22 | self.dont_repeat() 23 | 24 | def invoke(self, args, from_tty): 25 | argv = gdb.string_to_argv(args) 26 | 27 | # Parse arguments. 28 | address = get_number(argv[0]) 29 | path = get_string(argv[1]) 30 | print(f'{path}: {address:02x}') 31 | 32 | # Find the base address of the PE. 33 | base_address = address & 0xfffffffffffff000 34 | while get_number('*(unsigned int *){}'.format(base_address)) != pe_magic: 35 | base_address -= 0x1000 36 | 37 | # Print base address. 38 | print(f'Base ({path}): {base_address:02x}') 39 | 40 | # Parse PE. 41 | sections = {} 42 | pe = pefile.PE(path) 43 | for section in pe.sections: 44 | name = section.Name.decode().rstrip('\x00') 45 | address = section.VirtualAddress + base_address 46 | if name[0] != '/': 47 | print(f'Section: {name}: {address:02x}') 48 | sections[name] = address 49 | 50 | # Remove previous symbol file. 51 | try: 52 | gdb.execute('remove-symbol-file {path}'.format(path=path)) 53 | except Exception as _error: 54 | pass 55 | 56 | # Add the symbol file. 57 | gdb.execute('add-symbol-file {path} {textaddr} -s {sections}'.format( 58 | path=path, textaddr=sections['.text'], 59 | sections=' -s '.join( 60 | ' '.join((name, str(address))) for name, address in sections.items() if name != '.text') 61 | )) 62 | 63 | 64 | LoadSymbols() 65 | -------------------------------------------------------------------------------- /src/logger.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | use atomic_refcell::AtomicRefCell; 4 | use x86_64::instructions::port::PortWriteOnly; 5 | 6 | pub struct Serial; 7 | 8 | pub static PORT: AtomicRefCell> = AtomicRefCell::new(PortWriteOnly::new(0x3f8)); 9 | 10 | static LOG_LEVEL_NAMES: [&str; 5] = ["ERROR", "WARN", "INFO", "DEBUG", "TRACE"]; 11 | 12 | #[derive(Clone, Copy)] 13 | pub enum LogLevel { 14 | Error = 0, 15 | Warn, 16 | Info, 17 | Debug, 18 | Trace, 19 | } 20 | 21 | impl fmt::Display for LogLevel { 22 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 23 | fmt.pad(LOG_LEVEL_NAMES[*self as usize]) 24 | } 25 | } 26 | 27 | impl fmt::Write for Serial { 28 | fn write_str(&mut self, s: &str) -> fmt::Result { 29 | let mut port = PORT.borrow_mut(); 30 | for b in s.bytes() { 31 | unsafe { port.write(b) } 32 | } 33 | Ok(()) 34 | } 35 | } 36 | 37 | macro_rules! log { 38 | ($($arg:tt)*) => {{ 39 | use core::fmt::Write; 40 | writeln!(crate::logger::Serial, $($arg)*).unwrap(); 41 | }}; 42 | } 43 | 44 | #[macro_export] 45 | macro_rules! debug { 46 | ($format:expr) => ( 47 | log!(concat!("{:5} - ", $format), $crate::logger::LogLevel::Debug); 48 | ); 49 | ($format:expr, $($args:tt)*) => ( 50 | log!(concat!("{:5} - ", $format), $crate::logger::LogLevel::Debug, $($args)*); 51 | ) 52 | } 53 | 54 | #[macro_export] 55 | macro_rules! warn { 56 | ($format:expr) => ( 57 | log!(concat!("{:5} - ", $format), $crate::logger::LogLevel::Warn); 58 | ); 59 | ($format:expr, $($args:tt)*) => ( 60 | log!(concat!("{:5} - ", $format), $crate::logger::LogLevel::Warn, $($args)*); 61 | ) 62 | } 63 | 64 | #[macro_export] 65 | macro_rules! info { 66 | ($format:expr) => ( 67 | log!(concat!("{:5} - ", $format), $crate::logger::LogLevel::Info); 68 | ); 69 | ($format:expr, $($args:tt)*) => ( 70 | log!(concat!("{:5} - ", $format), $crate::logger::LogLevel::Info, $($args)*); 71 | ) 72 | } 73 | 74 | #[macro_export] 75 | macro_rules! error { 76 | ($format:expr) => ( 77 | log!(concat!("{:5} - ", $format), $crate::logger::LogLevel::Error); 78 | ); 79 | ($format:expr, $($args:tt)*) => ( 80 | log!(concat!("{:5} - ", $format), $crate::logger::LogLevel::Error, $($args)*); 81 | ) 82 | } 83 | 84 | #[macro_export] 85 | macro_rules! trace { 86 | ($format:expr) => ( 87 | log!(concat!("{:5} - ", $format), $crate::logger::LogLevel::Trace); 88 | ); 89 | ($format:expr, $($args:tt)*) => ( 90 | log!(concat!("{:5} - ", $format), $crate::logger::LogLevel::Trace, $($args)*); 91 | ) 92 | } 93 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | #![feature(asm)] 4 | #![feature(lang_items)] 5 | #![feature(panic_info_message)] 6 | 7 | #[macro_use] 8 | mod logger; 9 | mod utils; 10 | 11 | use core::borrow::BorrowMut; 12 | use core::mem::MaybeUninit; 13 | 14 | use r_efi::*; 15 | 16 | static mut SYSTEM_TABLE: MaybeUninit = MaybeUninit::uninit(); 17 | 18 | pub fn system_table() -> &'static efi::SystemTable { 19 | unsafe { &*SYSTEM_TABLE.as_ptr() } 20 | } 21 | 22 | pub fn runtime_services() -> &'static efi::RuntimeServices { 23 | unsafe { &*system_table().runtime_services } 24 | } 25 | 26 | pub fn boot_services() -> &'static efi::BootServices { 27 | unsafe { &*system_table().boot_services } 28 | } 29 | 30 | extern "win64" fn handle_exit_boot_services(_event: base::Event, _context: *mut core::ffi::c_void) { 31 | info!("[~] ExitBootServices() has been called."); 32 | } 33 | 34 | extern "win64" fn handle_set_virtual_address_map( 35 | _event: base::Event, 36 | _context: *mut core::ffi::c_void, 37 | ) { 38 | info!("[~] SetVirtualAddressMap() has been called."); 39 | } 40 | 41 | #[no_mangle] 42 | fn efi_main(_image_handle: efi::Handle, raw_system_table: *mut efi::SystemTable) -> efi::Status { 43 | #[cfg(debug_assertions)] 44 | { 45 | utils::wait_for_debugger(); 46 | } 47 | 48 | unsafe { SYSTEM_TABLE = MaybeUninit::new(raw_system_table.read()) }; 49 | 50 | // Register to events relevant for runtime drivers. 51 | let mut event_virtual_address: base::Event = core::ptr::null_mut(); 52 | let mut status = (boot_services().create_event_ex)( 53 | efi::EVT_NOTIFY_SIGNAL, 54 | efi::TPL_CALLBACK, 55 | handle_set_virtual_address_map, 56 | runtime_services() as *const _ as *mut core::ffi::c_void, 57 | &efi::EVENT_GROUP_VIRTUAL_ADDRESS_CHANGE, 58 | event_virtual_address.borrow_mut(), 59 | ); 60 | 61 | if status.is_error() { 62 | error!( 63 | "[-] Creating VIRTUAL_ADDRESS_CHANGE event failed: {:#x}", 64 | status.as_usize() 65 | ); 66 | return status; 67 | } 68 | 69 | let mut event_boot_services: base::Event = core::ptr::null_mut(); 70 | status = (boot_services().create_event_ex)( 71 | efi::EVT_NOTIFY_SIGNAL, 72 | efi::TPL_CALLBACK, 73 | handle_exit_boot_services, 74 | runtime_services() as *const _ as *mut core::ffi::c_void, 75 | &efi::EVENT_GROUP_EXIT_BOOT_SERVICES, 76 | event_boot_services.borrow_mut(), 77 | ); 78 | 79 | if status.is_error() { 80 | error!( 81 | "[-] Creating EXIT_BOOT_SERVICES event failed: {:#x}", 82 | status.as_usize() 83 | ); 84 | return status; 85 | } 86 | 87 | // Your runtime driver initialization. If the initialization fails, manually close the previously 88 | // created events with: 89 | // (boot_services().close_event)(event_virtual_address); 90 | // (boot_services().close_event)(event_boot_services); 91 | 92 | info!("[~] EFI runtime driver has been loaded and initialized."); 93 | 94 | efi::Status::SUCCESS 95 | } 96 | --------------------------------------------------------------------------------