# 入口函数
一个简单的
UEFI程序
,至少
包含两个
部分
C程序源文
件 : 用来实现具体功能
,在复杂程序中源文件可能包含:C/C++源代码
,.asm汇编文件
(.s 汇编文件,uin资源
文件 (字符串资源文件) 和.vfr
资源文件)INF元数据
文件用来编译代码
# 入口函数返回值类型 EFI_STATUS
- 在
UEFI
中基本所有的返回值类型都是EFI_STATUS
, 其本质是无符号长整数
- 最高位为
1
时其值为错误代码
,为0
表示正确
。听过宏EFI_ERROR(Status)
可以判断返回值Status
# 头文件
<Uefi.h>
: 定义了UEFI
中的基本数据类型
和核心数据结构
<Library/UefiLib.h>
: 提供通用的库函数
,包括时间
,简单锁
,任务优先级
,驱动管理和字符
,图形显示输出
等基本功能<Library/BaseLib.h>
: 提供字符串处理
,数学
,文件路径处理
等相关库函数<Library/BaseMemoryLib.h>
: 提供内存的库函数
,包括内存拷贝
,内存填充
,内存清空
等库函数<Library/DebugLib.h>
:提供调试输出
功能的库函数
# efi
文件
.efi
文件 (UEFI应用程序
或UEFI驱动程序
) 加载到内存后生成的对象成为Image
映像)。ImageHandle
是Image
对象的句柄,作为模版入口函数参数,它表示模块自身
加载到内存后生成的Image对象
。
UEFI Image
通常存储在文件系统中,可以是.efi
文件或一个文件夹,包含多个.efi
文件,UEFI Image 可以包含UEFI BIOS
的一些功能模块和驱动程序,也可以是一个 UEFI 应用程序或操作系统的引导程序。当 UEFI 启动时,UEFI Loader 会加载 UEFI Image 到内存中,并执行其中的代码来启动系统。在
UEFI
中,映像
通常指的是内存映像
,用于表示一个进程或操作系统执行时的内存状态。例如,在 UEFI 内存取证中,可以通过获取系统的内存映像来分析系统的运行状态和数据。
# GUID
typedef struct { | |
UINT32 Data1; | |
UINT16 Data2; | |
UINT16 Data3; | |
UINT8 Data4[8]; | |
} GUID; |
# 字符量
/* | |
__int64 的最大值:9223372036854775807 | |
__int64 的最小值:-9223372036854775808 | |
unsigned __int64 的最大值:18446744073709551615 | |
*/ | |
typedef unsigned __int64 UINT64; | |
typedef __int64 INT64; | |
typedef unsigned __int32 UINT32; | |
typedef __int32 INT32; | |
/* | |
unsigned short [int] 的字节数位 2,取值范围是 0 ~ 2^16-1,即 0 ~ 65535 | |
*/ | |
typedef unsigned short UINT16; | |
typedef unsigned short CHAR16; | |
// 短整型 -32768 ~ +32767 | |
typedef short INT16; | |
// 无符号字符型 unsigned 0~255 | |
typedef unsigned char BOOLEAN; | |
typedef unsigned char UINT8; | |
// 字符型 char 取值范围 -128 ~ +127 | |
typedef char CHAR8; | |
// 有符号字符类型 -128 到 127 | |
typedef signed char INT8; |
# 工程结构
Pkg
是Package
的缩写,包
的意思EDK2
模块化,每个Pkg
都是一个解决方案
build
保存着Pkg编译
的结果Conf
保存各种配置文
件,target.txt
编译目标,edksetup.bat脚本
dsc
是对整个包的描述
dec
是定义公开
的内容
和接口
,是 UEFI 接口的实现
# 启动 EDK2
EDK2
是实现UEFI
的源码
build
构建之后,进入对应的如下页面,启动WinHost.exe
文件。
D:\edk2\Build\EmulatorX64\DEBUG_VS2019\X64 |
# U 盘的文件系统为 FAT32 系统 I 会首先检查下面目录寻找,作为启动 | |
EFI/Boot/BootX64.efi |
# 第一个 Hello Word
一个基本的
UEFI程序
需要包括.c文件
,以及.inf文件模块
工程文件,描述模块的属性
,提供依赖库
,支持的CPU架构
等信息。
# 存储内容
EDK2文件夹
下分为两个文件edk2
和edk2-libc
- 在
EDK2文件夹
下创建mybuild.bat,
设置基本环境配置
set WORKSPACE=%CD% | |
set EDK_TOOLS_PATH=%CD%\edk2\BaseTools | |
set CONF_PATH=%CD%\edk2\Conf | |
set PACKAGES_PATH=%CD%\edk2;%CD%\edk2-lib |
- 在根目录的
AppPkg
目录Applications
目录下创建目录 - 分别创建
.c
和.inf
文件 - 在
Application
同级目录的AppPkg.dsc
文件中添加文件.inf模块
工程文件内容
[Components] | |
AppPkg/Applications/ShellApp_Main/ShellApp_Main.inf | |
AppPkg/Applications/Stdlib_Main/Stdlib_Main.inf | |
AppPkg/Applications/Uefi_Main/Uefi_Main.inf |
# 入口函数
# Uefi_Main.c
/** @file | |
This is Sample of UEFI aplication. | |
* **/ | |
#include <Uefi.h> | |
#include <Library/UefiLib.h> | |
/** | |
The user Entry Point for Application. The user code starts with this function | |
as the real entry point for the application. | |
@param[in] ImageHandle The firmware allocated handle for the EFI image. | |
@param[in] SystemTable A pointer to the EFI System Table. | |
EFI_STATUS typedef UINT64 UINTN; | |
@retval EFI_SUCCESS The entry point is executed successfully. | |
@retval other Some error occurs when executing this entry point. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
UefiMain ( | |
IN EFI_HANDLE ImageHandle, //Image-Handle 指向模版自身加载到内存的 Image 对象,也就是 Image 对象的句柄 | |
IN EFI_SYSTEM_TABLE *SystemTable //SystemTable 是 UEFI 应用与 UEFI 内核交互的桥梁,通过它可以获得 UEFI 提供的各种服务,包括 BS 服务,和 RT 服务 | |
) | |
{ | |
EFI_TIME curTime; // 时间函数结构体 | |
Print(L"Hello,this is Entry of UefiMain!\n"); // 使用 Print 方法输出 | |
// 使用 BootService 和 RuntimeService | |
SystemTable->BootServices->Stall(2000); // 延时 2 秒 | |
/* | |
调用了在 RuntimeServices 里面的 GetTime 函数指针 (&curTime,NULL) 为参数 | |
RuntimeServices 为结构体 | |
将得到的时间结构体赋值给了 curTime 结构体 | |
*/ | |
SystemTable->RuntimeServices->GetTime(&curTime,NULL); | |
// 通过结构体输出 | |
Print(L"Current Time: %d-%d-%d %02d:%02d:%02d\n",curTime.Year,curTime.Month,curTime.Day,curTime.Hour,curTime.Minute,curTime.Second); | |
Print(L"NanoTime:%d\n",curTime.Nanosecond); | |
Print(L"TimeZone:%d\n",curTime.TimeZone); | |
Print(L"Pad1:%d\n",curTime.Pad1); | |
Print(L"Pad2:%d\n",curTime.Pad2); | |
// 使用 SystemTable | |
// 使用 SystemTable 的输出函数调用进行输出 | |
SystemTable->ConOut->OutputString(SystemTable->ConOut,L"Test SystemTable...\n\r"); | |
return EFI_SUCCESS; //return 0 | |
} |
# Uefi_Main.inf
[Defines] | |
INF_VERSION = 0x00010005 | |
BASE_NAME = Hello | |
FILE_GUID = f35a7352-2cc1-44c0-9ba6-4c3b5f4dbe42 | |
MODULE_TYPE = UEFI_APPLICATION | |
VERSION_STRING = 1.0 | |
ENTRY_POINT = UefiMain | |
[sources] | |
Hello.c | |
[Packages] | |
MdePkg/MdePkg.dec | |
#MdeModulePkg/MdeModulePkg.dec | |
[LibraryClasses] | |
UefiApplicationEntryPoint | |
UefiLib |
# ShellApp_Main.c
#include <Uefi.h> | |
#include <Library/UefiLib.h> | |
#include <Library/ShellCEntryLib.h> | |
#include <Library/UefiBootServicesTableLib.h> //gST,gBs | |
#include <Library/UefiRuntimeServicesTableLib.h> //gRT | |
/*** | |
Print a welcoming message. | |
Establishes the main structure of the application. | |
@retval 0 The application exited normally. | |
@retval Other An error occurred. | |
***/ | |
INTN | |
EFIAPI | |
ShellAppMain ( | |
IN UINTN Argc, | |
IN CHAR16 **Argv | |
) | |
{ | |
EFI_TIME curTime; | |
Print(L"Hello,this is Entry of ShellAppMain!\n"); | |
// 使用 BootService 和 RuntimeService | |
//gST->BootServices->Stall (2000); // 延时 2 秒 | |
gBS->Stall(2000); | |
// gST->RuntimeServices->GetTime(&curTime,NULL); | |
gRT->GetTime(&curTime,NULL); | |
Print(L"Current Time: %d-%d-%d %02d:%02d:%02d\n",curTime.Year,curTime.Month,curTime.Day,curTime.Hour,curTime.Minute,curTime.Second); | |
// 使用 SystemTable | |
gST->ConOut->OutputString(gST->ConOut,L"Test SystemTable...\n\r"); | |
return(0); | |
} |
# ShellApp_Main.inf
## @file | |
# Sample UEFI Application Reference EDKII Module. | |
# | |
# This is a sample shell application that will print Hello,this is Entry of ShellAppMain!" to the | |
# UEFI Console. | |
# | |
## | |
[Defines] | |
INF_VERSION = 0x00010006 | |
BASE_NAME = ShellApp_Main | |
FILE_GUID = a912f198-7f0e-4813-b918-b757b106ec83 | |
MODULE_TYPE = UEFI_APPLICATION | |
VERSION_STRING = 0.1 | |
ENTRY_POINT = ShellCEntryLib | |
# | |
# VALID_ARCHITECTURES = IA32 X64 | |
# | |
[Sources] | |
ShellApp_Main.c | |
[Packages] | |
MdePkg/MdePkg.dec | |
ShellPkg/ShellPkg.dec | |
[LibraryClasses] | |
UefiLib | |
ShellCEntryLib |
# Stdlib_Main.c
#include <Uefi.h> | |
#include <Library/UefiLib.h> | |
#include <Library/ShellCEntryLib.h> | |
#include <Library/UefiBootServicesTableLib.h> //gST,gBs | |
#include <Library/UefiRuntimeServicesTableLib.h> //gRT | |
#include <stdio.h> | |
#include <stdlib.h> | |
/*** | |
Demonstrates basic workings of the main() function by displaying a | |
welcoming message. | |
Note that the UEFI command line is composed of 16-bit UCS2 wide characters. | |
The easiest way to access the command line parameters is to cast Argv as: | |
wchar_t **wArgv = (wchar_t **)Argv; | |
@param[in] Argc Number of argument tokens pointed to by Argv. | |
@param[in] Argv Array of Argc pointers to command line tokens. | |
@retval 0 The application exited normally. | |
@retval Other An error occurred. | |
***/ | |
int | |
main ( | |
IN int Argc, | |
IN char **Argv | |
) | |
{ | |
EFI_TIME curTime; | |
Print(L"Hello,this is Entry of ShellAppMain!\n"); | |
printf("Hello,this is Entry of main!\n"); | |
// 使用 BootService 和 RuntimeService | |
gST->BootServices->Stall(2000); // 延时 2 秒 | |
gBS->Stall(2000); // 调用全局变量 | |
// 获取时间,真实的函数只有一个,而实现函数的函数指针有多个 | |
gST->RuntimeServices->GetTime(&curTime,NULL); | |
gRT->GetTime(&curTime,NULL); | |
printf("Current Time: %d-%d-%d %02d:%02d:%02d\n",curTime.Year,curTime.Month,curTime.Day,curTime.Hour,curTime.Minute,curTime.Second); | |
// 使用 SystemTable | |
gST->ConOut->OutputString(gST->ConOut,L"Test SystemTable...\n\r"); | |
return 0; | |
} |
# Stdlib_Main.inf
## @file | |
# Sample UEFI Application Reference EDKII Module. | |
# | |
# This is a sample shell application that will print Hello,this is Entry of main!" to the | |
# UEFI Console. | |
# | |
## | |
[Defines] | |
INF_VERSION = 0x00010006 | |
BASE_NAME = Stdlib_Main | |
FILE_GUID = 4ea97c46-1491-4dfd-b412-747010f31e5f | |
MODULE_TYPE = UEFI_APPLICATION | |
VERSION_STRING = 0.1 | |
ENTRY_POINT = ShellCEntryLib | |
# | |
# VALID_ARCHITECTURES = IA32 X64 | |
# | |
[Sources] | |
Stdlib_Main.c | |
[Packages] | |
StdLib/StdLib.dec | |
MdePkg/MdePkg.dec | |
ShellPkg/ShellPkg.dec | |
[LibraryClasses] | |
LibC | |
LibStdio |
# DSC 添加编译
将添加到
AppPkg
目录下的文件添加到Components
目录下
[Components] | |
AppPkg/Applications/ShellApp_Main/ShellApp_Main.inf | |
AppPkg/Applications/Stdlib_Main/Stdlib_Main.inf | |
AppPkg/Applications/Uefi_Main/Uefi_Main.inf |
# 修改输出位置
为了
方便EDK2运行环境
执行,固将AppPkg.dsc
输出的目录
修改为EDK2
编译环境的执行位置
,方便测试执行
修改下面对应的
[Defines] | |
OUTPUT_DIRECTORY = Build/EmulatorX64/DEBUG_VS2019/X64 # 输出到指定的位置 |
# EDK2 编译
# 编译mybuild.bat | |
mybuild.bat | |
edk2\edksetup.bat | |
# 编译edk2的UEFI模拟器 | |
build -p edk2\EmulatorPkg\EmulatorPkg.dsc -t VS2019 -a X64 | |
# 编译UEFI程序 | |
build -p edk2-libc\AppPkg\AppPkg.dsc -t VS2019 -a X64 | |
# 清理编译结果,如果不清空,存在无法覆盖问题 | |
build clean -p edk2\EmulatorPkg\EmulatorPkg.dsc -t VS2019 -a X64 | |
# 清理edk2-lib2 | |
build clean -p edk2-libc\AppPkg\AppPkg.dsc -t VS2019 -a X64 |
编译完成
之后会创建Build目录
- 运行
build
即可对刚才输入的程序
进行编译
成efi可执行
文件- 进入
WinHost.exe
运行对应的文件名
即可运行成功
# 输入如下指令,进入根目录 | |
FS0: | |
# 输入 Hello 点回车自动补全,即可进入 HelloWorld.efi 输出 UEFI Hello World! | |
Hello.efi |
运行结束后,执行
build clean
清空缓存,以防止再次编译无法覆盖
问题。
# 相关指令
# 启动 UI 的 BIOS 页面 | |
UiApp.efi |
# 编译运行
在 edk2 的跟页面启动 CMD, 运行
edksetup.bat | |
# 设置之后修改 conf 中的 target.txt | |
edksetup.bat rebuild | |
# 通过 build 构建,此方法构建的是默认 conf 设置的包路径 | |
build |
# 鼠标抓取小项目
首先将
鼠标
和键盘
的时间绑定
到events事件
里
- 通过
while(1)
循环- 等待鼠标或键盘事件发生,
0
为鼠标
,其它为键盘
- 当为
数
表示时获取当前鼠标
的状态,根据其状态输出其对应的位置
和点击时间
,输出左键或右键- 键盘的话,判断其对应的是否为
q
, 若为q
将break
,结束循环
- 若键盘输入
不为k
,输出
其值后续可逐步进化到鼠标
控制UEFI
界面
小Bug
:
- 键盘上
输入
的字母
,需要点一下鼠标
才能显示出来。
# Mouse_Main.c
#include <Uefi.h> | |
#include <Library/UefiLib.h> | |
#include <Library/UefiBootServicesTableLib.h> //gST,gBs | |
#include <Library/UefiRuntimeServicesTableLib.h> | |
#include <Protocol/SimplePointer.h> //mouse point include Protocol | |
// Include/Protocol/SimplePointer | |
EFI_GUID gEfiSimplePointerProtocolGuid = { 0x31878C87, 0x0B75, 0x11D5, { 0x9A, 0x4F, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D }}; | |
EFI_STATUS | |
EFIAPI | |
UefiMain( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
EFI_TIME curTime; | |
Print(L"mouse clicked grap!\n"); | |
SystemTable->ConOut->OutputString(SystemTable->ConOut,L"mouse clicked grap\n"); | |
gRT->GetTime(&curTime,NULL); | |
Print(L"Clicked Time: %d-%d-%d %d-%d-%d-%d\n",curTime.Year,curTime.Month,curTime.Day,curTime.Hour,curTime.Minute,curTime.Second,curTime.Nanosecond); | |
// Enable Cursor | |
EFI_STATUS Status; | |
EFI_SIMPLE_POINTER_PROTOCOL* mounse = 0; | |
EFI_SIMPLE_POINTER_STATE State; | |
EFI_EVENT events[2]; // 键盘鼠标事件 | |
// 显示光标 | |
gST->ConOut->EnableCursor(gST->ConOut,TRUE); | |
// 根据 GUID 值找出鼠标设备 | |
Status = gBS->LocateProtocol( | |
&gEfiSimplePointerProtocolGuid, | |
NULL, | |
(VOID*)&mounse | |
); | |
// 错误判断 | |
if(EFI_ERROR(Status)) | |
{ | |
Print(L"Unable to initialize EFI_SIMPLE_POINTER_PROTOCOL protocol interface!\n"); | |
} | |
// 重置鼠标设置 | |
Status = mounse->Reset(mounse,TRUE); | |
// 错误判断 | |
if(EFI_ERROR(Status)) | |
{ | |
Print(L"Unable to initialize EFI_SIMPLE_POINTER_PROTOCOL protocol interface!\n"); | |
} | |
// 将鼠标事件放到等待事件数组 | |
events[0] = mounse->WaitForInput; | |
// 将键盘也放到等待数组中 | |
events[1] = mounse->WaitForInput; | |
// 使用循环读取 | |
while (1) | |
{ | |
/* code */ | |
EFI_INPUT_KEY Key; | |
UINTN index = 0; | |
// 等待 events 中的任一事件发生,根据时间判断键盘还是鼠标 | |
Status = gBS->WaitForEvent(2,events,&index); | |
// 根据 index 的不同值会传出不同的事件 | |
if(index == 0) | |
{ | |
if(State.LeftButton) | |
{ | |
Print(L"Pressed Left\n"); | |
} | |
else if(State.RightButton) | |
{ | |
Print(L"Pressed Right\n"); | |
} | |
// 获取鼠标状态并输出 | |
Status = mounse->GetState(mounse,&State); | |
gRT->GetTime(&curTime,NULL); | |
Print(L"Clicked Time minuter second N-Second:%d - %d - %d\t",curTime.Minute,curTime.Second,curTime.Nanosecond); | |
Print(L"X:%d Y:%d Z:%d L:%d R:%d\n", | |
State.RelativeMovementX, | |
State.RelativeMovementY, | |
State.RelativeMovementZ); | |
} | |
else | |
{ | |
// 按键盘事件发生后读取键盘 | |
Status = gST->ConIn->ReadKeyStroke(gST->ConIn,&Key); | |
// 错误判断 | |
if(EFI_ERROR(Status)) | |
{ | |
// Print (L"95 Read Key Stroke Error\n"); // 莫名多输入 | |
} | |
// 按 q 键退出 | |
if(Key.UnicodeChar == 'q') | |
{ | |
Print(L"Key Board %c Was Pressed\n",Key.UnicodeChar); | |
// SystemTable->ConOut->OutputString(SystemTable->ConOut,L"Key input:%s\n",Key.UnicodeChar); | |
// Print(L"Key input:%s\n",Key.UnicodeChar); | |
break; | |
} | |
else if(Key.UnicodeChar) // 当键盘有数据输入时,对其进行输入,若没有不操作 | |
{ | |
Print(L"Key Board %c Was Pressed\n",Key.UnicodeChar); | |
} | |
} | |
} | |
return EFI_SUCCESS; | |
} |
# Mouse_Main.inf
[Defines] | |
INF_VERSION = 0x00010007 | |
BASE_NAME = Mouse_Main | |
FILE_GUID = b579648b-5331-46a4-93ce-22423e567f55 | |
VERSION_STRING = 0.1 | |
MODULE_TYPE = UEFI_APPLICATION | |
ENTRY_POINT = UefiMain | |
[Sources] | |
Mouse_Main.c | |
[Packages] | |
MdePkg/MdePkg.dec | |
MdeModulePkg/MdeModulePkg.dec | |
[LibraryClasses] | |
UefiApplicationEntryPoint | |
UefiLib |
# 应用程序加载过程
一个
应用程序
被编译成.efi
文件,整体上分成三步
UefiMain.c
被编译成目标文件UefiMain.obj
(程序编译后的二进制代码)- 连接器将目标文件
UefiMain.obj
和其他库
连接成UefiMain.dll
(Dynamic Link LIbrary``动态链接库
) GenFW工具
将UefiMain.dl
转换成UefiMain.efi
# 报错解决思路
# 问题 error 7000
error 7000: Failed to execute command Vc\bin\nmake.exe /nologo tbuild
# 解决
通过
UltraEdit
查看中文注释
是否存在乱码
等行为.解决乱码就 OK 啦,本身使用 VS Code 输入的莫名乱码,查 Bug 查了好久。
# file read failture
inf 模版工程文件不可有中文,包括
注释
,中文会报错
# 注意性报错
在
输出
的字符串
要加上L
# fatal error LNK1120
链接失败
,可能为在引用GUID地址
时,没有
在当前函数
对其进行赋值
在引用
gEfiSimplePointerProtocolGuid
是在文件顶部
添加其对应的GUID
唯一编码。