欢迎光临散文网 会员登陆 & 注册

EFISTUB

2023-06-14 08:18 作者:附魔酱  | 我要投稿

linux内核的启动方式有非常多种,大方向来看分为bios和uefi,在此基础上又有各种各样的boot loader,比如我们常见的grub,它们会根据配置信息,加载linux内核到内存,并通过一定的协议来启动linux内核。 今天要讲的是efi stub的方式,你可以把它理解成另一种boot loader,只是它是内置在linux内核里的。 通过efi stub,linux内核可以在不使用grub等传统boot loader的情况下,直接在uefi硬件上,以uefi application的方式启动,可以说是非常简单。 该方式为我们研究内核启动降低了不少难度,我们不用再去看类似于grub等boot loader的代码了,从开机到启动完毕的所有流程代码,在内核里都可以找到,完美。 不过这也需要我们阅读大量的相关材料,比如 uefi 的各种specification(https://uefi.org/specifications),以及 uefi application 的具体文件格式(linux内核是以 uefi application 方式启动的,uefi 指定了 application 的格式为pecoff,该格式文档可参考其官方说明 https://docs.microsoft.com/en-us/windows/win32/debug/pe-format)。 efi stub是linux的一个feature,它可以通过配置 CONFIG_EFI_STUB 来开启和关闭。 它的实现原理是,按照 uefi 指定的 pecoff 格式,将内核伪装成一个 uefi application,这样在支持 uefi 的各种硬件上,就可以按照 uefi 协议,直接启动linux内核了。 所以下面的代码介绍和 pecoff 格式定义息息相关,建议先通读上面链接中 pecoff 格式文档,这样理解下面的内容就非常容易了。 linux内核efi stub有关pecoff 格式定义的部分都在 arch/x86/boot/header.S 这个文件里,具体如下:

其中 AddressOfEntryPoint字段填充的就是 efi stub 的入口函数地址,或者说是 uefi application 的入口函数地址,这个可以从 pecoff 文档

以及uefi specification中得到确认。

好,既然这个就是我们要找的 efi stub 的入口函数,那我们来看下它具体的值是什么。 由上面可见,它的初始值是0,然后注释中说它真正的值会在build.c中设置。 build.c其实是内核的一个小工具,在构建linux内核时,make最终会调用该工具把内核编译后的各个部分,组装成最终的bzImage。

bzImage的文件结构大致为: setup部分 - 对应到 arch/x86/boot/ 中的代码 compressed部分 - 对应到 arch/x86/boot/compressed/ 中的代码 这两个部分都可以认为是内核启动流程的部分,并不是真正的内核逻辑,真正内核逻辑被压缩到了compressed部分里的piggy.S文件里。 有关编译后的bzImage的layout情况,我们会在另一篇文章中详细讲,这里只需要知道,build.c这个工具把这些部分按照一定的顺序结合在一起,生成了最终的bzImage。 我们再来回头看下build.c中是如何设置efi stub的AddressOfEntryPoint的:

上面选中行就是设置AddressOfEntryPoint的部分,其中text_start你可以认为是compressed部分的起始地址,而efi_pe_entry就是我们最终要找的 efi stub 入口函数。 选择行中将 text_start + efi_pe_entry (efi_pe_entry运行时的地址) 的结果,赋值到 pe_header + 0x28 指向的内存里,结合 pecoff 文档以及上面的header.S文件内容,我们可知这个地址就是 header.S 文件中的 AddressOfEntryPoint 变量的地址。 这也说明了该选中行确实是在设置 AddressOfEntryPoint。 如果看过build.c中的代码,你会发现 efi_pe_entry 也是一个变量,那该变量具体指向的是哪个函数呢?

还是在build.c里,由上可见,efi_pe_entry是从zoffset.h中解析出来的,而zoffset.h是在make的过程中生成的:

zoffset.h的生成方式是用nm命令查询compressed中vmlinux的各种指定symbol的地址,zoffset最终文件内容如下:

也就是说,build.c中解析的 efi_pe_entry 其实指向的就是 compressed 部分中的某个函数,我们搜索后会发现这个:

这个就是我们最终要找的函数了。 这时,有些同学可能会有疑问,不是说是compressed部分里的代码吗,这怎么是driver里的代码了? 看compressed部分的makefile:

看上面选中行,compressed部分在编译时,也把libstub目录中的代码包含进来了。 现在,我们就找到了efi stub的入口函数。 这样,当linux内核以 uefi application 的形式,被 uefi 直接启动时,被执行的第一行代码就是这个方法。 或者说,在 uefi 平台上,以 efi stub形式启动内核时,开机后内核执行的第一个方法就是该方法。

EFISTUB的评论 (共 条)

分享到微博请遵守国家法律