最近,正在学习 iPXE 源码,于是开始各种 Google 查找 iPXE 的资料进行学习。以下就是学习过程中一些感觉比较重要的点,特此记录,以备后续查阅。
上世纪 90 年代初,网卡开始在其扩展卡上包含启动 ROM,每个扩展卡都遵循自己的专有协议来加载和执行引导程序。很快两个开源项目 Etherboot(1995)和 Netboot(1996)成立,并开始提供了具有可插拔驱动程序支持的通用 ROM 映像。
1999 年 Intel 发布 PXE 规范并在之后逐渐成为行业标准。最初的 PXE 客户端固件由英特尔编写(功能比较单一,支持协议较少),并且是作为可链接的 IA32 目标代码格式模块免费提供的,包含在其产品开发套件(PDK)中。
后来(大约 2005 年),Etherboot 开发者开始根据 PXE 标准,重构 Etherboot 源代码,并将新项目命名为 gPXE。此后,gPXE 开始活跃,而 Etherboot 慢慢不再活跃。并从 2010 年开始,Etherboot 没有再更新过。
gPXE 是对 Intel PXE 的扩展,是 PXE 客户端软件和引导加载程序的开源实现。它支持更多互联网协议,功能也更加强大。2010 年 2 月,gPXE-1.0.0 正式发布。源码仓库:http://git.etherboot.org/gpxe.git 。
然而,gpxe.org 和 etherboot.org 域名的所有者希望对项目和代码库进行高度的控制,因此,在 2010 年 4 月,gPXE 的开发者们决定以现有的代码库为起点创建一个名为 iPXE 的新项目。由于这两个项目出现分歧,gPXE 的开发已经停止。自 2011 年以来,gPXE 的代码就没有更新过。
iPXE 是由最初开发 gPXE(从 Etherboot 演化而来)的人开发的,是 gPXE 的官方替代品。它具有 gPXE 的所有功能,用户可以从 gPXE 无缝升级到 iPXE。iPXE 是先前 Intel 的 PXE 实现的超集,支持更多协议。官网是:https://ipxe.org/,源码仓库:https://github.com/ipxe/ipxe/。
根据官网说明,iPXE 使用滚动发布模型,其中每个提交都是为生产准备的。因此,我们总是应该使用最新版!
iPXE 的文档使用的 doxygen 文档系统,在源码中通过使用命令 make doc
或 make [platform]/doc
就可以在 [platform]
目录下生成一个 doc
的目录,文档就位于 doc
中,格式是 HTML 格式(直接打开 index.html
即可)。
- 生成依赖工具:
sudo apt install doxygen graphviz
- 生成的文档也是特定于平台的,例如,
make bin-x86_64-pcbios/doc
生成的文档不会包含 i386 或 arm 的相关介绍。
doxygen 文档系统最主要的就是从源代码中提取特定注释及解析函数调用关系来生成文档,然而,由于源码中的注释并不够详细,因此,实际产生的文档并没有啥用,顶多可以看个函数调用关系。
有些注释依旧是 Etherboot 时代的
相比之下,最有用(详细)的文档应该就是官网的文档或者直接看源代码。 此外,源码目录下的 doc
目录下也包含了俩说明文档(也被 doxygen 包含在了最终的 HTML 文档中)。
iPXE 包含一个交互式命令行,可用于手动启动和诊断问题。当 iPXE 固件启动时,就会在屏幕上打印一个欢迎的横幅信息,此时按下 Ctrl+B
就可以进入命令行模式。使用 help 就可以查看 iPXE 支持的全部命令。
注意,有些命令默认在源码中并没有启用(定义于 src/config/general.h
文件中),因此,上图显示的仅仅是编译该 iPXE 时那些默认启用的命令。iPXE 的全部命令可以直接在官网查看:Command。
其中,命令 show
、set
、clear
可以用来对 iPXE 的各种参数进行显示、设置及清除。设置被划分为了不同的范围。此外,iPXE 还提供了一个交互式配置命令 config [
,使用这个命令可以一步步引导用户进行配置。
可设置的参数可以在 https://www.ipxe.org/cfg 查看。
iPXE 支持灵活的配置脚本。iPXE 的脚本就是一个内容以 #!ipxe
开头的纯文本文件(类似于 Shell 脚本 #!/bin/bash
),iPXE 通过识别文件内容开头的 #!ipxe
来决定是否为一个 iPXE 脚本文件。这个文件不需要任何扩展名,但是一般我们会使用一个 .ipxe
的扩展名来方便表示(类似于 Shell 的 .sh
)。
goto
来进行流程跳转xxx
goto label2
xxx
:label1
xxx
:label2
xxx
&&
和 ||
进行逻辑连接。例如,dhcp && echo DHCP succeeded
只有 dhcp
成功才会执行后面的 echo DHCP succeeded
; dhcp || echo DHCP failed
只有 dhcp
执行失败时,才会执行后面的 echo DHCP failed
。特殊情况 dhcp ||
后面没有任何东西,则表示不关心 dhcp
成功与否(成功或失败后啥也不干)。;
连接起来的命令将忽略前置的执行错误。例如,dhcp ; route
表示不管 dhcp
是否成功,route
都会被执行。#
开头的表示注释。注释必须单独占一行(不支持在代码后面注释的方式)EMBED=xxx
参数将 iPXE 脚本 xxx
内嵌到固件中。这样,iPXE 固件启动后,就会自动执行脚本。例如,make bin/undionly.kpxe EMBED=myscript.ipxe
。${xxx}
命令替换exit
退出脚本。shell
进入 iPXE 命令行,命令行中执行 exit
返回到脚本;执行 config
进入交互式配置模式 现在,在 iPXE 的源代码中很多描述还在使用 gPXE 这个术语,更甚至,最初 Etherboot 时代的很多注释也保留了下来。整个目录结构与最初的 Etherboot 也没啥太大变化,就是多了一些内容。
TODO
iPXE 源码采用了一个名为 Named configurations 的配置机制。这个机制决定了 ./src/config/
目录下的各配置文件(包含其中子目录中的文件)如何引入到源码中,从而实现从相同的代码库构建具有不同配置的二进制文件。
在构建 iPXE 固件时,可以通过显示指明 CONFIG=
从而指明本次编译使用了哪些额外的(自定义的,专用的)配置文件。当我们构建 iPXE 固件时,以下配置文件(.h
文件)会被从上到下依次引入到源码中(其实就是从通用配置 ➜ 专有配置 逐渐过渡,类似 Linux 系统中的 Profile 文件)。
config/defaults/.h
:最顶级的配置文件,总是会被包含使用。这个目录的头文件会根据构建时给出的构建目标进行匹配,例如,make bin-i386-pcbios/ipxe.pxe
,就会匹配 pcbios.h
,详细说明见后文。config/.h
:iPXE 默认的配置文件,总是会被包含使用,属于次级别的配置文件。config//.h
:这里主要是指的 iPXE 针对某些 CASE 预定义配置,属于再次级别的配置文件。注意,这里的
就对应 ./src/config/cloud
、./src/config/qemu
、./src/config/rpi
、./src/config/vbox
这四个文件夹,不要与 CONFIG=
混淆。config/local/.h
:构建系统会自动(第一次构建不存在时)生成这些配置文件。默认情况下,这些文件的内容都是空的。config/local//.h
:如果构建时显示指定了 CONFIG=
,那么构建系统会自动(第一次构建不存在时)创建
文件夹,然后在里面生成各配置文件。默认情况下,这些文件的内容都是空的。 在此机制下,这样就可以实现,对于某些编译,将配置文件单独存放一个文件夹。并且,如果有需要修改配置,只需要修改 config/local/
或者 config/local/
下的文件即可,从而无需修改 iPXE 的源码(注意,配置项是分类放到不同 .h
文件中的,例如 general.h
中定义的是 iPXE 的命令,colour.h
定义的是屏幕输出内容的颜色)。 举例如下:
同时需要注意,只有 Named Configurations 文件支持后面的覆盖前面的,那些定义 iPXE API 的配置文件不可以,这可以避免在切换到不同的命名配置时重新构建整个 iPXE 代码库。全部配置项见 https://www.ipxe.org/buildcfg。
iPXE 采用 Makefile 来组织整个构建过程,主要包含根目录下的 的 Makefile 和 arch/xxx
下的 Makefile 两大部分。其中,这些 Makefile 又根据平台(二进制格式)的不同分为了 Makefile.efi、Makefile.pcbios、Makefile.linux。整个层级关系如下图所示:
注意,编译依赖工具:gcc
、binutils
、make
、perl
、liblzma or xz header files
、mtools
、mkisofs or genisoimage or xorrisofs
、syslinux (isolinux)
。其中,最后俩依赖仅在生成 .iso 时使用。根据自己使用的 Linux 发行版安装即可。
在设计上,iPXE 的所有源代码都会被编译,而不管有没有使用。 官方的解释是,使用 #ifdef
有条件的部分编译会导致很多不用的代码没有编译而得不到有效验证,时间久了就会出现各种问题。独立的功能必须放到一个独立的 .c
文件中,然后,采用了被称为 Linker tables 的处理机制。
Linker tables 的处理机制依赖于 src/config
中的各 .h
(Named configurations 各文件)、src/include/ipxe/tables.h
以及 src/config/config.c
的相关定义及链接脚本。根据此处理机制,例如,如果在 src/config/general.h
中有 #define DOWNLOAD_PROTO_TFTP
或 在 src/config/console.h
中有 #define CONSOLE_SERIAL
时,那么。在构建(任意目标)的链接阶段,tftp.o
和 serial.o
就会被链接。
接下来,还有一个问题需要解决:这些被链接的对象中函数如何互相调用?前面说了,iPXE 固件不采用 #ifdef
,怎么办呢?解决方法就是 Linker tables 机制,定义于 src/include/ipxe/tables.h
中。传统上,通常是如下这样的:
#ifdef CONSOLE_SERIAL
serial_init();
#endif
我们来看看 iPXE 的 Linker tables 机制。以 serial.c
为例,让 serial.c
在初始化函数表中生成一个条目,然后有一个call_init_fns()
函数,它只调用该表中的所有函数。当且仅当 serial.o
被链接进来,然后它的初始化函数将被调用。这样就即避免了连接器符号污染,又避免了 #ifdef
。
链接器脚本负责为我们组装函数表。我们所有的函数表节都有 .tbl.NAME.NN
格式的名称。其中,NAME 指定存储在函数表中的数据结构(例如 init_fns), NN 是一个两位数的十进制数,用于在需要时对表进行排序。NN = 00
保留给指示 “表启动” 的符号,而 NN = 99
保留给指示 “表末端” 的符号。该机制在 iPXE 代码中随处可见,例如,其 main
函数中的调用:
整个构建过程可以分为三个阶段:编译 ➜ 连接 ➜ 转换,正好对应了编译套件经典的编译器 ➜ 连接器 ➜可执行程序处理三步策略。编译阶段又可以划分为好多个子阶段。
为了实现所有代码都会被编译,iPXE 在 Makefile 中使用 SRCDIRS
来指定了所有要编译的源码的目录。使用命令 make srcdirs
就可以查看会被编译的目录。使用命令 make srcs
就可以查看哪些源码文件将被编译。
编译 xxx.C
和 xxx.S
文件的规则在 Makefile.housekeeping
变量 RULE_c
和 RULE_s
中定义。使用这些规则会自动为每个源文件生成MakeFile 规则。生成的规则可以在与每个源文件相对应的 bin/dep/xxx.D
文件中找到。
处理规则优先生成每个源码文件(.s 和 .c)的依赖关系,然后会挨个编译每个源文件,并将产生的 .o 放到 bin 目录下。一旦所有对象都被编译,它们将被收集到名为 bin/blib.a
中的构建库(build library)中。
接下来就是连接阶段,通过只链接在任何特定构建中真正需要的特性就可以保证 iPXE 固件非常小和可定制。这主要是通过我们的构建命令指定特定目标及源码中的 config 目录下的文件内容来实现的。使用命令 make [platform]/[driver].[extension].info
就可以查看我们编译目标的详细信息。
链接最终产生的是
xxx.tmp
(原始二进制文件),xxx.tmp
经过处理才是最终的固件
最后就是进行转换。主要是指将链接产生的 xxx.tmp
转换成最终的二进制镜像。这一步就是我们常见的 objcopy -O binary
。此外,有些 ROM 镜像必须四舍五入到合适的 ROM 大小(例如 16kB 或 32kB),并且需要填写某些报头信息,如校验和,源码的 util
下相应工具会被调用来处理。
这里还有个比较重要的东西:map 文件。.map 文件包含了生成过程需要包含哪些对象及为什么要包含这些对象、逐行处理链接器脚本的结果、最终构建结果的完整符号表。使用命令 make [platform]/[driver].[extension].map
就可以查看 map 文件内容。
iPXE 的 MAKECMDGOALS
与我们平时见的有些不同,它的 MAKECMDGOALS
具有特定的格式:make [platform]/[driver].[extension]
,并且支持一次指定多个(以空格分割)目标(例如,make bin/ipxe.pxe bin-x86_64-pcbios/ipxe.pxe
)。Makefile 中响应的规则会解析目标(定义于根目录的 Makefile.housekeeping
中)以生成不同的结果。
[platform]
:完整格式是 bin[-[
,其中,
可以是 arm32、arm64、i386、x86_64;
又被称为引导类型(boot type),可以是 pcbios 、efi、linux,主要是最终的固件二进制格式不同。
+ [platform]
唯一确定一个固件可运行的平台,全部组合取值如下:
bin
:就是 bin-i386-pcbios
的别名。bin
目录包含在源码中,不知道为啥要包含这个目录,完全没必要存在,编译的时候就自动创建了。bin-i386-pcbios
bin-i386-efi
bin-i386-linux
bin-x86_64-efi
bin-x86_64-linux
bin-x86_64-pcbios
bin-arm32-efi
:目前仅支持 bin-arm32-efi/snp.efi
、bin-arm32-efi/snponly.efi
bin-arm64-efi
编译出的可执行程序是特定于
+ [platform]
的。当编译时,构建系统会自动创建 bin[-[
目录,并把编译过程及最终可执行程序放到 bin[-[
目录下。
源码根目录的 Makefile.housekeeping
中有相应的规则将其中的
和
解析出来。其中,bin
没啥用,arch
则会对应于源码的 arch/$(ARCH)
, platform
则对应了 efi
、linux
、pcbios
,并进一步对应到源码的 arch/$(ARCH)/Makefile.$(PLATFORM)
。
-sb
即 Secure Boot 的缩写,与 UEFI 的安全启动有关。指定 -sb
后,将会在构建中排除一些包含不符合 UEFI 安全引导签名标准的代码的目录。要排除的目录定义在源码根目录的 Makefile 中,在 Makefile.housekeeping
中处理。
[driver]
:代表了最终固件中包含的内容,有些取值需要与指定的 [extension]
配合使用。取值如下:
ipxe
:表示用 iPXE 拥有的(几乎)所有 PCI 网卡驱动构建一个二进制(目前不能用于 ARM 平台,因为有些接口(例如 SNP)还没有实现)。它表示了构建一个完全独立的可执行程序,ipxe.pxe
不加载 UNDI
,也不加载 PXE
,只能通过 iPXE
自己实现的硬件驱动来操作网卡。如果该网卡不被 iPXE
支持,就会出错。808610de
:表示构建包含特定 PCI 设备(指定 pci-id)驱动的二进制程序,格式为 xxxxyyyy
,其中 xxxx
是 vendor_id,yyyy
是 device_id。例如,make bin/808610de.rom
。驱动源码中有这两个值的定义lspci -nn
查看网卡的这两个值。ecm--ncm
:double dash (–) adds multiple drivers to one file, in this case we pull in most USB based drivers (you can add double dash multiple times for more than two drivers)undionly
:与 .kpxe
扩展名一起使用,表示为 pcbios 平台构建一个可链式加载的固件。undionly.kpxe
仅加载 UNDI
而不加载 PXE
,后缀 .kpxe
中的 k
表示 keep UNDI
,undionly.kpxe
会通过 UNDI 复用网卡自身 PXE 软件栈的驱动。snponly
:表示构建一个使用 EFI 提供的 SNP(Simple Network Protocol)或 NII(Network Interfacesnp
:与 snponly
一样,但是它尝试识别所有设备,而不仅仅是链式加载它的那个。ipxe
中默认就包含了该功能。efidrv.cab
:用于 UEFI 安全启动签名tests
:表示构建一个 Linux 测试套件示例。例如,make bin-x86_64-linux/tests.linux
,然后就可以直接运行 ./bin-x86_64-linux/tests.linux
。tap
:表示构建一个 Linux tap 驱动程序,与 .linux
扩展名一起使用。例如,make bin-x86_64-linux/tap.linux
,然后就可以直接运行 ./bin-x86_64-linux/tap.linux –net tap,if=tap0,mac=00:0c:29:c5:39:a1
。slirp
:表示构建一个 Linux slip 驱动程序,与 .linux
扩展名一起使用。例如,make bin-x86_64-linux/slirp.linux
,然后就可以直接运行 ./bin-x86_64-linux/slirp.linux --net slirp
。[extension]
:又被称为 Boot Type(引导类型),在 Makefile 代码中又被称为 Media,决定了最终固件的二进制格式(详见下一节说明)。[extension]
与 [platform]
有一定的对应关系,错误的对应将导致编译错误:
extension | Valid platforms | Description |
---|---|---|
.pxe | pcbios | Headerless X86 assembly code, PXE- or NBP-booted, sometimes renamed to .0 to work on older DHCP/TFTP servers |
.efi | efi | EFI 格式的可执行程序 |
.kpxe | pcbios | 功能与 .pxe 相同,但是 .kpxe 会保留原始的 UNDI 堆栈/驱动程序(但是会卸载 PXE 的基本代码)。undionly 必须与这个扩展名一起使用。 |
.kkpxe | pcbios | 功能与 .kpxe 相同,它会保留 PXE 的基本代码。只能在有 bug 的 bios 中使用。它可用于解决 INT 18 损坏的错误 BIOS,iPXE 退出的唯一方法是返回到 PXE 基代码。 |
.lkrn | pcbios | 编译时使用了 Linux 类似的头文件,可以由许多引导加载程序启动 |
.iso | pcbios | 在 .lkrn 的基础上添加 ISOLINUX 创建的 CD-ROM 镜像,可以由许多引导加载程序启动 |
.hd | pcbios | 放在硬盘上(32KB 块)的直接可执行的 i386 代码 |
.dsk | pcbios | 放在软盘上(512字节块)的直接可执行的 i386 代码 |
.pdsk | pcbios | 将 .dsk 对齐后以供一些需要精确大小的加载器(例如,iLO)使用 |
.usb | pcbios, efi | 在 pcbios 模式下,与 .dsk 相同;在 efi 模式下,它是一个 1440K 的带有分区的镜像,主要用于制作 u 盘镜像 |
.vhd | pcbios | .usb 镜像转换为 vhd |
.raw | pcbios | 通用 RAW 镜像,可以与 RPL 一起使用 |
.rom | pcbios | 用于烧录到 PCI 网卡 ROM 中的镜像 |
.mrom | pcbios | 用于烧录到 PCI 网卡 ROM 中的镜像,与 .rom 相比非常小。并非所有网卡都支持 .mrom 映像。 |
.pcirom | pcbios | 与 .rom 一样 |
.isarom | pcbios | 用于烧录到 ISA 网卡 ROM 的固件,必须与 VirtualBox 等软件一起使用 |
.efidrv | efi | 其他 EFI 固件可以使用的网卡驱动程序 |
.efirom | efi | 用于烧写到 NIC ROM 的 EFI 固件,配合 UEFI 使用 |
.linux | linux | Linux ELF 可执行文件,用于测试,slirp 和 tap 驱动程序 |
为了通用性,还支持一下简化的命令:
make
: 不指定任何目标时,就相当于 make all
make all
: 定义于源码根目录下的 Makefile
中,生成一些预定义的最常见目标,并打印一些有用的提示消息。make everything
: 定义于源码根目录下的 Makefile
中,尝试构建多个平台,并且包含 make all
的全部内容。make vmware
: 定义于源码根目录下的 Makefile
中,编译针对 VMware 的固件make doc
: 等同于 make bin/doc
make [platform]/doc
: 定义于源码根目录下的 Makefile.housekeeping
中,用于生成文档。iPXE 的文档采用的是 doxygen
来生成的。make docview
: 定义于源码根目录下的 Makefile.housekeeping
中,尝试在浏览器中打开 doxygen 文档。 iPXE 固件的使用取决于构建时指定的目标类型,所有固件可以分为两大类:烧录到响应设备的固件(.rom
、.iso
、.usb
等)和可链式加载的固件(.pxe
、.efi
等),这在上一节构建目标中的已经详细介绍过了。
链式加载主要是配合网卡中原有的 PXE 固件使用(目前,很多网卡内置了 Intel 最初的 PXE 固件)。网卡中原有的 PXE 固件运行后,读取链式加载 iPXE 固件(配合 PXE DHCP 服务器),然后,iPXE 运行,重新执行整个 PXE 过程。
详见 Network 之十一 详解 PXE 原理、工作流程、服务端(Tiny PXE Server、Serva、Ubuntu)搭建
这里有必要重点说一些烧录到网卡中的 iPXE 固件(.rom
、.efirom
)。首先,烧录到网卡中的 iPXE 固件在编译时是直接指定 PCI-ID 构建的(例如,make bin/808610de.rom
),也就是说烧录到到网卡的固件仅识别网卡本身(仅包含网卡本身驱动)。
其次,烧录工具一般由网卡厂家提供。例如,Intel 提供了 Intel® Ethernet Flash Firmware Utility(Bootutil),详细使用见 https://ipxe.org/howto/romburning/intel。此外,还有个通用工具 flashrom,具体使用方法见官网及https://ipxe.org/howto/romburning/flashrom。
iPXE 的固件根据构建目标中显示给出的引导类型(boot type)的不同而不同。引导类型(boot type)可以是 pcbios 、efi、linux,他们主要的区别在于产生的固件的二进制格式不同。注意,编译及链接后产生的原始二进制文件为 xxx.tmp
,使用工具(位于 src/util
目录下)处理 xxx.tmp
后才形成的我们实际的固件。
原始的 xxx.tmp
其实是一个 ELF 格式的文件。转换后,EFI 固件的二进制文件格式采用的是微软为 Windows NT 定制的 PE(Portable Executable) 文件格式;linux 采用的是 ELF(Executable and Linkable Format) 文件格式;pcbios 则应该是一个从 ELF 文件中提取出来的只包含程序和数据的二进制文件(还经过了其他处理)。
目前常见的二进制文件格式有:Windows 的 PE、Linux 的 ELF、MacOS 的 Mach-O。
此外,bin-arm32-efi
和 bin-arm64-efi
需要进行交叉编译,因此在编译时需要指定交叉编译器前缀,例如,make CROSS=aarch64-linux-gnu- bin-arm64-efi/ipxe.efi
。而且,根据官网提示,现在不需要指定 ARCH
,Makefile 中的规则会根据平台自动推算 ARCH
的类型。
编译出来的 iPXE 固件不能直接在在当前环境运行。因此,调试有些麻烦,我目前没有找到能用 IDE 在线调试的方法。目前要调试 iPXE 固件还是得通过原始的 printf 法。
Console 就是 iPXE 运行是打印的信息输出的控制台。iPXE 支持多种控制台配置,定义于源码的 config
目录下的 console.h
文件中。默认情况下,PCBIOS 平台固件开启了 CONSOLE_PCBIOS
;EFI 平台固件开启了 CONSOLE_EFI
;Linux 平台固件开启了 CONSOLE_LINUX
。
可选的 Console 还有串口、Syslog 等,启用方式是 #define xxxx
定义对应的宏,例如,启用串口输出 #define CONSOLE_SERIAL
。注意,上面默认的平台控制台默认以备启用,通过 #undef xxx
可以关闭。
- 注意,根据前文说明,不要直接修改
config/console.h
中的定义,而是应该修改config/local/console.h
- 串口的相关参数在
src/config/serial.h
中有定义;Syslog 需要配合宏LOG_LEVEL
来使用,用于配置将报告的日志消息的最低级别,可用取值定义于src/include/syslog.h
文件中。
最后需要在说一下,Console 的实现方式是,只要定义了对应的宏值,就会将对应的 .c
文件添加到构建中。对应的 .c
文件的引入就在 src/config/config.c
文件中。
根据官方说明,iPXE 内置了一些调试手段,实现方法是在构建时指定一个 DEBUG 对象列表来有选择地启用这些调试支持。例如,make bin-x86_64-efi-sb/ipxe.efi DEBUG=pci:3
。其中,pci
就对应源码中的 pci.c
, :n
表示调试等级对应的 BIT 位,取值为 0 ≤ n ≤ 3 。
n 取值 | 调试等级 | 含义 |
---|---|---|
0 | 1 | 启用基本调试消息 |
1 | 2 | 启用详细调试消息(例如,每个事件一条消息) |
2 | 4 | 启用额外详细的调试消息(例如,每个字节一个消息) |
3 | 8 | 启用 I/O 跟踪(例如,每个 readl() / writel() 调用一个消息) |
如果需要在多个文件中启用调试,直接在 DEBUG=
中将多个文件以 ,
分割罗列出来即可,例如 DEBUG=pci,iscsi
。每个对象都对应一个源码中的 .c
文件(例如,DEBUG=pci,iscsi
就对应 pci.c
和 iscsi.c
)。
指定了 DEBUG=xxx
参数,实际是告诉 Makefile ,在处理对应的 xxx.c
文件时启用其中的 DBG()
宏。此外,我们指定了 DEBUG=xxx
参数后,在编译对应的文件时,就会生成 xxx.dbgn.o
(其中的 n
是上面说的调试等级对应的 BIT 位),而不指定时,生成的是 xxx.o
。在构建命令后添加 .info
可以查看哪些启用了调试。
DBG()
宏定义于 src/include/compiler.h
文件中,有多个变种,我们根据需要添加到自己的源码中,用于打印各种调试信息。在 iPXE 的源代码中,随处可以见的 DBG()
宏(及变体,例如 DBGC()
)就是通过这个方式启用的。
移植新驱动的重点就是在 src/drivers/net
目录下添加自己的驱动。首先,前文说过,iPXE 的构建系统是全代码编译,因此,如果是直接在 src/drivers/net
下添加源码文件,则不需要更改 Makefile,如果选择添加一个子目录,则需要更改 Makefile。
只需要以上简单的两步,我们的驱动就添加到了 iPXE 中,接下来就是实现驱动的具体内容了。目前,没有找到官方文档来介绍驱动的基本框架,因此,我这里主要是参考已有驱动来实现的。
驱动的基本框架与 Linux 及 U-Boot 基本是一样的。重点关注实现 struct net_device_operations
和 struct pci_driver
,其中前者是驱动的操作接口,后者是整个驱动的入口。