专栏首页Linux内核及编程语言底层相关技术研究linux内核启动流程分析 - efi_pe_entry

linux内核启动流程分析 - efi_pe_entry

接上一篇文章 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函数的实现。

本文分享自微信公众号 - Linux内核及JVM底层相关技术研究(ytcode),作者:wangyuntao

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-06-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • linux内核启动流程分析 - efistub的入口函数

    网上类似标题的文章很多,但大都是从start_kernel讲起,我觉得这是远远不够的。

    KINGYT
  • linux内核启动流程分析 - efi_stub_entry

    接上一篇文章 linux内核启动流程分析 - efi_pe_entry,我们继续看efi_stub_entry函数。

    KINGYT
  • linux内核启动流程分析 - efi_main

    上一篇文章 linux内核启动流程分析 - efi_stub_entry 中,为了叙述方便,我们只是粗略的讲了下efi_main函数,这里我们再具体看下。

    KINGYT
  • linux内核启动流程分析 - startup_64

    在前面的第三篇文章中我们说道,efi_stub_entry最终会调用startup_64,那这篇文章我们就来看下startup_64的具体逻辑。

    KINGYT
  • 06.内核启动流程分析之内核启动

    嵌入式与Linux那些事
  • 有了这张图,看谁还能阻拦我研究linux内核源码!

    最近在写一个 linux内核启动流程分析 的系列文章,主要是想从源码角度,非常细致的给大家讲下linux内核是如何启动的。

    KINGYT
  • linux内核启动过程分析

    start_kernel是内核启动阶段的入口,通过单步调试,可以发现它是linux内核执行的第一个init,我们单步进入看看它做了哪些操作:

    De4dCr0w
  • 05.内核启动流程分析之makefile

      分析makefile从顶层开始,顺藤摸瓜的分析下去,会涉及到所有的makefile文件。各级子目下的makefile完成的动作obj -y += obj -...

    嵌入式与Linux那些事
  • Linux 启动流程分析

    这个部分比较有意思。因为在BIOS阶段,计算机的行为基本上被写死了,程序员可以做的事情并不多;但是,一旦进入操作系统,程序员几乎可以定制所有方面。所以,这个部分...

    用户5807183
  • Linux 启动流程分析

    这个部分比较有意思。因为在BIOS阶段,计算机的行为基本上被写死了,程序员可以做的事情并不多;但是,一旦进入操作系统,程序员几乎可以定制所有方面。所以,这个部分...

    良月柒
  • Linux 启动流程分析

    这个部分比较有意思。因为在BIOS阶段,计算机的行为基本上被写死了,程序员可以做的事情并不多;但是,一旦进入操作系统,程序员几乎可以定制所有方面。所以,这个部分...

    用户6543014
  • 第3阶段——内核启动分析之创建si工程和分析stext启动内核函数(4)

    目标: (1)创建Source Insight 工程,方便后面分析如何启动内核的 (2)分析uboot传递参数,链接脚本如何进入stext的  (3) 分析st...

    张诺谦
  • 第3阶段——内核启动分析之创建si工程和分析stext启动内核函数(4)

    目标: (1)创建Source Insight 工程,方便后面分析如何启动内核的 (2)分析uboot传递参数,链接脚本如何进入stext的  (3) 分析st...

    张诺谦
  • Spark内核详解 (3) | Spark集群启动流程的简单分析

    本片博文主要分析的是Standalone 模式下 Spark 集群(Master, work)启动流程

    不温卜火
  • Influxdb启动流程分析

    我们启动的话通常是 influxd run -config [config file path]

    扫帚的影子
  • kubelet 启动流程分析

    本来这篇文章会继续讲述 kubelet 中的主要模块,但由于网友反馈能不能先从 kubelet 的启动流程开始,kubelet 的启动流程在很久之前基于 v1....

    田飞雨
  • kubelet 启动流程分析

    上篇文章(kubelet 架构浅析 )已经介绍过 kubelet 在整个集群架构中的功能以及自身各模块的用途,本篇文章主要介绍 kubelet 的启动流程。

    田飞雨
  • kubelet 启动流程分析

    上篇文章(kubelet 架构浅析 )已经介绍过 kubelet 在整个集群架构中的功能以及自身各模块的用途,本篇文章主要介绍 kubelet 的启动流程。

    田飞雨
  • 04.uboot分析之uboot启动内核

    首先要明确:uboot目标是从flash读出内核(nand read.jffs2 0x30007FC0 kernel;),启动它(bootm 0x30007FC...

    嵌入式与Linux那些事

扫码关注云+社区

领取腾讯云代金券