很多人对计算机的启动方式感兴趣。这是魔术开始的地方,只要设备打开,魔术就会继续。在本文中,我们将概述启动过程,包括其各个阶段、涉及的关键组件以及在此过程中面临的挑战。虽然我们的主要关注点是 x86 架构(使用最广泛的架构),但其他架构在启动过程中会有许多相似之处。我希望这篇文章对于任何希望加深该领域知识的人来说都是一个宝贵的资源。来吧!
启动 ROM
位于主板上并存储负责启动计算机的固件代码的集成电路(芯片)称为 BOOT ROM。这个名称不是标准化的,所以其他开发人员经常称它为FLASH ROM,BIOS FLASH,BOOT FLASH,SPI FLASH等(这些名称是因为技术,接口和目的名称而赋予他们的)。 这意味着同样的事情,这些术语是可以互换的。BOOT ROM 中的固件代码在计算机通电时首先执行,它执行基本测试,初始化硬件,然后将操作系统加载程序从可启动设备(如硬盘驱动器或 USB 驱动器)加载到内存中。该芯片由非易失性存储器 (NVM) 制成。
非易失性存储器
非易失性存储器是一种计算机存储器,即使在电源关闭时也能保留其内容。它使这种类型的内存非常适合存储即使在计算机关机时也需要保留的重要数据。此外,讨论将仅集中在保存固件代码的内存上。我们不会谈论硬盘驱动器 (HDD)、固态驱动器 (SSD)、软盘等存储。
基本上,我们可以将这种类型的内存分为以下几组。
一次性编程
- 屏蔽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的名称仍然存在,作为对内存原始用途的历史参考。
通过应用写保护,某些类型的可重新编程 ROM 可能会暂时成为只读存储器。
这些并不是所有现有的非易失性存储器类型,但大多数流行的存储器可能只是偶然听说的。如今,在大多数主板上,这些芯片都是使用 NOR Flash 技术制造的。
eXecute 就地 (XIP)
就地执行 (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 缓存用作 RAM,我们需要将缓存设置为在非逐出模式(也称为无填充模式)下运行。此技术可防止由于缓存未命中而逐出。相反,缓存被视为常规SRAM,所有访问(读/写)都将命中缓存,而不会命中主存储器。可以使用特定于供应商的 CPU 指令激活该模式。
布局和内存映射
实际上,BOOT ROM包含多种类型的固件。一旦一堆固件存储在BOOT ROM中,就需要以某种方式对其进行组织以区分它们。让我们来看看它是如何完成的。
非描述符模式
最初,芯片组将整个 BOOT ROM 内容直接映射到内存(从 4GB 到 4GB — 16MB)。通常,如果 BOOT ROM 小于 16 MB,则会重复映射内容。CPU 和固件可以不受任何限制地读/写到闪存。
新芯片组不再支持非描述符模式。
英特尔闪存描述符/描述符模式
最终,在 ICH8 中,英特尔为 BOOT ROM 引入了一种特殊的布局。闪光灯分为以下区域:
- 闪存描述符(FD) — 此数据结构必须位于设备的开头,偏移量为“0x10”。它由11个部分组成,如下图所示:
描述符 MAP 具有指向其他区域的指针以及每个区域的大小。
组件部分包含有关系统中闪存的信息(组件数量、每个组件的密度、无效指令等)。
“Masters”部分定义区域的读/写权限。只要读/写权限必须设置为只读,则存储在此区域中的信息只能在制造过程中写入。
为了更好地理解细节,我建议您阅读 Open Security Training 的演示文稿。
- BIOS — 只有此区域映射到内存中。
- 英特尔融合安全与管理引擎 (CSME / ME) — 支持不同英特尔技术和 ME 的固件。
- 千兆以太网(GbE) — 只能由千兆以太网控制器直接访问。
- 平台数据
- 嵌入式控制器 (EC)
闪存描述符和英特尔 ME 是唯一需要的区域。
英特尔固件接口表 (FIT)
FIT 是 BIOS 区域内的数据结构,包含描述平台配置的各种条目。表中的每个条目大小为 16 个字节。第一个称为 FIT 标头,另一个称为 FIT 条目。它位于物理地址“0xFFFFFFC0”(4GB — 0x40)处的 FIT 指针。
在从复位向量执行第一个 CPU 指令之前,必须处理这些组件。条目包括 CPU 微码更新、启动 ACM、平台启动/TPM/BIOS/TXT 策略和其他内容。但至少 FIT 应包括 FIT 标头和微码更新条目。因此,FIT的常见用法是在执行复位向量之前更新微码。内存映射如下所示:
与往常一样,有关更多详细信息,请使用 FIT 规范 1.4。
AMD 嵌入式固件结构
不幸的是,信息要少得多,我找不到任何泄露的 AMD 芯片组文档以及有关其布局的详细信息。所以我不能告诉你比 coreboot 文档说的更好。它是基于 AMD 文档编写的,该文档仅在 NDA 下可用。
实际上,只要知道 Flash Descriptor 的 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 规范 v2.1 可从英特尔网站获取。
AMD 通用封装软件架构 (AGESA)
早于 Family 17h 的产品的 AGESA 称为 v5 或 Arch2008。当时,AGESA 是开源的,代码在 coreboot 存储库中可用(在 4.18 版本后被弃用)。Arch2008 的规格可以在 AMD 网站上找到。
随着Family 17h(Zen微架构)产品的推出,AMD尚未发布AGESA源码,仅预构建二进制解决方案。这样的继任者称为 AGESA v9,支持 Family 17h 及更高版本。
openSIL的
没有详细信息,只有新闻。
自治子系统
现代 x86 启动过程的组成部分,没有它,x86 内核将永远不会被激活。因此,不可能完全禁用它们。这些技术负责初始化硬件、验证系统完整性、电源管理和 CPU 启动。这些子系统的固件在主处理器开始执行自己的固件之前加载并执行。此类系统上的代码独立于平台的 CPU 内核运行。
只要许多硬件公司通过隐蔽性纳入了安全原则,这些子系统的源代码和文档就不可用。幸运的是,我们知道它如何影响启动过程 — 请参阅硬件电源序列。
我们不会详细介绍,因为互联网上已经有来自世界各地的研究人员的大量文章。但我只想给你一个简短的描述。
英特尔管理引擎 (ME)
英特尔ME是一个独立的i486 / 80486微处理器,自2008年以来集成到英特尔芯片组(PCH)中。它有自己的 RAM、内置 ROM、到芯片组内所有总线的总线桥接(因此,它可以访问网络,甚至可以访问 CPU 上的主 RAM)等等。运行基于 MINIX 的自定义操作系统。
AMD 平台安全处理器 (PSP)
AMD PSP 是一个依赖于 Trustzone 扩展的 ARM 内核,它作为协处理器插入到 CPU 芯片中。自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 中找到英特尔闪存描述符。
- 芯片组将 CSME 固件复制到内部存储器中,英特尔 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 主板为例进行了解释。尽管它是俄语的,但如果你打开自动生成的英文字幕,你将能够理解所有内容。
本文提供了大量与引导工作原理相关的理论信息。但是,要真正理解这个过程,我们需要仔细研究现有固件的源代码和架构。在下一篇文章中,我们将深入研究 BIOS、UEFI 和 coreboot,以详细研究它们。
资源
- 启动英特尔架构系统,第一部分:早期初始化,Web 存档
- 计算机如何启动
- ROM、EPROM 和 EEPROM 技术
- EEPROM和NOR闪存的区别
- X86 上的缓存即 RAM 的温和介绍
- 有关非描述符和描述符模式的图片源自 Xeno Kovah 的“架构 4001:x86–64 英特尔固件攻击和防御”类,该类源自 John Butterworth 和 Xeno Kovah 的“高级英特尔 x86:BIOS 和 SMM”类。
原文始发于微信公众号(安全狗的自我修养):探究固件的秘密:探索基础
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论