├── .gitignore ├── Cargo.toml ├── Makefile ├── Cargo.lock └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bootloader-uefi" 3 | version = "0.1.0" 4 | authors = ["Bobby Reynolds "] 5 | 6 | [dependencies] 7 | efi = { git = "https://github.com/reynoldsbd/libefi" } 8 | xmas-elf = "0.6.2" 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | arch ?= x86_64 2 | ovmf ?= /usr/share/ovmf/OVMF.fd 3 | profile ?= debug 4 | 5 | build_dir := target/$(arch) 6 | 7 | kernel := kernel/target/$(arch)-rust_os/$(profile)/kernel 8 | bootloader := bootloader-uefi/target/$(arch)-pc-uefi/$(profile)/bootloader-uefi.efi 9 | esp := $(build_dir)/EFISys.img 10 | iso := $(build_dir)/rust_os.iso 11 | 12 | 13 | export RUST_TARGET_PATH=$(abspath .) 14 | ifeq ($(profile), debug) 15 | profile_arg := 16 | else 17 | profile_arg := --$(profile) 18 | endif 19 | 20 | 21 | all: kernel bootloader 22 | 23 | 24 | kernel: $(kernel) 25 | 26 | 27 | bootloader: $(bootloader) 28 | 29 | 30 | test: $(iso) 31 | @qemu-system-$(arch) -net none -bios $(ovmf) -cdrom $(iso) 32 | 33 | 34 | debug: $(iso) 35 | @qemu-system-$(arch) -net none -bios $(ovmf) -cdrom $(iso) -s -S 36 | 37 | 38 | clean: 39 | @rm -rf $(build_dir) 40 | @cd bootloader-uefi; xargo clean 41 | @cd kernel; xargo clean 42 | 43 | 44 | $(kernel): $(shell find kernel/src -type f) 45 | @cd kernel; \ 46 | xargo build \ 47 | --target=$(arch)-rust_os 48 | $(profile_arg) 49 | 50 | 51 | $(bootloader): $(shell find bootloader-uefi/src -type f) 52 | @cd bootloader-uefi; \ 53 | xargo build \ 54 | --target=$(arch)-pc-uefi \ 55 | $(profile_arg) 56 | 57 | 58 | $(esp): $(kernel) $(bootloader) 59 | @mkdir -p $(build_dir)/esp/EFI/BOOT 60 | @mkdir -p $(build_dir)/esp/EFI/RustOs 61 | @cp $(bootloader) $(build_dir)/esp/EFI/BOOT/BOOTX64.EFI 62 | @cp $(kernel) $(build_dir)/esp/EFI/RustOs/Kernel 63 | @rm -f $(esp) 64 | @dd if=/dev/zero of=$(esp) bs=1M count=64 65 | @mkfs.vfat -F 32 $(esp) -n EFISys 66 | @mcopy -i $(esp) -s $(build_dir)/esp/EFI :: 67 | 68 | 69 | $(iso): $(esp) 70 | @mkdir -p $(build_dir)/iso 71 | @cp $(esp) $(build_dir)/iso/ 72 | @xorriso -as mkisofs \ 73 | -o $(iso) \ 74 | -e $(notdir $(esp)) \ 75 | -no-emul-boot \ 76 | $(build_dir)/iso 77 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "bitflags" 3 | version = "1.0.1" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | 6 | [[package]] 7 | name = "bootloader-uefi" 8 | version = "0.1.0" 9 | dependencies = [ 10 | "efi 0.1.0 (git+https://github.com/reynoldsbd/libefi)", 11 | "xmas-elf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 12 | ] 13 | 14 | [[package]] 15 | name = "efi" 16 | version = "0.1.0" 17 | source = "git+https://github.com/reynoldsbd/libefi#da66191ef38d3a45c933374c494adcc8c01350ec" 18 | dependencies = [ 19 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 20 | "rlibc 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 21 | ] 22 | 23 | [[package]] 24 | name = "rlibc" 25 | version = "1.0.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | 28 | [[package]] 29 | name = "xmas-elf" 30 | version = "0.6.2" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | dependencies = [ 33 | "zero 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 34 | ] 35 | 36 | [[package]] 37 | name = "zero" 38 | version = "0.1.2" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | 41 | [metadata] 42 | "checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" 43 | "checksum efi 0.1.0 (git+https://github.com/reynoldsbd/libefi)" = "" 44 | "checksum rlibc 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc874b127765f014d792f16763a81245ab80500e2ad921ed4ee9e82481ee08fe" 45 | "checksum xmas-elf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "22678df5df766e8d1e5d609da69f0c3132d794edf6ab5e75e7abcd2270d4cf58" 46 | "checksum zero 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5f1bc8a6b2005884962297587045002d8cfb8dcec9db332f4ca216ddc5de82c5" 47 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(lang_items)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | 6 | extern crate efi; 7 | extern crate xmas_elf; 8 | 9 | 10 | use core::{ 11 | fmt, 12 | mem, 13 | slice, 14 | }; 15 | use efi::{ 16 | boot_services, 17 | boot_services::{ 18 | AllocateType, 19 | MemoryMap, 20 | MemoryType, 21 | OpenProtocolAttributes, 22 | Pool, 23 | Protocol, 24 | SearchType, 25 | }, 26 | protocols::{ 27 | FileAttributes, 28 | FileInfo, 29 | FileMode, 30 | FileSystemInfo, 31 | SimpleFileSystem, 32 | }, 33 | SystemTable, 34 | types::{ 35 | Handle, 36 | Status, 37 | }, 38 | }; 39 | use xmas_elf::{ 40 | ElfFile, 41 | header, 42 | program::{ 43 | ProgramHeader, 44 | ProgramHeader64, 45 | Type, 46 | }, 47 | }; 48 | 49 | 50 | /// Print text to the console 51 | macro_rules! efi_print { 52 | ($system_table:expr, $($arg:tt)*) => ({ 53 | use core::fmt::Write; 54 | (&*($system_table).con_out) 55 | .write_fmt(format_args!($($arg)*)) 56 | .expect("could not write to console"); 57 | }); 58 | } 59 | 60 | 61 | /// Print a line of text to the console 62 | macro_rules! efi_println { 63 | ($system_table:expr, $fmt:expr) => 64 | (efi_print!($system_table, concat!($fmt, "\r\n"))); 65 | ($system_table:expr, $fmt:expr, $($arg:tt)*) => 66 | (efi_print!($system_table, concat!($fmt, "\r\n"), $($arg)*)); 67 | } 68 | 69 | 70 | /// Reads the specified file into memory 71 | fn read_file<'a>( 72 | volume_label: &str, 73 | file_name: &str, 74 | image_handle: Handle, 75 | system_table: &'a SystemTable 76 | ) -> Result, Status> { 77 | 78 | // Open the specified volume 79 | let vol_root = system_table.boot_services 80 | // Get the list of handles to available file systems 81 | .locate_handle(SearchType::ByProtocol, Some(SimpleFileSystem::guid()), None)? 82 | .iter() 83 | // Open each handle and get the root node 84 | .filter_map(|handle| { 85 | system_table.boot_services 86 | .open_protocol::( 87 | *handle, 88 | image_handle, 89 | 0, 90 | OpenProtocolAttributes::BY_HANDLE_PROTOCOL 91 | ) 92 | .and_then(|vol| vol.open_volume()) 93 | // If there was some issue opening a volume, just move on to the next one 94 | .ok() 95 | }) 96 | // Keep only the volume with the specified label 97 | .find(|root| { 98 | root.get_info::(&*system_table.boot_services) 99 | .and_then(|info| info.volume_label(&*system_table.boot_services)) 100 | .map(|label| label == volume_label) 101 | .unwrap_or(false) 102 | }) 103 | .ok_or(Status::NotFound)?; 104 | 105 | // Open the specified file 106 | let path = boot_services::str_to_utf16(file_name, &system_table.boot_services)?; 107 | let file = vol_root.open(&path, FileMode::READ, FileAttributes::empty())?; 108 | 109 | // Allocate a suitably-sized buffer from pool memory 110 | let file_size = file 111 | .get_info::(&*system_table.boot_services)? 112 | .file_size as usize; 113 | let mut file_buf = system_table.boot_services.allocate_slice::(file_size)?; 114 | 115 | // Read the entire file 116 | let _ = file.read(&mut file_buf)?; 117 | 118 | Ok(file_buf) 119 | } 120 | 121 | 122 | /// Loads the given ELF file and returns a pointer to its entry point 123 | fn load_elf( 124 | elf_file: &ElfFile, 125 | system_table: &SystemTable 126 | ) -> Result !, Status> { 127 | 128 | for header in elf_file.program_iter() { 129 | match header { 130 | ProgramHeader::Ph32(_) => return Err(Status::Unsupported), 131 | ProgramHeader::Ph64(header) => load_section(header, elf_file, system_table)?, 132 | } 133 | } 134 | 135 | efi_println!(system_table, "Entry point: {:x}", elf_file.header.pt2.entry_point()); 136 | unsafe { 137 | Ok(mem::transmute(elf_file.header.pt2.entry_point())) 138 | } 139 | } 140 | 141 | 142 | /// Loads the given program segment into memory 143 | fn load_section( 144 | header: &ProgramHeader64, 145 | elf_file: &ElfFile, 146 | system_table: &SystemTable 147 | ) -> Result<(), Status> { 148 | 149 | // Skip any section that's not loadable 150 | match header.get_type() { 151 | Err(err) => { 152 | efi_println!(system_table, "Failed to read section type: {}", err); 153 | return Err(Status::InvalidParameter); 154 | }, 155 | Ok(Type::Load) => {}, 156 | Ok(_) => return Ok(()), 157 | } 158 | 159 | // header.virtual_addr might not be on a 4K page boundary 160 | let mut destination: *mut u8 = (header.virtual_addr & !0x0fff) as *mut u8; 161 | 162 | // Don't just allocate enough room for header.mem_size, also account for any padding that may be 163 | // present between destination and header.virtual_addr 164 | let num_pages: usize = { 165 | let padding = header.virtual_addr & 0x0fff; 166 | let total_bytes = header.mem_size + padding; 167 | (1 + (total_bytes >> 12)) as usize 168 | }; 169 | 170 | // Do the allocation and sanity-check that the firmware respected our requested address 171 | system_table.boot_services.allocate_pages( 172 | AllocateType::AllocateAddress, 173 | MemoryType::LoaderCode, 174 | num_pages, 175 | &mut destination 176 | )?; 177 | assert!(destination as u64 == header.virtual_addr & !0x0fff); 178 | 179 | // Zero out the allocated pages 180 | unsafe { 181 | system_table.boot_services.set_mem(destination, num_pages * 4096, 0); 182 | } 183 | 184 | // Copy any program bits to their destination 185 | let dst_buf = unsafe { 186 | slice::from_raw_parts_mut(header.virtual_addr as *mut u8, header.mem_size as usize) 187 | }; 188 | dst_buf.copy_from_slice(header.raw_data(elf_file)); 189 | 190 | Ok(()) 191 | } 192 | 193 | 194 | #[no_mangle] 195 | pub extern fn efi_main(image_handle: Handle, system_table: &SystemTable) -> ! { 196 | 197 | // Give debugger time to attach 198 | // loop { } 199 | 200 | // Store a reference to the system table to enable panic_fmt 201 | // This is safe at least until exit_boot_services is called 202 | unsafe { 203 | SYSTEM_TABLE = system_table; 204 | } 205 | 206 | efi_println!(system_table, "Reading kernel from EFISys"); 207 | let res = read_file("EFISys", "EFI\\RustOS\\Kernel", image_handle, system_table); 208 | let mut file_buf = match res { 209 | Ok(buf) => buf, 210 | Err(err) => { 211 | efi_println!(system_table, "Failed to read kernel: {:?}", err); 212 | loop { } 213 | }, 214 | }; 215 | 216 | efi_println!(system_table, "Parsing ELF image and performing sanity check"); 217 | let elf = match ElfFile::new(&mut file_buf) { 218 | Ok(elf) => elf, 219 | Err(err) => { 220 | efi_println!(system_table, "Failed to parse ELF image: {}", err); 221 | loop { } 222 | }, 223 | }; 224 | if let Err(err) = header::sanity_check(&elf) { 225 | efi_println!(system_table, "ELF sanity check failed: {}", err); 226 | loop { } 227 | } 228 | 229 | efi_println!(system_table, "Loading kernel into memory"); 230 | let entry = match load_elf(&elf, system_table) { 231 | Ok(entry) => entry, 232 | Err(err) => { 233 | efi_println!(system_table, "Failed to load kernel: {:?}", err); 234 | loop { } 235 | }, 236 | }; 237 | 238 | efi_println!(system_table, "Retrieving memory map"); 239 | // Evidently invoking con_out triggers memory allocations, so no more efi_println! after this 240 | let mem_map = match system_table.boot_services.get_memory_map() { 241 | Ok(map) => map, 242 | Err(err) => { 243 | efi_println!(system_table, "Failed to retrieve memory map: {:?}", err); 244 | loop { } 245 | }, 246 | }; 247 | 248 | if let Err(err) = system_table.boot_services.exit_boot_services(image_handle, mem_map.key) { 249 | efi_println!(system_table, "Failed to exit boot services: {:?}", err); 250 | loop { } 251 | } 252 | 253 | entry(system_table, &mem_map); 254 | } 255 | 256 | 257 | static mut SYSTEM_TABLE: *const SystemTable = 0 as *const _; 258 | 259 | 260 | #[allow(private_no_mangle_fns)] 261 | #[lang = "panic_fmt"] 262 | #[no_mangle] 263 | fn panic_fmt(args: &fmt::Arguments, file: &str, line: u32, col: u32) -> ! { 264 | 265 | let system_table = unsafe { SYSTEM_TABLE.as_ref().unwrap() }; 266 | efi_println!(system_table, "Panic at {}:{}:{}", file, line, col); 267 | efi_println!(system_table, "{}", args); 268 | 269 | loop { } 270 | } 271 | 272 | 273 | #[allow(private_no_mangle_fns)] 274 | #[lang = "eh_personality"] 275 | #[no_mangle] 276 | fn eh_personality() { 277 | 278 | loop { } 279 | } 280 | --------------------------------------------------------------------------------