临时接手BIOS相关代码的验收与修改工作,一头雾水555,花了快一个月时间才大体上有了认识,然后搭好一个简单的模拟环境。
一 UEFI与TianoCore简介
“统一的可扩展固件接口”(Unified Extensible Firmware Interface),是一个旨在代替传统Bios,统一操作系统与底层固件之间的连接层的公开标准,各大厂商基于此标准进行了自己的实现。UEFI的主要用途(个人理解)是在主板接上电后,进行硬件的检查和初始化,然后引导操作系统启动,并为运行中的操作系统提供服务。
图1 UEFI启动流程
TianoCore是由Intel开源的一个UEFI实现,可以用于UEFI的学习和研究。下文对Windows10系统下的EDK2(uEFI Development Kit Ⅱ)模拟环境搭建方法进行叙述。
主要有两种搭建模拟环境的方式,Nt32功能包可以直接编译产生一个可执行程序(Windows下是.exe文件),运行后就启动一个模拟的UEFI界面。这种方式可以很方便的调试程序,但无法对模拟的硬件平台进行修改,比如只能在32位下运行。还有一种方法是通过OVMF功能包编译UEFI固件镜像(.fd文件),然后在QEMU模拟器中运行,通过模拟器的自带功能来修改环境硬件配置。
二 Nt32模拟环境安装配置
2.1环境信息
操作系统:Windows Professional 10.0.17763.678
CPU:Intel Core i5-8265U
内存:8G
2.2安装步骤
- 安装windows SDK(10.0.18362.0):https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk
- 安装Visual Studio 2015 Community(或者其他C编译器):https://my.visualstudio.com/Downloads?q=visual%20studio%202015&wt.mc_id=o~msft~vscom~older-downloads 需要登录MS账号并加入Dev Essentials计划。
- 安装IASL编译器:https://acpica.org/downloads/binary-tools
- 下载EDK2 UDK2018:https://github.com/tianocore/edk2/tree/vUDK2018
解压后将所有文件放到C:/MyWorkspace下
- 下载安装NASM、PYTHON27、OPENSSL。链接:http://www.nasm.us/、
https://www.python.org/、http://wiki.overbyte.eu/arch/openssl-1.1.0g-win32.zip。
可以下载cx_Freeze-4.3.4.win-amd64-py2.7并放到PYTHON27目录下的Scripts下。不下载会在编译过程中提示WARNING但不影响UDK2018环境的搭建。将这三项都安装到C盘目录下。然后在系统环境变量PATH中添加C:\NASM和C:\Openssl。新建环境变量PYTHON_HOME,值为C:\Python27。
- 下载https://github.com/openssl/openssl/archive/OpenSSL_1_1_0g.zip,然后解压到C:\MyWorkspace\CryptoPkg\Library\OpensslLib中,重命名为openssl。在加密库中添加openssl源码。
- 直接下载已编译好的运行工具base tools。从https://github.com/tianocore/edk2-BaseTools-win32 下载并解压到C:\MyWorkspace\BaseTools\Bin,将文件夹重命名为Win32,注意忽略此步会在接下来的搭建中遇到报错环境变量PYTHON_HOME的问题。
- 打开CMD,运行命令:
cd C:\MyWorkspace
edksetup --nt32
初始化UEFI的运行环境,运行后会在C:\MyWorkspace\Conf目录下生成三个配置文件,打开target.txt,将其中的TOOL_CHAIN_TAG的值修改为VS2015x86(所使用的编译器,可以参考同目录下tools_def.txt中的名称)。
Nt32Pkg(Nt32Pkg / Nt32Pkg.dsc)仿真平台需要Windows头文件。--nt32选项会檢測Microsoft Visual Studio是否安裝並執行它的setup命令,例如vsvars32.bat,从而包含Windows头文件。
- 运行命令:
build
开始安装。
图3
- 运行命令:
build run
或者打开C:\MyWorkspace\Build\NT32IA32\DEBUG_VS2015x86\IA32文件夹下的SecMain.exe
启动UEFI的Nt32模拟环境。
图4
Nt32模拟环境安装成功!
在此环境中可以学习EFI SHELL的功能和设置细节,测试写好的小程序。若要进行更深度的开发工作则可使用UDK源码中的OVMF包。安装OVMF能够获得一个运行在QEMU模拟器中的完整UEFI固件镜像。
2.3遇到的问题
- Rebuild过程中py脚本报编码错误:遇到此种有明确位置提示的报错,首先根据报错信息去分析,报错源头是print语句,报错信息类型是UnicodeEncodeError,可以确认是在打印编译信息时出现了编码错误。
图5
我们先找到NmakeSubdirs.py文件,然后对报错的print(message),修改message对象的编码格式(.encode(encoding='UTF-8')),通常使用UTF-8格式即可。
- 报找不到CYGWIN_HOME环境变量的WARNING,因为UDK默认编译器是Cygwin,所以编译时会先去寻找其路径,可以无视,实际上我们使用了VS进行编译。对于找不到其他变量地址的WARNING,可以检查一下是否已经在系统环境变量中进行正确的设置。
图6
- 大量“无法解析的外部符号”报错。我对配置文件进行了修改,然后又用新的UDK源码覆盖了原来的文件夹,再次编译时就出现了此错误。无法确定是哪些文件不匹配导致的问题,因此我将MyWorkspace文件夹下的所有文件都删除,然后按照前文的步骤重新安装,没有出现错误。
图7
- 负责解析FDF文件的GenFds工具(实际上是封装为.exe的Python脚本)报错,可能与操作系统版本以及Windows SDK的版本有关。
图8
通过百度查询,得知可以使用源码包中自带的备用版GenFds(.bat脚本)。将C:\MyWorkspace\BaseTools\Bin\Win32文件夹下的GenFds.exe更名为GenFds.LABZ,然后将BaseTools\BinWrappers\WindowsLike;添加到Path路径中,编译时就会优先从WindowsLike文件夹中寻找备用的GenFds工具。(此问题的解决可以参照http://www.lab-z.com/udk2018coming/和https://github.com/tzz1996/Blog/blob/master/compile_udk2018_in_windows.md)
三 OVMF固件编译及虚拟机环境配置
3.1环境信息
同上
3.2安装步骤
- 下载并安装QEMU:https://www.qemu.org/download/
根据自己的操作系统环境选择安装方式,这里我们选择下载qemu-w64-setup-20190815安装包(QEMU 4.1.0),安装到C:\Program Files\qemu。
- 如果之前没有进行过前述Nt32模拟环境的安装,则需要先执行其安装步骤的第1-7步。
- 打开CMD,运行命令:
cd C:\MyWorkspace
edksetup –nt32
设置环境变量
- 运行命令:
build clean
build -a X64 -p OvmfPkg\OvmfPkgX64.dsc
编译64位OVMF固件。
或者运行命令:
build -a IA32 -p OvmfPkg\OvmfPkgIa32.dsc
编译32位OVMF固件。
编译完成后的固件是C:\MyWorkspace\Build\OvmfIa32\DEBUG_VS2015x86\FV\OVMF.fd
- 通过QEMU命令运行编译好的固件并设置硬件参数,运行命令:
cd C:\Program Files\qemu
qemu-system-x86_64.exe -bios "OVMF.fd" -m 1024 -cpu "qemu64" -vga cirrus -serial vc -parallel vc -name "UEFI" -boot order=dc
参数说明
1. qemu-system-x86_64命令是结合自己的电脑构架使用的,因为本机使用的是Intel x64平台;
2. -boot d:表示从CDROM启动系统,因为虚拟盘中目前还没有系统,可以从CDROM中启动安装盘;
3. -cdrom:可以指定系统iso镜像名称;
4. -hda:指定之前创建的虚拟磁盘作为模拟环境的硬盘;
5. -m:指定了QEMU使用的系统内存大小,这里指定的是2G;
6. -bios:指定了QEMU运行的BIOS,默认使用的是seabios,这里使用了自己编译的OVMF.fd。
7. -smp,指定核数。
8. -serial stdio:指定io方式,可以定义为标准输入输出流。
输入命令后,可以看到qemu启动并加载了TianoCore,进入到EFI SHELL命令行界面。
图9
OVMF编译运行成功!
3.3遇到的问题
- build时报错:
图10
可以看到是python脚本在运行sql语句时报错,去查找MetaDataTable.py中的函数定义,看不出问题,因此在调用SqlCommand前添加一行打印语句print SqlCommand,查看打印输出。输出显示显示报错的是insert的语句,向8列的表插入7个值。解决思路:可以记录下该表的表名,在检测到要向该表插入数据时,检测拼接到SQL语句中的插入值的数量,并根据缺少的数据,额外拼接一个默认值到SQL语句中。
实际情况中,添加了打印语句后,该问题就不再出现了。
- 通过qemu启动后找不到fs0:盘符:
图11
可以使用qemu-img create -f qcow2 OS.img 20G 命令创建一个20g大的虚拟磁盘并在运行固件时加载,qcow2类型文件系统支持写时复制,加密,压缩以及VM快照。除此之外,如下类型也是被支持的:raw ,cloop ,cow,qcow,vmdk ,vdi ,vhdx,vpc 。
生成的虚拟盘中放入事先编译的.efi程序,就可以在挂载后运行。FS0就存在于虚拟磁盘中,它用来存放启动系统的GRUB文件(如果安装了系统),可以直接执行引导的.efi文件来启动系统
四 附录
4.1EFI SHELL命令表
Shell提供了丰富的内部命令。如果不清楚某个指令的用法,可以使用help。比如:help ifconfig,就可以查看ifconfig的帮助信息,单独输入help,则会显示所有的指令。另外,若不特殊说明,Shell内置命令的参数中的数值使用十六进制,且不区分大小写。
ver | 展示shell的版本号 |
reconnect -r | 重新连接EFI驱动 |
alias | 显示,创建,删除别名 |
dmem | 显示内存目录 |
connect | 连接驱动程序和设备 |
cp | 复制文件 |
date | 显示当前或者系统的日期 |
devices | 显示EFI驱动程序管理的设备 |
devtree | 显示设备树 |
dh | 显示设备句柄 |
disconnect | 断开驱动程序与设备的连接 |
dmpstore | 管理UEFI NVRAM变量 |
drivers | 显示设备驱动 |
drvcfg | 调用驱动程序配置协议 |
drvdiag | 调用驱动程序诊断协议 |
echo | 关闭或者打开回显 |
edit | 能够打开文件并且编辑它 |
eficompress | 压缩文件 |
efidecompress | 解压文件 |
hexedit | 用十六进制编辑文件,块设备或者内存区域 |
ls | 列出目录内容或者文件信息 |
map | 显示挂载的磁盘 |
mem | 显示memory信息 |
mode | 显示控制台输出设备的模样 |
mount | 在块设备上挂载文件 |
mv | 移动一个文件从一个地方到另一个地方 |
touch | 更新当前时间更新文件或者目录的文件 |
pci | 显示PCI设备 |
rm | 删除文件 |
stall | 停止处理器几微秒 |
4.2运行测试程序
编写HelloWorld程序。编译为.efi文件,然后在模拟环境中运行。
搭建好uefi开发环境之后,在MyWorkspace文件夹中建立一个文件夹ExamplePkg; ,然后在ExamplePkg文件夹中创建HelloWorld文件夹,Include文件夹,ExamplePkg.dec文件,ExamplePkg.dsc文件,buildx86.bat文件。
.dec文件中内容为:
[Defines]
DEC_SPECIFICATION = 0x00010006
PACKAGE_NAME = ExamplePkg
PACKAGE_GUID = A0D78D6-2CAF-414b-BD4D-B6762A894289
PACKAGE_VERSION = 1.01
[Includes]
Include
[LibraryClasses]
.dsc文件中内容为:
[Defines]
PLATFORM_NAME = Example
PLATFORM_GUID = 587CE499-6CBE-43cd-94E2-186218569479
PLATFORM_VERSION = 1.01
DSC_SPECIFICATION = 0x00010006
OUTPUT_DIRECTORY = Build/Example
SUPPORTED_ARCHITECTURES = IA32|IPF|X64|EBC|ARM|AARCH64
BUILD_TARGETS = DEBUG|RELEASE
SKUID_IDENTIFIER = DEFAULT
[LibraryClasses]
UefiApplicationEntryPoint|MdePkg/Library/UefiApplicationEntryPoint/UefiApplicationEntryPoint.inf
UefiLib|MdePkg/Library/UefiLib/UefiLib.inf
UefiBootServicesTableLib|MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.inf
DebugLib|MdePkg/Library/UefiDebugLibStdErr/UefiDebugLibStdErr.inf
BaseLib|MdePkg/Library/BaseLib/BaseLib.inf
BaseMemoryLib|MdePkg/Library/BaseMemoryLib/BaseMemoryLib.inf
DebugPrintErrorLevelLib|MdePkg/Library/BaseDebugPrintErrorLevelLib/BaseDebugPrintErrorLevelLib.inf
MemoryAllocationLib|MdePkg/Library/UefiMemoryAllocationLib/UefiMemoryAllocationLib.inf
DevicePathLib|MdePkg/Library/UefiDevicePathLib/UefiDevicePathLib.inf
PrintLib|MdePkg/Library/BasePrintLib/BasePrintLib.inf
PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf
UefiRuntimeServicesTableLib|MdePkg/Library/UefiRuntimeServicesTableLib/UefiRuntimeServicesTableLib.inf
[Components]
ExamplePkg/HelloWorld/HelloWorld.inf
.bat文件中内容为:
@call "C:\MyWorkSpace\edksetup.bat"
Build -t VS2015x86 -a IA32 -p ExamplePkg\ExamplePkg.dsc -m ExamplePkg\HelloWorld\HelloWorld.inf -b RELEASE
pause
HelloWorld文件夹中要两个文件,HelloWorld.c HelloWorld.inf
.c文件中内容为
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiApplicationEntryPoint.h>
EFI_STATUS EFIAPI UefiMain(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
_asm int 3
Print(L"*************");
Print(L"*HelloWorld*");
Print(L"*************");
return EFI_SUCCESS;
}
.inf文件中内容为
[Defines]
INF_VERSION = 0x00010006
BASE_NAME = HelloWorld
FILE_GUID = 6987936E-ED34-44db-AE97-1FA5E4ED2117
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 1.01
ENTRY_POINT = UefiMain
[Sources]
HelloWorld.c
[Packages]
MdePkg/MdePkg.dec
ExamplePkg/ExamplePkg.dec
[LibraryClasses]
UefiApplicationEntryPoint
UefiLib
因为要做的事情很简单只是打印hellowold所以Include文件夹中不需要创建文件。
其目录形式为:
ExamplePkg-->HelloWorld-->HelloWorld.c
ExamplePkg-->HelloWorld-->HelloWorld.inf
ExamplePkg-->Include
ExamplePkg-->buildx86.bat
ExamplePkg-->ExamplePkg.dec
ExamplePkg-->ExamplePkg.dsc
然后执行buildx86.bat
在MyWorkspace\build\Example\RELEASE_VS2015x86\IA32文件夹中的HellowWorld.efi文件就是编译生成的.efi应用,将该文件放到MyWorkSpace\Build\NT32IA32\DEBUG_VS2015x86\IA32文件夹下。
打开同目录下的SecMain.exe,在Shell中输入”fs0:”选择盘符,再输入helloworld.efi,然后回车就可以看到输出了。
图12