前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >linux内核启动流程分析 - efi_pe_entry

linux内核启动流程分析 - efi_pe_entry

作者头像
KINGYT
发布2020-06-29 16:07:26
2.6K0
发布2020-06-29 16:07:26
举报

接上一篇文章 linux内核启动流程分析 - efistub的入口函数,我们继续看efi_pe_entry这个函数。

该函数有两个参数,根据uefi specification中有关entry point的定义可知:

handle指向的是运行时的kernel,sys_table_arg指向的是uefi的system table(有了system table,就可以使用uefi的各种服务,比如输入输出、boot service、runtime service等)。

接下来该函数验证了system table中的signature是否等于uefi specification中定义的signature,以此来判断该次启动是否用的是uefi方式。

然后通过efi_bs_call宏,调用system table中的boot service的handle_protocol方法,该方法指定的protocol为LOADED_IMAGE_PROTOCOL_GUID,即查询handle指向的image的相关信息,并把查询结果返回在image这个参数里。

我们来具体看下uefi specification中定义的LOADED_IMAGE_PROTOCOL返回的结果是什么。

由上可见,该protocol返回结果的数据结构是EFI_LOADED_IMAGE_PROTOCOL,从该数据结构中,我们可以获取很多有关image的信息,比如ImageBase、ImageSize等。

继续看efi_pe_entry函数。

在调用完handle_protocol获取了image信息后,该函数紧接着使用了efi_table_attr宏,从image中获取image_base的值,即运行时的kernel在内存中的起始地址。

接着根据startup_32函数地址和image_base的值,算出image_offset,该offset指的是bzImage中的compressed部分在整个bzImage中的偏移量(startup_32是compressed部分的第一个方法)。

接下来调用efi_allocate_pages函数,创建了一个boot_params实例,并将各字段初始化为0。

boot_params又被称为zeropage,该结构体用来存放各种启动参数,供后续启动kernel使用,其具体结构如下:

接下来pe_efi_entry又调用memcpy,将加载到内存的bzImage的第二个sector的内容,拷贝到boot_params里的setup_header里,拷贝的起始位置为setup_header里的jump字段。

那bzImage的第二个sector是在哪里,且又是什么内容呢?

其实就是在上一篇文章中讲到的 arch/x86/boot/header.S 文件里。

header.S里不仅有pecoff header,还有kernel的setup header,其主要用途是使kernel和bootloader之间可以进行数据交换。

有关setup header更详细的介绍,请看下面的链接:

https://www.kernel.org/doc/html/latest/x86/boot.html

header.S里的第512字节正是jump指令,且该指令之后的各种setup header字段和boot_params中setup header字段是一一对应的。

也就是说,该拷贝操作是把bzImage中的setup_header里的内容拷贝到boot_params里的setup_header里。

继续efi_pe_entry函数。

在拷贝完setup header之后,该函数又设置了boot_params里的setup header的root_flags、boot_flag、cmdline等字段。

在boot_params里的setup_header都初始化完毕之后,该函数最终调用了efi_stub_entry函数,并将参数image handle,system table,和boot params传递给了它。

至此,efi_pe_entry函数就结束了。

如果熟读过uefi specification,该函数的大部分逻辑理解起来都非常简单,所以很多细节我就不再赘述了。

这里我们只再重点说下image_offset的计算,这个当时花了我不少时间才理解清楚。

由上一篇文章我们知道,bzImage是由build.c这个工具,将setup部分和compressed部分顺序拼接在一起的,并没有做什么特殊处理。

startup_32作为compressed部分中的一个函数,我们可以通过下面的方法获取其编译后的地址:

由上可见,startup_32函数的地址是0。

既然build.c只是将setup和compressed部分顺序拼接,并没有做地址的转换处理,那理应efi_pe_entry函数里使用的startup_32函数的地址就是0。

如果此想法成立,那在image_base大于0的情况下,image_offset岂不是负数了?

这种推断肯定是有问题的,那问题出在哪呢?

后来经过一番苦苦查找,终于通过反汇编找到了答案,我们来看下在反汇编情况下,image_offset具体是怎么计算的。

由上可见,startup_32的地址,是根据rip的当前值减去0x895b8c得来的,而0x895b8c这个值正好是startup_32函数地址到上图选中指令的下一条指令的偏移量。

又由于rip存放的是下一条指令的地址,所以上面rip减去0x895b8c正好就是startup_32运行时的函数地址。

所以最开始我认为efi_pe_entry中使用的是startup_32的绝对地址,即上面输出的0,这种想法是错误的,其实它是根据当前rip中的地址,以及startup_32到下一条指令的偏移量,计算出真正的运行时中的startup_32的地址,这样不管compressed部分被加载到了内存的什么位置,startup_32的地址都是正确的,即位置无关。

这个结论我们从compressed/Makefile中加了-pie参数,可以进一步得到验证:

好,今天就讲这么多,下篇文章我们接着看最后的efi_stub_entry函数的实现。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-06-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Linux内核及JVM底层相关技术研究 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档