深入EFI了解背后的运行

EFI介绍

EFI全名Extended Firmware Interface,一种应用于固态硬盘的启动引导,作为Bios后新发展的PC扩展接口升级。在Windows系统安装中,会在硬盘分出一片区域,名字就是efi,用于引导系统的启动(之前我卸载过这个分区,印象很深)

对于更多EFI启动的流程可以看下面的图片,里面包含了一套完整的EFI周期

更多的EFI介绍可以参考百度,现在不关心历史只关系用处🤣

安装解释EDK

EDK也就是EFI Development Kit,类似JDK一样,EDK将会是开发efi文件的库,我们在ubuntu下操作,安装EDK2

1
git clone https://github.com/tianocore/edk2.git

一些依赖库文件的下载指令放在下面,当然是ubuntu的apt,如果你是debian就改成yum

1
apt install build-essential uuid-dev iasl git gcc-5 nasm python3-distutils

文件指针切入edk2目录下,导入一些子项目

1
git submodule update --init

安装完成会出现一大堆文件,这些文件夹的共性就是都以Pkg结尾,其中关注两个文件夹MdePkgBaseTools,前者是库而后者就是工具,因为我们要自己写Efi,所以我们新建一个文件夹,就叫MinPkg

1
2
3
4
5
6
7
8
9
root@minloha:~/LearnEFI/code/edk2# ls
ArmPkg CryptoPkg FatPkg Maintainers.txt pip-requirements.txt SourceLevelDebugPkg
ArmPlatformPkg DynamicTablesPkg FmpDevicePkg MdeModulePkg ReadMe.rst StandaloneMmPkg
ArmVirtPkg edksetup.bat IntelFsp2Pkg MdePkg RedfishPkg UefiCpuPkg
BaseTools edksetup.sh IntelFsp2WrapperPkg NetworkPkg SecurityPkg UefiPayloadPkg
Conf EmbeddedPkg License-History.txt OvmfPkg ShellPkg UnitTestFrameworkPkg
CONTRIBUTING.md EmulatorPkg License.txt PcAtChipsetPkg SignedCapsulePkg
root@minloha:~/LearnEFI/code/edk2# mkdir MinPkg
root@minloha:~/LearnEFI/code/edk2#

强调两个文件类型.dsc.dec,前者是对当前Pkg的描述后者是用于声明公开接口的描述。找到最经典的Hello world代码存放位置,里面保存了.inf和源码.c文件,这里注意,因为是底层所以没有标准库,也就意味着所有的输出函数和变量定义都必须重新写,也就是无法再像以往写代码一样去定义变量了

1
2
3
4
root@minloha:~/LearnEFI/code# cd edk2/MdeModulePkg/Application/HelloWorld
root@minloha:~/LearnEFI/code/edk2/MdeModulePkg/Application/HelloWorld# ls
HelloWorld.c HelloWorldExtra.uni HelloWorld.inf HelloWorldStr.uni HelloWorld.uni
root@minloha:~/LearnEFI/code/edk2/MdeModulePkg/Application/HelloWorld#

打开.inf进行解释,内容如下,[Defines]定义了程序的版本、程序名、引导UID、版本以及程序入口,[Sources]写出了程序所需要的资源文件,即源码和配置。[Packages]定义了所需要EDK的包,[LibraryClasses]列举了使用的接口,[Pcd]规定了一些常量字符串,引用了.dec文件中所写的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
## @file
# Sample UEFI Application Reference EDKII Module.
#
# This is a sample shell application that will print "UEFI Hello World!" to the
# UEFI Console based on PCD setting.
#
# It demos how to use EDKII PCD mechanism to make code more flexible.
#
# Copyright (c) 2008 - 2018, Intel Corporation. All rights reserved.<BR>
#
# SPDX-License-Identifier: BSD-2-Clause-Patent
#
#
##

[Defines]
INF_VERSION = 0x00010005
BASE_NAME = HelloWorld
MODULE_UNI_FILE = HelloWorld.uni
FILE_GUID = 6987936E-ED34-44db-AE97-1FA5E4ED2116
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 1.0
ENTRY_POINT = UefiMain

#
# This flag specifies whether HII resource section is generated into PE image.
#
UEFI_HII_RESOURCE_SECTION = TRUE

#
# The following information is for reference only and not required by the build tools.
#
# VALID_ARCHITECTURES = IA32 X64 EBC
#
[Sources]
HelloWorld.c
HelloWorldStr.uni

[Packages]
MdePkg/MdePkg.dec
MdeModulePkg/MdeModulePkg.dec

[LibraryClasses]
UefiApplicationEntryPoint
UefiLib
PcdLib

[FeaturePcd]
gEfiMdeModulePkgTokenSpaceGuid.PcdHelloWorldPrintEnable ## CONSUMES

[Pcd]
gEfiMdeModulePkgTokenSpaceGuid.PcdHelloWorldPrintString ## SOMETIMES_CONSUMES
gEfiMdeModulePkgTokenSpaceGuid.PcdHelloWorldPrintTimes ## SOMETIMES_CONSUMES

[UserExtensions.TianoCore."ExtraFiles"]
HelloWorldExtra.uni

EDK的编译是通过make进行的,我们需要切换到EDK2的目录下的Conf文件夹修改target.txt来改变编译输出。

C的指针很强大

指针是C语言的核心,在深入EFI底层前,必须要先复习一下C指针的含义,随便写两句代码

1
2
3
4
5
6
7
8
9
#include "stdio.h"

int main(){
int age = 20;
int *p_age = &age;
printf("age=%d,address=%p\n",age,&age);
printf("p_age=%d,address=%p,paddress=%p\n",*p_age,p_age,&p_age);
return 0;
}

利用gcc编译并运行,可以看到age与p_age的内存地址address是一个,但是paddress却和address不同

p_age作为指向age内存的指针,内部必然存储了age变量的内存地址,对p_age变量而言,输出的address就是保存age的内存地址,而paddress输出的是指针在内存的位置,通俗的说就是一个是复制别人的,一个是自己保存的。

1
2
3
4
5
root@minloha:~/LearnEFI/C/pointer# gcc main.c
root@minloha:~/LearnEFI/C/pointer# ./a.out
age=20,address=0x7ffee75e2bac
p_age=20,address=0x7ffee75e2bac,paddress=0x7ffee75e2bb0
root@minloha:~/LearnEFI/C/pointer#

pointer

指针就是保存地址的变量,地址就是一个可以被指的指针。在底层操作中,直接操作变量是无意义的,因为无法从根本上修改变量内容,所以利用指针进行操作内存,直接修改数据是非常高效的。

编译出EFI

在EDK2目录中使用指令,可以编译出.efi文件

1
make -C BaseTools

第一次编译会出现问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
make[2]: Leaving directory '/root/LearnEFI/code/edk2/BaseTools/Source/C/DevicePath'
Finished building BaseTools C Tools with HOST_ARCH=X64
make[1]: Leaving directory '/root/LearnEFI/code/edk2/BaseTools/Source/C'
make -C Source/Python
make[1]: Entering directory '/root/LearnEFI/code/edk2/BaseTools/Source/Python'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/root/LearnEFI/code/edk2/BaseTools/Source/Python'
make -C Tests
make[1]: Entering directory '/root/LearnEFI/code/edk2/BaseTools/Tests'
/bin/sh: 1: python: not found
make[1]: *** [GNUmakefile:11: test] Error 127
make[1]: Leaving directory '/root/LearnEFI/code/edk2/BaseTools/Tests'
make: *** [GNUmakefile:19: Tests] Error 2
make: Leaving directory '/root/LearnEFI/code/edk2/BaseTools'
root@minloha:~/LearnEFI/code/edk2#

没错,python: not found,原因是python版本问题,解决方法就是先找到python位置,然后就要把python3.8与python连接在一起。

1
2
3
4
5
6
root@minloha:~/LearnEFI/code/edk2# whereis python
python: /usr/bin/python3.8-config /usr/bin/python3.8 /usr/bin/python2.7 /usr/lib/python3.8 /usr/lib/python2.7 /usr/lib/python3.9 /etc/python3.8 /etc/python2.7 /usr/local/lib/python3.8 /usr/local/lib/python2.7 /usr/include/python3.8
root@minloha:~/LearnEFI/code/edk2# sudo ln -s /usr/bin/python3.8 /usr/bin/python
root@minloha:~/LearnEFI/code/edk2# python --version
Python 3.8.10
root@minloha:~/LearnEFI/code/edk2#

然后重新make一下就可以了。为了运行.efi我们要一个软件qemu,安装一下吧。

结语

在了解这么多关于EFI的知识和C语言的操作后,相信你一定可以写出一个自己的EFI系统了,年三十晚的一篇文字

新年快乐哟!


深入EFI了解背后的运行
https://blog.minloha.cn/posts/003100a90a5a042022013100.html
作者
Minloha
发布于
2022年1月31日
更新于
2023年12月21日
许可协议