# 入口函数

一个简单的 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 映像)。 ImageHandleImage 对象的句柄,作为模版入口函数参数,它表示 模块自身 加载到内存后生成的 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;

# 工程结构

  • PkgPackage 的缩写, 的意思
  • 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文件夹 下分为两个文件 edk2edk2-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

第一个HelloWorld

运行结束后,执行 build clean 清空缓存,以防止 再次编译无法覆盖 问题。

# 相关指令

# 启动 UI 的 BIOS 页面
UiApp.efi

# 编译运行

在 edk2 的跟页面启动 CMD, 运行

edksetup.bat
# 设置之后修改 conf 中的 target.txt
edksetup.bat rebuild 
# 通过 build 构建,此方法构建的是默认 conf 设置的包路径
build

# 鼠标抓取小项目

首先将 鼠标键盘时间绑定events事件

  • 通过 while(1) 循环
  • 等待鼠标或键盘事件发生, 0鼠标 ,其它为 键盘
  • 当为 表示时获取 当前鼠标 的状态,根据其状态输 出其对应的位置点击时间 ,输出左键或右键
  • 键盘的话,判断其对应的是否为 q , 若为 qbreak结束循环
  • 若键盘输入 不为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

UEFI编译过程

# 报错解决思路

# 问题 error 7000

error 7000: Failed to execute command Vc\bin\nmake.exe /nologo tbuild

error 7000

# 解决

通过 UltraEdit 查看 中文注释 是否存在 乱码 等行为.

解决乱码就 OK 啦,本身使用 VS Code 输入的莫名乱码,查 Bug 查了好久。

# file read failture

inf 模版工程文件不可有中文,包括 注释中文会报错

# 注意性报错

输出字符串 要加上 L

# fatal error LNK1120

链接失败 ,可能为在引用 GUID地址 时, 没有当前函数 对其进行 赋值

在引用 gEfiSimplePointerProtocolGuid 是在 文件顶部 添加其 对应的GUID 唯一编码。

链接GUID

此文章已被阅读次数:正在加载...更新于

请我喝[茶]~( ̄▽ ̄)~*

YuHeShui 微信支付

微信支付

YuHeShui 支付宝

支付宝

YuHeShui 贝宝

贝宝