paint-brush
固件的隐藏世界:探索计算机的启动过程经过@tristejoursoir
3,062 讀數
3,062 讀數

固件的隐藏世界:探索计算机的启动过程

经过 Aleksandr Goncharov15m2023/04/21
Read on Terminal Reader

太長; 讀書

在本文中,我们将概述启动过程,包括其各个阶段、涉及的关键组件以及过程中面临的挑战。虽然我们主要关注 **x86 架构**(使用最广泛的架构),但其他架构在其引导过程中会有许多相似之处。
featured image - 固件的隐藏世界:探索计算机的启动过程
Aleksandr Goncharov HackerNoon profile picture


很多人对计算机的启动方式很感兴趣。只要设备开启,这就是魔法开始和持续的地方。在本文中,我们将概述启动过程,包括其各个阶段、涉及的关键组件以及过程中面临的挑战。


虽然我们主要关注x86 架构(使用最广泛的架构),但其他架构在其引导过程中会有许多相似之处。我希望这篇文章对于任何希望加深他们在该领域的知识的人来说都是宝贵的资源。开始了!

目录:

  • 开机ROM
    • 非易失性存储器
      • 一次性可编程
      • 现场可编程
    • 就地执行 (XIP)
      • 缓存作为 Ram (CAR)
    • 布局和内存映射
      • 非描述符模式
      • Intel 闪存描述符/描述符模式
      • 英特尔固件接口表 (FIT)
      • AMD 嵌入式固件结构
  • 硅初始化
    • 英特尔固件支持包 (FSP)
    • AMD 通用封装软件架构 (AGESA)
  • 自治子系统
    • 英特尔我
    • 超微掌机
  • 硬件电源序列

开机ROM

位于主板上并存储负责启动计算机的固件代码的集成电路(芯片)称为BOOT ROM 。这个名字是不规范的,所以其他开发者常称它为FLASH ROMBIOS FLASHBOOT FLASHSPI FLASH等(这些名字是因为技术、接口和用途名称而给它们起的)。不用担心,这些术语可以互换。 BOOT ROM中的固件代码在计算机开机时首先执行。它执行基本测试、初始化硬件,然后将操作系统加载程序从可启动设备(例如硬盘驱动器或 USB 驱动器)加载到内存中。该芯片由非易失性存储器 (NVM)制成。

非易失性存储器

非易失性存储器是一种计算机存储器,即使在电源关闭时也能保留其内容。它使这种类型的内存非常适合存储即使在计算机断电时也需要保留的重要数据。此外,讨论将仅集中在保存固件代码的存储器上。我们不会谈论硬盘驱动器 (HDD)、固态驱动器 (SSD)、软盘等存储。


基本上,我们可以将这种类型的内存分为以下几类。

一次性可编程

  • Masked ROM:内容在制造时确定,之后无法更改。
  • 可编程 ROM (PROM):与屏蔽 ROM 不同,这种类型的存储器可以在制造后进行编程。但还是只有一次。

现场可编程

  • 可擦可编程 ROM (EPROM):可以多次编程,但可以使用紫外线擦除和重新编程其内容。


  • 电可擦除可编程 (EEPROM):可以使用电信号多次重新编程。


    • NOR 闪存:在结构上按块排列,其中数据在块级别擦除,并且可以在字节级别读取或写入。 NOR 存储器可使用字节并行、I2C 或 SPI 等标准接口直接访问。


      在行业中,有一种惯例将术语EEPROM保留为字节方式可擦除存储器,而不是块方式可擦除闪存


可编程存储器有一条规则——先擦后写。在这样的内存中,写入新数据更加复杂,因为数据以电荷的形式存储在浮栅上(原因主要在于存储单元的物理特性)。栅极上的电荷量决定了单元存储“0”还是“1”。


擦除闪存芯片时,会将存储在其上的所有数据位设置为已知(默认)状态,通常为逻辑“1”。这允许您从一个干净的平台开始,可以这么说,并将新数据编程到芯片上,而不会在其中存储任何旧数据的残余。当新数据写入芯片时,各个位的状态从“1”变为“0”以表示新数据。


如果不先擦除芯片而直接向芯片写入新数据,新数据将与旧数据结合,导致不可预知的结果。例如,考虑一个具有 8 位内存存储值“0110 0010”的闪存芯片。如果您将新数据“1100 1001”写入芯片而不先将其擦除,则芯片的最终状态将是“0100 0000”,这可能不是您想要的。


主要的混淆与ROM这个词有关,它代表只读存储器。术语“只读存储器”历来用于指代永久且用户无法更改的存储器。然而,随着技术的进步,ROM 的定义也发生了变化,现在它通常被用来指代在工厂预编程且最终用户无法轻易更改的存储器。但如果用户拥有所需的技能和专用设备(例如编程器),则可以对芯片进行重新编程。尽管定义已更改,但 ROM 这个名称仍然保留,作为对存储器原始用途的历史参考。


通过应用写保护,某些类型的可重新编程 ROM 可能会暂时变为只读存储器。


这些不是所有现有类型的非易失性存储器,而是您可能偶然听说的大多数流行类型。如今,在大多数主板上,这些芯片都是使用NOR Flash 技术制成的。

就地执行 (XIP)

Execute in Place (XIP) 是一种允许处理器直接从闪存中执行代码而无需先将其复制到易失性存储器(如RAM )中的方法。这是通过将闪存映射到处理器的地址空间来实现的,因此可以直接从闪存执行代码。因此,系统能够尽快开始执行代码,而不必先等待 RAM 初始化。


等等...CPU 可以通过 SPI/Parallel/etc 协议与 BOOT ROM 通信吗?当然不是,它只是从系统内存中获取指令,对这个内存区域的请求被重定向到 Intel Direct Media Interface (DMI )或 AMD Infinity Fabric (IF) / Unified Media Interface (UMI)(前身)。它是主板上 CPU 和芯片组之间的链接。此时,通过位于芯片组中的解码器对地址进行解码,并将来自芯片的数据返回给处理器。


当芯片由支持随机访问读取但不支持随机访问写入的 NOR 闪存制成时,问题来了。只要可写内存不可用,所有计算都必须在处理器寄存器内执行。此时,代码只能用汇编语言编写,并倾向于为高级语言(通常为C 语言)搭建环境。原因是内存初始化变得如此复杂,纯汇编很难写。由于此类语言至少需要堆和栈,因此我们需要可写内存。一些处理器在芯片本身中嵌入了 SRAM,但更现代的方法是使用板载高速缓存作为 RAM (CAR)


缓存作为 Ram (CAR)

CPU 高速缓存是一种高速存储器,用于存储来自主存储器的常用数据和指令的副本。高速缓存位于更靠近处理器的位置,并分为多个级别(L1、L2、L3 ……),每个级别都比前一个更大、更慢。


如果数据在缓存中,CPU 可以从缓存中检索请求的数据(称为缓存命中)。当 CPU 缓存无法找到所需的数据时,就会导致缓存未命中。发生这种情况的原因可能是数据从未存储在缓存中,或者因为数据以前存储过但已从缓存中逐出。无论如何,处理器必须一路走到主存才能访问数据并将其复制到缓存中。


缓存逐出是从缓存中删除数据以为新数据释放空间的过程。数据的逐出可以由缓存系统发起(通常当缓存已满并且需要存储新数据时,或者当数据的生存时间策略已过期时)或通过显式请求发起。


但是,如果我们想将 CPU Cache 用作 RAM ,我们需要将缓存设置为在Non-Eviction Mode下运行,也称为No-Fill Mode 。此技术可防止因高速缓存未命中而逐出。相反,缓存被视为常规 SRAM,所有访问(读/写)都将访问缓存,而不会访问主内存。可以使用供应商特定的 CPU 指令激活该模式。

布局和内存映射

实际上,BOOT ROM 包含多种类型的固件。一旦一堆固件存储在 BOOT ROM 中,就需要以某种方式对其进行组织以区分它们。让我们看看它是如何完成的。

非描述符模式

最初,芯片组将整个 BOOT ROM 内容直接映射到内存(从 4GB 到 4GB - 16MB)。通常,如果 BOOT ROM 小于 16 MB,则内容会被重复映射。 CPU 和固件可以不受任何限制地读/写闪存。





新芯片组不再支持非描述符模式。

Intel 闪存描述符/描述符模式

最终,在 ICH8 中,Intel 引入了一种特殊的 BOOT ROM 布局。闪存分为以下区域:


  • 闪存描述符 (FD) - 此数据结构必须位于设备的开头,偏移量为0x10 。它由十一部分组成,如下图所示:



Descriptor MAP具有指向其他区域的指针以及每个区域的大小。


组件部分包含有关系统中闪存的信息(组件数量、每个组件的密度、无效指令等)。


Masters部分定义了区域的读/写权限。就读/写而言,权限必须设置为只读,该区域存储的信息只能在制造过程中写入。


  • BIOS - 只有这个区域被映射到内存中。





  • Intel Converged Security and Management Engine (CSME / ME) - 支持不同英特尔技术和 ME 的固件。
  • 千兆以太网 (GbE) - 只能由千兆以太网控制器直接访问。
  • 平台数据
  • 嵌入式控制器 (EC)


闪存描述符Intel ME是唯一需要的区域。

英特尔固件接口表 (FIT)

FIT 是BIOS 区域内的数据结构,包含描述平台配置的各种条目。表中的每个条目大小为 16 个字节。第一个称为FIT 标头,另一个称为FIT 条目。它位于物理地址0xFFFFFFC0 (4GB - 0x40) 的FIT 指针处。


在执行来自复位向量的第一条 CPU 指令之前,必须处理这些组件。条目包括 CPU 微代码更新、启动 ACM、平台引导/TPM/BIOS/TXT 策略和其他内容。但至少 FIT 应该包括FIT 标头微码更新条目。因此,FIT 的常见用法是在执行重置向量之前更新微码。


这是内存映射的样子:






AMD 嵌入式固件结构

不幸的是,信息少得多,我找不到任何泄露的 AMD 芯片组文档,其中包含有关其布局的详细信息。所以我不能告诉你比coreboot 文档更好的了。它是根据仅在 NDA 下可用的AMD文档编写的。


实际上,只要知道闪存描述符的 AMD 模拟是嵌入式固件结构就足够了,它包含指向PSP 目录表BIOS 目录表和其他固件的指针。


硅初始化

如果你想看看现代内存和 CPU 是如何初始化的,那么我不得不打扰你。英特尔和 AMD 并不急于向社区发布硅初始化代码。就此类信息不公开而言,他们提供必要的硅初始化代码的二进制分发。这被认为是固件开发人员的库,包含用于初始化内存控制器、芯片组、CPU 和系统其他不同部分的二进制代码。

英特尔固件支持包 (FSP)

该二进制文件可以分为 4 个部分:


  • FSP-T:设置可以执行 C 代码的早期执行环境(“临时 RAM”)。实际上,这个二进制文件设置了 CAR,但也做了一些早期的硬件初始化,比如设置 PCIe 内存映射配置空间。
  • FSP-M:初始化永久存储器(如DRAM)。
  • FSP-S:完成芯片初始化,包括CPU和IO控制器初始化。
  • FSP-O:提供 OEM 设备初始化的可选组件。


这是英特尔发布的英特尔 FSP 二进制文件存储库,您可以在他们的 GitHub 上找到。 FSP Specification v2.1可从 Intel 网站获取。

AMD 通用封装软件架构 (AGESA)

早于 Family 17h 的产品的 AGESA 称为v5Arch2008 。当时,AGESA 是开源的,代码在coreboot 存储库中可用(4.18 版后已弃用)。 Arch2008 的规范可以在 AMD 网站上找到。


随着Family 17h (Zen 微架构)产品的推出,AMD 并未发布 AGESA 源代码,仅发布了预构建的二进制解决方案。这样的继任者称为AGESA v9 ,支持 Family 17h 及更高版本。

开放SIL

没有可用的详细信息,只有新闻

自治子系统

现代 x86 启动过程的一个组成部分,没有它 x86 内核将永远不会被激活。因此不可能完全禁用它们。这些技术负责硬件初始化、验证系统完整性、电源管理和 CPU 启动。这些子系统的固件在主处理器开始执行它自己的固件之前被加载和执行。此类系统上的代码独立于平台的 CPU 内核运行。


只要许多硬件公司通过隐匿性来纳入安全原则,这些子系统的源代码和文档就都无法获得。幸运的是,我们知道它如何影响引导过程 - 请参阅硬件电源序列


我们不会详细介绍,因为互联网上已经有来自世界各地的研究人员的大量文章。但我只会给你一个简短的描述。

英特尔管理引擎 (ME)

Intel ME 是一个独立的i486/80486微处理器,自 2008 年起集成到 Intel 芯片组 (PCH) 中。它有自己的 RAM、内置 ROM、连接芯片组内所有总线的总线桥(因此,它可以访问网络甚至 CPU 上的主 RAM),等等。运行基于 MINIX 的自定义操作系统。

AMD 平台安全处理器 (PSP)

AMD PSP 是一个依赖于 Trustzone 扩展的ARM内核,作为协处理器被插入到 CPU die 中。自 2013 年以来,该芯片已集成到大多数 AMD 平台中。运行未记录的专有操作系统。

硬件电源序列

此过程也称为上电顺序电源排序,以平台所需的特定顺序提供多个派生电压电平和/或电源轨。更简单地说,它按特定顺序为多个平台组件供电。该过程因系统或平台设计而异,但通常标准 PC 包括以下步骤:


  • 您按下电源按钮。但是等等……这个按钮位于计算机机箱上,它不是计算机的必要部件。通常,电源按钮是一根电缆。我们在一侧有一个按钮,在另一侧的主板上的两个金属插脚上有一个开关。当我们按下按钮时,这些插脚连接在一起,因此电流可以通过它们。如果您感兴趣,请观看下面的视频,了解如何在没有电源按钮的情况下打开计算机。


  • 主板向电源单元 (PSU) 发送信号。


  • 电源接收信号,提供适量的电量,并将信号传回主板。
  • 一旦主板收到电源良好信号,它就会为核心、时钟、芯片组、内存、不同控制器等平台组件加电。
  • 各种子系统,包括自主子系统(上面讨论过),可以在主处理器之前启动。


  • 基于 AMD 的系统(适用于 Family 17h 及更高版本)


    • PSP 执行片BOOT ROM。

    • PSP 在片外BOOT ROM 中找到嵌入式固件表并执行 PSP 固件。

    • PSP 解析PSP 目录表以查找 ABL 阶段并执行它们。

    • ABL 阶段初始化主内存,在 BOOT ROM 中找到 BIOS 映像,并将其加载到 DRAM 中(如果映像被压缩,则解压缩)。


      该平台没有理由使用 CAR,因为 DRAM 已经可用并且 PSP 将固件映像加载到其中。


  • 基于英特尔的系统
    • 芯片组 (ICH/PCH) 在 BOOT ROM 中找到Intel 闪存描述符
    • 芯片组将 CSME 固件复制到内部存储器中,Intel ME 可以访问它,最后一个开始执行它。
    • 芯片组将BIOS 区域映射到内存。
    • 位于固件接口表中的微代码更新被加载到 CPU 中。它们必须在每次系统启动时应用
    • (可选)如果找到经过身份验证的代码模块 (ACM),则执行该条目。


  • 一直以来, CPU 复位信号都有效,以防止 CPU 在系统其他部分准备就绪之前启动。当平台准备就绪时, CPU 复位线被取消断言。在多处理器或多核系统中,一个 CPU 被动态选择为运行所有固件初始化代码的引导处理器 (BSP) 。其余的处理器,此时称为应用程序处理器 (AP) ,将保持暂停状态,直到稍后它们被固件/内核明确激活。


  • CPU首次上电后,以实模式运行。大多数寄存器都有明确定义的值,包括指令指针 (IP)、代码段 (CS) 和描述符缓存,它是处理器中每个段描述符副本,以允许快速访问段内存。


    段描述符全局描述符表(GDT)中的一个条目,包含基地址、段限制和访问信息(这部分被忽略,因为实模式没有像保护模式那样的访问控制)。不是为每次内存访问访问 GDT(位于内存中),而是将信息存储在描述符缓存中。


    但是,GDT 不涉及实模式,因此处理器在内部生成条目。用于访问段描述符的 CS 选择器寄存器加载了0xF000 。 CS 基地址初始化为0xFFFF_0000 。 IP 初始化为0xFFF0


    因此,处理器开始从位于物理地址0xFFFF_FFF0 ( 0xFFFF_0000 + 0x0000_FFF0 ) 的内存中获取指令。在该地址执行的第一条指令称为复位向量


    注意:此技巧可让您访问高地址空间,但是,您无法访问0xFFFF_0000地址以下的代码。 CS 基地址保持此初始值,直到 CS 选择器寄存器被固件加载。可以通过执行远跳来完成。


    此时,最好的决定是切换到具有 4 GB 寻址能力的保护模式。如果固件不这样做,那么为了使实模式工作,芯片组必须能够将低于 1 MB 的内存范围别名化为略低于 4 GB 的等效范围。某些芯片组没有这种别名,可能需要在执行第一次跳远之前切换到另一种操作模式。


  • 该地址位于一段非易失性存储器中,因此 CPU 使用就地执行 (XIP) 方法。尽管如果它是基于 AMD 的系统,您可能正在从主内存中读取。


  • CPU 执行固件代码。


我建议您观看下面关于开机顺序的视频,该视频以华硕 P9X79 主板为例说明了该过程。尽管它是俄语,但如果您打开自动生成的英文字幕,您将能够理解所有内容。




本文提供了很多与引导如何工作相关的理论信息。然而,要真正理解这个过程,我们需要仔细研究现有固件的源代码和架构。


在下一篇文章中,我们将深入研究BIOSUEFIcoreboot以详细检查它们。

资源