在本篇文章,咱将一起使用rust实现一个UEFI引导程序!
将加载一个简单的内核。
值得的是:代码未经过测试,有问题可以联系咱。
首先,咱们需要创建一个新项目:
咱们还需要使用x86_64-unknown-uefi这个target。不过一般不需要安装。
接着,咱们设置咱们的cargo.toml
。
我们使用uefi-rs这个库。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
[package]
name = "hello_uefi"
version = "0.1.0"
edition = "2018"
[profile.dev]
# 禁用栈展开
panic = "abort"
[profile.release]
# 同上
panic = "abort"
[dependencies]
# 引入uefi
uefi = "0.11.0"
|
然后编写main.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
// 禁用自带的东西
// 同时启用一些feature
#![no_std]
#![no_main]
#![feature(abi_efiapi)]
#![feature(panic_info_message)]
#![feature(alloc_error_handler)]
use core::panic::PanicInfo;
extern crate alloc;
// 载入uefi定义
use core::fmt::Write;
use uefi::prelude::*;
use uefi::CStr16;
// 全局系统表
pub static mut IMAGE_HANDLE: Option<Handle> = None;
pub static mut IMAGE_SYSTEM_TABLE: Option<SystemTable<Boot>> = None;
/// 入口函数
#[no_mangle]
pub extern "C" fn efi_main(handle: Handle, system_table: SystemTable<Boot>) -> Status {
// 初始化全局变量
unsafe {
IMAGE_HANDLE = Some(handle);
IMAGE_SYSTEM_TABLE = Some(system_table);
}
// 什么都不干
loop {}
}
/// panic擦屁股函数
#[panic_handler]
fn panic(panic_info: &PanicInfo) -> ! {
// 什么都不干
loop {}
}
|
因为我们使用了特殊的target,所以我们需要自己构建标准库等基础措施。
还需要compiler_builtins提供的一些列操作内存的函数。
我们再编写一个脚本以构建此项目:
1
|
cargo build --target=x86_64-unknown-uefi -Z build-std=core,compiler_builtins,alloc -Z build-std-features=compiler-builtins-mem
|
接下来,我们应该就可以编译我们的第一个uefi程序了!
不出所料,我们应该得到了一个错误:
error: no global memory allocator found but one is required; link to std or add `#[global_allocator]` to a static item that implements the GlobalAlloc trait.
看起来,我们并没有给这个程序添加内存分配器。解决方案也都不难:
下面是内存分配器的源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
use core::alloc::{GlobalAlloc, Layout};
use uefi::table::boot::MemoryType;
/// UEfi内存分配器
pub struct UefiAllocator;
/// 实现内存分配器
unsafe impl GlobalAlloc for UefiAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let boot = IMAGE_SYSTEM_TABLE.as_ref().unwrap().boot_services();
// 内存类型为LOADER_DATA(即数据)
boot.allocate_pool(MemoryType::LOADER_DATA, layout.size())
.unwrap()
.unwrap()
}
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
let boot = IMAGE_SYSTEM_TABLE.as_ref().unwrap().boot_services();
boot.free_pool(ptr).unwrap().unwrap();
}
}
/// 设置内存分配器
#[global_allocator]
static GLOBAL: UefiAllocator = UefiAllocator;
/// 设置分配error处理函数
#[alloc_error_handler]
fn alloc_error_catch(layout: core::alloc::Layout) -> ! {
panic!(
"Couldn't alloc memory! Size:{0} Align:{1}",
layout.size(),
layout.align()
);
}
|
UEFI中的allocate_pool()内存以8字节对齐,我们不需要再关心此事(8字节是rust的最大对齐)。
我们现在应该可以顺利通过编译。
下面,咱们就可以开始进行测试啦!
有一个名为uefi-run
的rust的工具可以帮到我们,install at once:
1
|
$ cargo install uefi-run
|
用法也很简单:
1
|
$ uefi-run -b 你的OVMF.fd所在的地址 -q 你的qemu可执行文件的地址,通常是qemu-system-x86_64 你的ELF文件所在的地址 -- 你的QEMU参数列表...
|
OVMF.fd可以从一些Linux发行版的包管理器中找到。
也可以从这里找到一些有用的信息。
不出意外,我们的程序应该没什么动作。
下面,我们应该可以直接加载内核了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
/// 读取文件
///
/// 返回读取到的文件的内容
pub fn read_file(path: &str) -> Vec<u8> {
unsafe {
// 获取引导服务
let boot_services = IMAGE_SYSTEM_TABLE.as_mut().unwrap().boot_services();
// 获取fs服务
let fs = match boot_services.locate_protocol::<SimpleFileSystem>() {
Ok(some) => some.unwrap().get(),
Err(_err) => panic!("Load SimpleFileSystem failed down!"),
};
crate::tool::print_fmt(format_args!("Get fs done.\n"));
// 打开文件
let mut dic: Directory = fs.as_mut().unwrap().open_volume().unwrap().unwrap();
let file = dic
.open(path, FileMode::Read, FileAttribute::empty())
.unwrap()
.unwrap();
// 读取文件
if let FileType::Regular(mut reader) = file.into_type().unwrap().unwrap() {
// 获取文件大小
// 设置缓冲区...
// 应该不会溢出
let mut info_buffer: [u8; 512] = [0u8; 512];
let file_size = reader
.get_info::<FileInfo>(&mut info_buffer)
.unwrap()
.unwrap()
.file_size();
// 读进缓冲区
let mut text_buffer = vec![0u8; file_size.try_into().unwrap()].into_boxed_slice();
reader.read(&mut text_buffer).unwrap().unwrap();
// 转换为字符串
(&text_buffer).to_vec()
} else {
panic!("Filed to open file. Look like a Directory");
}
}
}
|
emmm…DONE OT TODO