HelloWorld Application
- 前言
- 为什么是HelloWorld
- Application又是什么
- 代码部分
- test_application.c文件
- test_application.inf
- 编译运行
- 总结
前言
毕业之后工作开始接触UEFI,现在为止也不过短短的四个月,UEFI开发涉猎面广,知识体系庞杂,参考文献却相对少的可先,所以在此将自己学习过程中一些问题、成果、问题、经验进行一个记录,一来方便自己的复习,二来能给一些入坑的伙伴一点方向。
我自己磕磕绊绊,发现书、经验贴上的知识都是高屋建瓴,很少从基础一点一点说起,让我摸不到头脑,因此我从最简单的地方一点一点把初学者可能遇到的问题写下来,帮助大家少走弯路。
为什么是HelloWorld
我本人是从C++语言转到这个方向,不能说毫不相关,只能说关系不大。UEFI中主要使用的还是C语言,但这不是主要的问题,我觉得从我的角度出发,对于一个UEFI的初学者,理解UEFI整个过程中的概念、流程才是最难的。
通过网上的资料我们能够很容易的知道UEFI是OS前的一个过程,相当于传统BIOS的替代品或者说是优化版本。但是又有几个人能够详细的说明白传统BIOS在OS前是怎么工作的?可能对于大神很容易,但是对我,这个问题很难。所以我更加不能理解UEFI的详细内容。Lucky,经过这几个月,我想明白了,我可以慢慢的去理解具体的结构,转而把实践放在第一位,遇到问题之后再去解决研究可能效果更好。
在学习C、C++语言的时候,我们写的第一个程序一般就是HelloWorld,简单的一句HelloWorld,向我们展示了程序的魅力,更重要的是,直观的展现了Code与Cout的关系。听别人说代码很抽象,但是如果自己能够在代码上进行修改,比如把HelloWorld
改成干啥呢 老铁
,然后在屏幕上观察两次输出的不同就非常的直观。按照这个思路,我选择了HelloWorld作为自己学习UEFI的第一个小目标:在屏幕上输出自己的HelloWorld
Application又是什么
如果现在你一定要让我解释清楚什么是UEFI中的Application模块,那我只能说一声打扰了。对于Driver、Application这种划分,目前我自己也是迷迷糊糊,因此,也不敢在此胡言乱语。但是为了能够让初学的人理解,我还是想用我自己的方式介绍一下,此处纯属个人理解,不保证正确性,如有错误请见谅。
首先,前面已经说了UEFI的过程细节我并不全部了解,但为了不影响后续的学习和说明,我将整个UEFI过程进行了一句话概括:UEFI就是为OS创造完备运行环境的准备阶段。也就是说,UEFI运行完成之后没就算没有进入传统的OS比如Windows、Linux,也能够通过某种方式进行类似操作系统的各种操作——最简单的操作就是输入输出。
UEFI的大牛们已经贴心的为我们准备好了UEFI Shell(把他想象成基于UEFI的OS),我们就可以在UEFI Shell下面安装自己的Application,并且让自己的Application执行相应的功能。这样是不是容易理解了?UEFI Application 就是 基于UEFI Shell的能够执行任务的Application(相当于OS下的软件)。Application加载的过程就可以看成是软件运行的过程,Application运行的结果就是软件执行的结果。Ok?
注意:本节的解释只是为了容易理解,和实际UEFI的运行无关!!!
代码部分
说了这么多,可能你也是蒙圈的,是的,刚开始学的我也是,那既然不明白,不如直接上Code。前面已经说过了UEFI 是C语言主打,那么我们就从.C文件开始。
test_application.c文件
代码直接上
#include <Uefi.h>EFI_STATUS
EFIAPI
Test_Application_Entry (IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE *SystemTable
){SystemTable->ConOut->OutputString(SystemTable->ConOut,L"HelloWorld\n");return EFI_SUCCESS;
}
上面就是想要输出HelloWorld的最简单的C代码。代码很简单核心只有一行,但是对于啥也不懂的新人(比如我),刚刚开始学的时候可能就要发问了 EFI_SYSTEM_TABLE
表示什么啊?EFI_HANDLE
表示什么啊?SystemTable
是什么样子的啊?我现在告诉你,作为一个新人,你不需要知道的非常详细,你需要知道的就是如下几点:
- 这是一个入口函数,这种函数的开头和结尾是固定的格式,UEFI Application的入口函数都必须这么写。
格式模板:
EFI_STATUS
EFIAPI
FunctionName(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE *SystemTable
){//Code Bodyreturn EFI_SUCCESS;
}
- UEFI Application的C代码都必须包含
<Uefi.h>
文件。这个文件定义了最最基础的结构,是整个UEFI程序搭建的基础。 SystemTable->ConOut->OutputString(SystemTable->ConOut,L"HelloWorld\n");
是Application中输出的固定语句,将想要输出的内容进行替换即可。(替换哪里不需要说吧)
掌握了以上三点,本片文章C代码部分的精髓你就已经掌握了。
test_application.inf
在UEFI的开发中,会看到众多的文件格式,包括但是不限于:.c
.h
.sdl
.inf
.dec
.dsc
.fdf
.sd
,涉及到的我们在一个一个的忽悠你 解释。本篇文章中除了.c
文件外需要自己编写的文件就是.inf
文件。
.inf
(Module Information File)文件是模块(挖个坑,后面写文章总结这些个用语)的工程文件,其作用相当于Makefile文件或者VisualStudio的.proj
文件1。一句话概括就是:.inf
文件是告诉编译器模块可能使用的其他模块以及本模块的基本情况。
上代码:
[Defines]INF_VERSION = 0x00010015BASE_NAME = Test_ApplicationFILE_GUID = 16BEFBED-60DC-4EA2-8E81-A3430A6C2117MODULE_TYPE = UEFI_APPLICATIONVERSION_STRING = 1.0ENTRY_POINT = Test_Application_Entry[Sources]test_application.c[Packages]MdePkg/MdePkg.dec[LibraryClasses]UefiApplicationEntryPoint
从上面的,Code可以看出,.inf
由[Name]
为分界分成不同的块,[Name]
表示块的名称,块名独占一行才能生效。上述四个块是每一个.inf
必须的块,还有一些非必须的块,用到再说。先解释上面四个块的情况:
- Defines块
下面是Application的Defines块模板。
[Defines]INF_VERSION = 0x00010015 BASE_NAME = XXXXXFILE_GUID = XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX (8-4-4-4-12格式)MODULE_TYPE = UEFI_APPLICATIONVERSION_STRING = 1.0ENTRY_POINT = XXXXX
INF_VERSION
版本号 ,不赘述
BASE_NAME
表示本模块的名字,最后输出的.efi
文件也是这个名字
FILE_GUID
GUID是一个唯一的标记,通过这一串唯一的标记可以找到对应的模块,想象成人的身份证就比较容易理解了。
MODULE_TYPE
Application的时候必须这么写。其他情况遇到再说
VERSION_STRING
不重要 1.0就行
ENTRY_POINT
本模块的入口函数的名称,就是.c
文件中函数体的名称
-
Sources 块
这部分相对简单,只需要写出本模块使用到的相关文件就行。 -
Package 块
使用相对路径写出使用到的包的对应.dec
文件。
我们用到了<Uefi.h>
文件,这个文件在MdePkg
下,所以引用对应的.dec
文件。 -
LibraryClasses块
描述本模块使用的库。所有的Application都要连接UefiApplicationEntryPoint
库 ,写上就完事。
以上涉及的很多名词三言两语解释不清楚,本篇文章需要的知识已经说明或者强加给了读者,详细规范的整理可能需要等我学习后写了。
编译运行
(如果你还没有完成基本的环境安装、配置等,可以参考安装教程先将环境进行配置。)
需要的基本代码已经编译完成,接下来就是如何在UEFI Shell上让这个Application运行起来。
在此之前还需要进行一步操作,将test_application.inf
文件添加到MdeModulePkg的.dsc
的Component
部分。
注意了,我添加在MdeModulePkg的.dsc
是因为我将这个文件夹放在了MdeModulePkg下面,如果你不是,请在对应文件夹的.dsc
文件夹下添加。
为了说明文件夹的关系,这里放个插图:
接下来就可以进行编译了。
我用的是Windows系统,具体的过程如下:
-
在命令行窗口打开Code所在文件夹,执行edksetup.bat --nt32
执行完毕结果如图(Warning忽略即可):
-
build编译
-
启动模拟器 执行命令加载Application
我的模拟器位置:D:\MyWorkSpace\Build\NT32IA32\DEBUG_VS2013x86\IA32\SecMain.exe
请大家按照自己文件的位置找模拟器
启动后就会出现如下三个界面:
加载中(两个一样的):
加载完成(两个一样的):
-
执行命令 运行Application
这个地方实际上是Shell加载了生成了.efi
文件(文件名与BASE_NAME
一致),最后输出对应的结果。最终结果如图。
总结
本片文章主要就是手把手的教你写一个基于UEFI的HelloWorld,以此打消新手对UEFI的恐惧,让菜鸟们知道UEFI 和其他的各种代码一样是从HelloWorld起步的,让小白不至于两眼一抹黑。第一次写博客难免有疏漏不足,欢迎指正,也希望有更多志同道合的伙伴一起交流。
文末了 ,我要着重感谢几个CSDN上的大佬 @Hi,Hubery @jiangwei0512,感谢他们在我学习道路上的帮助。最后 感谢@我是管小亮 一路以来的支持和陪伴
参考来源:UEFI原理与编程(戴正华著) ↩︎