EFI(x64)简易开发环境
文章目录
- 1 必须文件
- 2 运行环境
- 3 构建应用 (Visual Studio)
- 4 引用 EDK2 头文件
1 必须文件
EDK2: 可以只拉取仓库本身, 不拉取其子仓库(完整构建才需要)
qemu: qemu 以源码发布, QEMU for Windows – Installers (64 bit) 这里有民间构建的安装包
2 运行环境
创建一个 root 文件夹, 用本机的文件夹作为"磁盘"
下面用 qemu 虚拟机来运行, 启动 qemu 虚拟机, 加载 UEFI 固件, 运行 UEFI Shell
qemu-system-x86_64.exe ^-net none ^-hda fat:rw:root ^-drive if=pflash,format=raw,unit=0,file="C:\Program Files\qemu\share\edk2-x86_64-code.fd",readonly=on
-
-net none
关闭网络加快启动 -
-hda fat:rw:root
创建一个 MBR 分区的磁盘, 以 root 文件夹为磁盘内内容, 文件系统为 FATqemu 还支持 VHDX 虚拟磁盘, 选项为
-drive if=ide,format=vhdx,file=<虚拟磁盘路径.vhdx>
-
-drive if=pflash,format=raw,unit=0,file="C:\Program Files\qemu\share\edk2-x86_64-code.fd",readonly=on
加载 UEFI 固件
启动后, 由于 root 目录中路径 \EFI\Boot\bootx64.efi
位置为空, 默认运行 UEFI 固件自带的 UEFI Shell
3 构建应用 (Visual Studio)
EFI 应用程序本质是一个 PE 程序, 使用 CFF Explorer 可以看到其 Subsystem 为 EFI Application (10)
Visual Studio 中创建一个 efitest 项目, 做如下配置
- 项目属性页>配置属性>C/C++>常规>支持仅我的代码调试>否
- 项目属性页>配置属性>C/C++>常规>SDL 检查>否
- 项目属性页>配置属性>C/C++>预处理器>预处理器定义 在前面添加
_HAS_EXCEPTIONS=0;
- 项目属性页>配置属性>C/C++>代码生成>启用C++异常>否
- (必须) 项目属性页>配置属性>C/C++>代码生成>基本运行时检查>默认值
- 项目属性页>配置属性>C/C++>代码生成>安全检查>禁用安全检查
- 项目属性页>配置属性>C/C++>命令行 添加
/Gs65536
- (可选) 项目属性页>配置属性>链接器>输入>附加依赖项 清空
- (可选) 项目属性页>配置属性>链接器>输入>忽略所有默认库>是
- (必须) 项目属性页>配置属性>链接器>清单文件>启用用户账户控制(UAC)>否
- (必须) 项目属性页>配置属性>链接器>系统>子系统>EFI 应用程序
- (建议) 项目属性页>配置属性>链接器>高级>入口点 填写 UefiMain
- (必须) 项目属性页>配置属性>链接器>高级>随机基址>否
- (必须) 项目属性页>配置属性>链接器>高级>数据执行保护>否
为方便进行部署, 在 项目属性页>配置属性>生成事件>生成后事件 添加复制构建目标的命令
copy $(TargetPath) C:\Users\...\root\efitest.efi
编译以下代码
#include <stdint.h>
uint64_t UefiMain() {return 0xC0FFEE;
}
EFI Shell 中, 输入命令
FS0:
efitest.efi
set
第 1 行通过 PATH 变量找到程序来运行, 第 2 行查看 lasterror 变量值, 应当为 0xC0FFEE
4 引用 EDK2 头文件
EDK2 即 UEFI SDK, 其中 Uefi.h 包含了基本的数据结构可用
构建 EDK2 不够简易, 下面只依赖其头文件中的数据结构
项目属性页>VC++目录>包含目录 添加 edk2\MdePkg\Include;edk2\MdePkg\Include\X64
C++ 中包含 EDK2 头文件时需要用 extern “C” 包裹
下面是 Hello World 示例
extern "C" {
#include <Uefi.h>
}EFI_STATUS EFIAPI UefiMain(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE *SystemTable
) {EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL* Console = SystemTable->ConOut;Console->OutputString(Console, (CHAR16*)L"Hello world!\r\n");return EFI_SUCCESS;
}