入口函数
一个简单的
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
UefiLibShellApp_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
ShellCEntryLibStdlib_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
LibStdioDSC添加编译
将添加到
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唯一编码。