在本篇文章,咱将一起使用rust实现一个UEFI引导程序!

将加载一个简单的内核。

值得的是:代码未经过测试,有问题可以联系咱。

首先,咱们需要创建一个新项目:

1
 $ cargo new hello_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.

看起来,我们并没有给这个程序添加内存分配器。解决方案也都不难:

  • 添加一个内存分配器,鉴于UEFI已经提供了非常简陋的内存"管理",我们将使用这个方案。

  • 删除源码中的extern crate alloc;

下面是内存分配器的源码:

 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