大多数人第一次认真关注 Windows 的启动过程,往往是被一块蓝屏逼的。屏幕上写着 0xc000000f、0xc0000225,或是那句经典的"你的电脑需要修复",附带的"按 Enter 重试、按 F8 进入启动设置、按 ESC 进入 UEFI 固件设置"三个选项全都不管用。此时你会发现一个尴尬的事实:我们对这台每天开机使用的机器,了解得几乎为零。
本文要做的事,就是把下面这条被无数技术文章反复引用、却极少被真正讲透的链路,拆成可以逐环节审视的工程细节:
- 控制权交接了三次,而不是一次。 固件 → 引导管理器、引导管理器 → 系统加载器、系统加载器 → 内核,每一次交接都伴随着执行环境(分页是否开启、物理地址还是虚拟地址、谁拥有内存、谁拥有中断)的根本性变化。
- 中间夹着两个配置数据库。 一个是固件侧的 NVRAM 变量(BootOrder / Boot####),一个是微软侧的 BCD 注册表 hive。二者都叫"启动配置",格式、位置、读写者完全不同,但它们必须保持一致,任何一边漂移都会导致启动失败。
- 有一条与安全启动并行的"测量链"。 Secure Boot 决定"能不能跑",Measured Boot 记录"跑了什么",BitLocker 则基于后者决定"要不要解开磁盘密钥"。三者共用同一套 PCR 与事件日志。
- 用户给出的链路里有一个历史遗留的表述需要修正。 在 Windows 10 2004 及以后,hal.dll 已经不再是独立于内核的硬件抽象层实现,它被静态链接进了 ntoskrnl.exe。这不是咬文嚼字,而是理解现代 Windows 内核布局的关键。
- 每一次"下一步"都有失败分支。 启动链路真正的复杂度不在正常路径,而在它为每个环节准备的诊断、回滚与自动修复机制:Boot Status Policy、Last Known Good、WinRE、自动修复、启动计数。
本文按这条链路的自然顺序展开,但每一章都会同时讲三件事:它做了什么 、它依赖什么前提 、它在什么条件下失败,以及失败时你看到的是什么。写作目标是让读者读完以后,面对任何一条启动错误信息,都能立刻定位到它发生在链路的哪一段、该查什么证据、用什么工具修。
阅读约定:
- 文中出现的十六进制地址、结构体字段名、函数名,凡属未公开接口(如
OslFwpKernelSetupPhase1、OslArchTransferToKernel、ImgArchStartBootApplication)均来自公开的逆向工程与安全研究文献,版本号不同会存在差异,阅读时请以"机制描述"为主,不要把它当成稳定 ABI。 - 涉及注册表键、bcdedit 命令、错误码的部分属于稳定事实,可直接用于生产环境排障。
- 文中所有"实践建议"均标注了风险等级,涉及分区与 NVRAM 的操作请务必先备份。
第一章 固件初始化:从复位向量到 ExitBootServices
1.1 一次加电,CPU 做的第一件事
按下电源键后,电源管理电路把 PWR_OK 信号拉高,主板解除对 CPU 的复位。x86-64 处理器在复位后的状态是一套刻意保留的、近乎"原始"的状态:寄存器被清零或置为固定值,分页未开启,大部分特性未启用,CPU 处于实地址模式的长模式前状态。唯一重要的是指令指针------它固定指向线性地址 0xFFFFFFF0,这个地址被称为复位向量(Reset Vector)。
这个地址位于 4GB 物理地址空间尽头往下 16 字节处,硬件(芯片组)保证这段地址被映射到主板 SPI Flash 上的固件映像末尾。那里通常只有一条远跳转指令,把执行流转向固件真正的入口。
从这一刻起,运行的代码来自主板上的那颗闪存芯片,而不是硬盘。这一点是理解整个安全启动模型的前提:操作系统还没参与任何事,第一道信任判定已经在固件里完成了。
1.2 PI 规范的七个阶段
现代 UEFI 固件不是一块单体的 16 位 ROM,而是一套模块化、分阶段执行的"微型操作系统"。UEFI 平台初始化(PI)规范把它划分为七段:

各阶段的要点如下。
SEC 阶段。 这是信任根所在。它处理的输入是"任何形式的处理器重启",输出是"一个可用于执行 C 语言代码的临时栈"。技术上最关键的手段是 Cache-as-RAM(CAR):在没有 DRAM 可用的情况下,把 CPU 的 L1/L2 缓存配置为不回写的临时 SRAM 使用,从而让后续的 C 代码能正常跑起来。SEC 还负责加载微码补丁,并把控制权交给 PEI 核心。
在开启了 Intel Boot Guard(或 AMD 平台的对等技术)的机器上,SEC 阶段之前还有一层更底层的硬件信任根:CPU 内部的微码会验证固件中的认证代码模块(ACM),ACM 再测量并验证固件的初始引导块(IBB)。IBB 验证失败,机器根本不会走到你能看到屏幕的地方------这是一个"固件级根套件防护",同时也是为什么某些刷过第三方 BIOS 或绕过 Boot Guard 的机器会直接变砖。
PEI 阶段。 它完成整个启动过程中最耗时、也最容易出硬件故障的一步:内存训练(Memory Training)。DDR 内存的初始化需要对信号时序做大量校准,这段时间内屏幕通常是黑的或卡在厂商 LOGO 上,耗时从几百毫秒到数秒不等,取决于内存容量、插槽数量与固件策略。这也是"开机黑屏十几秒才亮"最常见的非故障解释。
PEI 的输出是一组 HOB(Hand-Off Block) 列表,用一种自描述的数据结构把内存布局、固件卷位置、平台信息传给 DXE。这是固件内部的"交接文档"。
DXE 阶段。 固件的主体。DXE 派发器会遍历固件卷(FV)中的 FFS 文件,按依赖表达式(DEPEX)顺序执行 DXE 驱动。在这里,UEFI 的"服务导向"架构才真正成型:
- 句柄数据库(Handle Database) 与 Protocol 机制:设备被抽象为句柄,能力被抽象为 Protocol(如
EFI_BLOCK_IO_PROTOCOL、EFI_SIMPLE_FILE_SYSTEM_PROTOCOL、EFI_GRAPHICS_OUTPUT_PROTOCOL)。 - Boot Services :内存分配、事件/定时器、映像加载(
LoadImage/StartImage)、协议安装等。这些服务在ExitBootServices()之后全部失效。 - Runtime Services :变量读写(
GetVariable/SetVariable)、实时时钟、系统复位、以及后文会重点讲的SetVirtualAddressMap。这些服务在操作系统运行期间依然可用。
这一阶段会初始化 USB、存储控制器、PCIe 总线、网卡(PXE)、显卡(GOP 驱动)。显卡 GOP(Graphics Output Protocol)在这里被建立起来,它是后续 Windows 引导管理器能显示图形化启动菜单、能显示那个转圈小圆点的根本原因------在纯 Legacy BIOS 时代,引导阶段只有 80x25 的文本模式和 VGA 调色板。
BDS 阶段。 固件开始执行启动策略。它的行为围绕 NVRAM 中的一组变量展开:
BootOrder:一个 16 位整数数组,定义启动项尝试顺序。Boot####:####是十六进制编号,每个变量是一条"启动选项",其载荷是EFI_LOAD_OPTION结构,包含描述文字、设备路径(Device Path)、可选的参数数据。BootNext:一次性的"下一次启动用这一项",优先级高于BootOrder的第一项。Windows 的"高级启动 → 立即重启"、"从设备启动"就是通过写入BootNext实现的。BootCurrent:当前正在使用的启动项编号,只读。Timeout:固件启动菜单等待时间。
BDS 按 BootOrder 依次尝试:解析 Boot#### 中的设备路径,找到对应分区上的 EFI 应用文件,调用 LoadImage 加载、StartImage 执行。若某一条失败(文件不存在、签名不通过),则尝试下一条;若全部失败,则进入"可移动介质回退路径"------查找默认的 \\EFI\\BOOT\\BOOTX64.EFI(x64 平台的默认回退文件名)。这个回退机制正是 Windows 安装 U 盘、以及绝大多数修复工具盘能"插上就能启动"的原因:固件不需要任何 NVRAM 条目,只要在某个 FAT 分区上放一个正确命名的文件即可。
关于设备路径。 启动项里最关键的数据结构是 EFI_DEVICE_PATH_PROTOCOL,它是一条自描述的、二进制的"寻址链"。一条典型的 Windows 启动项设备路径在概念上是:
PciRoot(0x0)/Pci(0x1,0x2)/Pci(0x0,0x0)/NVMe(0x1,00-25-38-B9-31-A4-FE-FD)
/HD(1,GPT,d022bdc9-8147-4835-812a-8f0fa6365b22,0x800,0x32000)
/\\EFI\\Microsoft\\Boot\\bootmgfw.efi
即:从 PCI 根桥出发,走两段 PCI 桥,定位到 NVMe 控制器与命名空间,再定位到 GPT 分区表第 1 个分区(用分区 GUID 而不是盘符或序号标识),最后是分区内的文件路径。注意这里两个细节:
- 分区用 GUID 标识。这意味着你可以把磁盘接到主板的另一个接口、甚至改变分区顺序,只要分区 GUID 不变,固件依然能找到它。这是 UEFI 相对 Legacy MBR"第几块盘第几个分区"这种脆弱寻址的重大改进,也是为什么"换了个 SATA 口就起不来"在 UEFI 机器上罕见得多。
- 文件系统路径用反斜杠、大小写不敏感 。UEFI 规范定义的 FAT 文件系统驱动本身大小写不敏感,因此
\\EFI\\Microsoft\\Boot\\bootmgfw.efi与\\efi\\microsoft\\boot\\BOOTMGFW.EFI等价。
1.3 ESP:固件唯一认识的那个分区
上文的路径落在一个特殊分区上:EFI 系统分区(ESP)。

为什么必须是 FAT32? 因为 UEFI 规范只强制要求固件实现 FAT 文件系统驱动。NTFS、exFAT 的支持依赖厂商额外实现,不能假定存在。这带来一个重要的排障推论:如果你的 ESP 被格式化成 NTFS,Windows 将永远无法启动 ,而修复方法只能是重建 ESP(diskpart 重建 + bcdboot 重新写入)。这在"用第三方分区工具整理磁盘"之后是相当常见的故障。
另一个常见坑是 ESP 与"系统保留分区"的混淆:
- UEFI/GPT 机器上:ESP(FAT32,约 100MB)承担启动文件的角色;可能还有一个 MSR(Microsoft 保留分区,16MB,无文件系统,供磁盘管理使用)。
- Legacy BIOS/MBR 机器上 :承担启动文件角色的是"系统保留"(System Reserved)分区(NTFS,约 100--500MB,带活动标记),启动文件位于
\\Boot\\BCD,引导代码位于 MBR 与分区引导扇区。
这两套体系的文件名、命令参数、修复方式全都不同。在动手修复之前,第一件事永远是确认当前是 UEFI 还是 Legacy ------CMD 里执行 bcdedit /enum 看 {bootmgr} 的 path 是 \\EFI\\Microsoft\\Boot\\bootmgfw.efi(UEFI)还是 \\Windows\\system32\\winload.exe(Legacy 场景下的不同结构),或者用 msinfo32 查看"BIOS 模式"。
1.4 Secure Boot:固件侧的"能不能跑"
启用了 Secure Boot 的固件,在执行任何一个 EFI 映像之前都要做一次签名验证。其信任结构是一个四层密钥体系:

验证流程本身不复杂:固件读出映像中 PE/COFF 的安全目录(WIN_CERTIFICATE),取出 Authenticode 签名链,然后依次判断------
- 签名的哈希是否出现在 dbx 中?是 → 拒绝执行。这一条优先级最高,是微软封杀已知恶意引导器(如某些被泄露签名的 bootkit)的手段。
- 签名链能否回溯到 db 中的某个证书或被信任的哈希?能 → 允许执行。
- 否则 → 拒绝,通常在屏幕上提示 "Selected boot image did not authenticate" 或类似信息。
Windows 引导管理器 bootmgfw.efi 由 Microsoft Windows Production PCA 2011 (及其继任体系)签署,几乎所有出厂机器的 db 中都含有这张证书,所以 Windows 能顺利启动。第三方引导器(Linux 的 shim、GRUB、rEFInd)则由 Microsoft Corporation UEFI CA 2011 体系签署,走的是微软提供的第三方签名服务------这也是为什么"装 Ubuntu 双系统"在 Secure Boot 开启的机器上依然可用。
一个正在发生、且必须知道的现实问题:2011 系列证书正在到期。 微软的替换方案是 2023 系列证书:

这件事的实践含义是:db 中只有 2011 证书的老机器,将来无法信任用 2023 证书签署的新版 bootmgfw.efi 与新的 dbx 更新。 它不会立刻导致启动失败(现有已签名文件仍然有效),但会形成"安全冻结"------引导器无法再被安全更新,dbx 无法再扩展。这是当前企业环境中需要提前规划证书更新的重要议题。
1.5 Measured Boot:记录"跑了什么"
Secure Boot 是"准入控制",它只回答能或不能。另一条并行的链条是测量启动(Measured Boot):每加载一个组件,就把它的摘要"扩展(Extend)"进 TPM 的一个 PCR,并把对应事件写入事件日志。
PCR 的核心性质是只能扩展、不能直接写:
PCR[N]_new = Hash( PCR[N]_old || 待测量数据 )
这意味着 PCR 的最终值隐含了整个测量序列,任何一步被替换或插入,最终值都会变化,且无法伪造一个"看似正确"的历史。
TCG PC 客户端规范定义的 PCR 分配如下(这是排障与取证时最常查的一张表):

有几点值得单独强调:
- PCR 4 记录的是 bootmgfw.efi 本体。 从实测的事件日志中可以直接读到类似
PciRoot(0x0)/Pci(...)/NVMe(...)/HD(1,GPT,...)/\\EFI\\Microsoft\\Boot\\bootmgfw.efi的设备路径,以及该映像在内存中的加载地址、长度、链接时基址。也就是说,如果 Windows 是被另一个引导器(如 GRUB)链式加载进来的,PCR 4 会出现两次EV_EFI_BOOT_SERVICES_APPLICATION事件,且第一条的设备路径指向非微软的 EFI 文件。这是远程证明(Attestation)检测"是否经过第三方引导器"的常用判据,也是部分反作弊引擎检测虚拟机的手段。 - PCR 7 记录的是 Secure Boot 策略,而不是引导器本体。 它包含 PK、KEK、db、dbx 的内容与 Secure Boot 开关状态。这个区别后文讲 BitLocker 时至关重要。
- separator 事件。 固件在 PCR 0--6 各写入一个
EV_SEPARATOR,用于标记"测量阶段的边界",防止后续组件伪造出"我早就测量过了"的假象。 - 事件日志在哪? Windows 把合并后的日志(固件部分 + OS 加载器扩展部分)保存在
%WINDIR%\\Logs\\MeasuredBoot\\,可用tpmtool.exe或跨平台的tpm2_eventlog解析;运行期通过Tbsi_Get_TCG_Log提供给用户态。
1.6 ExitBootServices:整条链路最不可逆的一步
当引导加载器(对 Windows 而言是 winload.efi)认为自己已经准备好了,它会调用 ExitBootServices()。这一步的意义远超"卸载驱动":
- 它宣告固件引导服务终结。 从此刻起,所有 Boot Services(内存分配、协议数据库、定时器、映像加载)全部失效。
- 它把内存的所有权交给操作系统。 调用时需要传入一个由
GetMemoryMap()获得的MapKey,固件用它校验"你看到的内存映射是最新的"。如果期间发生了任何改变映射的事(比如有人分配了内存),MapKey失效,调用会返回错误,加载器必须重新取映射再试。这是恶意 UEFI 驱动常挂钩的点:通过 hookExitBootServices,可以在这一刻拿到 winload 的返回地址,进而定位LOADER_PARAMETER_BLOCK、找到 ntoskrnl 的基址------这正是若干已知 bootkit(如 BlackLotus 一类技术路线)的通用套路。 - Runtime Services 仍需保留,但地址要换。 引导期 Runtime Services 用的是物理地址,而操作系统马上要开启自己的分页与虚拟地址空间。因此固件要求:OS 在调用
SetVirtualAddressMap()时,把每一个运行时内存描述符的物理地址替换为虚拟地址。固件驱动借此"搬迁"自己的指针。此后操作系统通过gRT(Runtime Services 表)继续调用变量读写、重启、关机、实时时钟。
这就是为什么 Windows 能在一个已经跑起来的系统里继续读写 UEFI 变量(bcdedit 改 NVRAM 启动项、Get-SecureBootUEFI 读 Secure Boot 状态、Set-SecureBootUEFI 更新证书)------这些操作最终都经由 Runtime Services 落到固件。
实践提示: 某些机器上 bcdedit /set {fwbootmgr} displayorder ... 报"数据无效"或直接失败,根因往往不在 Windows,而在固件对变量写操作的实现质量(变量空间不足、认证变量需要特定属性、或固件要求进入 Setup 模式)。这类问题只能靠升级固件解决。
1.7 时间都去哪了:固件阶段的耗时构成
如果一台机器"开机慢",先分清慢在哪一段。固件阶段(从上电到 bootmgfw.efi 获得控制权)的典型耗时分布:

最有效的固件级优化手段:
- 在固件设置中启用 Fast Boot(注意与 Windows 的"快速启动"是两个不同的东西):跳过不必要的 USB 设备枚举、省略部分自检,通常能省下 1--5 秒。代价是可能识别不到慢速外设,需要用"Ultra Fast"以外的档位或临时关闭来进 UEFI 设置。
- 减少不必要的 Option ROM:未使用的 RAID 控制器、网卡 PXE ROM 都会增加 DXE 时间。
- 关闭不需要的启动项(尤其是网络上不存在的 PXE 项),避免 BDS 逐个超时。
- 注意:在开启 BitLocker 且 PCR 绑定包含 PCR 0/2/4 的机器上,更新固件会触发 BitLocker 恢复密钥界面 。所以"升级 BIOS"之前应先
manage-bde -protectors -disable C:挂起保护,升级完成后再-enable。这也解释了为什么微软默认把 BitLocker 绑到 PCR 7 + 11 而不是 0+2+4(详见第五章相关讨论)。
第二章 bootmgfw.efi:Windows 引导管理器
2.1 它是谁,它从哪来
bootmgfw.efi 是 Windows Boot Manager 在 UEFI 平台上的实现。"mgfw"即 M anager G raphics/EFI F irmware W rapper 一类的历史缩写延续,其对应的 Legacy BIOS 版本叫 bootmgr(无扩展名)。
它在磁盘上有几处副本,理解它们的区别很重要:

当你在 WinRE 命令提示符里执行:
bcdboot C:\\Windows /s S: /f UEFI
这条命令做的事情,本质上就是:在 \\Windows\\Boot\\EFI\\ 下取一组文件,复制到 ESP 的 \\EFI\\Microsoft\\Boot\\,生成/更新其中的 BCD,并向 NVRAM 写入(或刷新)一条指向 \\EFI\\Microsoft\\Boot\\bootmgfw.efi 的 Windows Boot Manager 启动项。
注意一个常见误解 :bcdboot 并不只复制 bootmgfw.efi。它复制的是整套引导环境:引导管理器本体、语言资源文件(\\EFI\\Microsoft\\Boot\\Fonts\\ 下的字体、bg-BG、zh-CN 等语言目录)、内存诊断程序、以及用当前系统配置生成的 BCD。这也解释了为什么在一个"BCD 彻底损坏"的机器上,bcdboot 往往比 bootrec /rebuildbcd 更彻底------后者试图在既有 BCD 上打补丁,前者直接从源头重建整套。
2.2 作为 EFI 应用的 PE/COFF 结构
bootmgfw.efi 是一个标准的 PE32+ 映像 (64 位 PE/COFF),只是它的子系统类型不是常见的 3(IMAGE_SUBSYSTEM_WINDOWS_CUI)或 2(GUI),而是 10( IMAGE_SUBSYSTEM_EFI_APPLICATION)。固件内置的 PE/COFF 加载器正是靠这个字段判断"这是一个可以直接执行的 EFI 应用"。
一个典型的 bootmgfw.efi 大致结构如下(用 dumpbin /headers 或任何 PE 查看工具可见):
DOS Header (e_magic "MZ", e_lfanew → PE 签名偏移)
└─ DOS Stub ("This program cannot be run in DOS mode.")
PE Signature "PE\\0\\0"
COFF File Header
Machine: IMAGE_FILE_MACHINE_AMD64 (0x8664)
NumberOfSections: 典型 5--8
Characteristics: IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE ...
Optional Header (PE32+)
Magic: 0x20b (PE32+)
Subsystem: 10 (EFI Application)
SizeOfImage: 全部节按 SectionAlignment 展开后的大小
SizeOfHeaders: 头部按 FileAlignment 对齐后的大小
ImageBase: 链接时基址(典型 0x10000000)
SectionAlignment: 通常为 0x1000(4KB,与页对齐)
FileAlignment: 通常为 0x200
CheckSum: 对 EFI 应用有要求
DataDirectory[SECURITY]: 指向 Authenticode 签名(WIN_CERTIFICATE)
DataDirectory[BASERELOC]: 基址重定位表
Sections: .text .rdata .data .pdata .reloc ...
Security Directory: WIN_CERTIFICATE → Authenticode 签名
几个与启动密切相关的细节:
SectionAlignment 与内存布局。 UEFI 规范要求加载器按节头(Section Header)把映像"铺"到内存中:每个节的 VirtualAddress/VirtualSize 决定它在映像内的偏移,节与节之间按 SectionAlignment 对齐。加载器先用 AllocatePages 申请 SizeOfImage 大小的内存,拷贝头部与各个节,处理 .reloc,然后才能跳转。若 ImageBase 处内存被占用,就要重定位------UEFI 环境中固件通常能按链接基址加载(因为此时内存几乎全空),所以重定位表往往用不上,但它是安全启动验证的一部分:签名覆盖的是校验和计算范围内的内容,重定位表参与完整性计算。
.pdata与异常处理。 x64 采用基于表的异常处理(不像 x86 用链式 SEH),.pdata 节存放函数表的 RUNTIME_FUNCTION 条目。引导管理器自身也使用结构化异常处理,错误分支(例如读 BCD 失败)最终会走到一个统一的失败处理路径,在屏幕上渲染那个我们熟悉的错误界面。
签名与校验和。 CheckSum 与 DataDirectory[SECURITY] 是 Secure Boot 能否通过的关键。任何对 bootmgfw.efi 的字节级修改(打补丁、注入代码)都会让签名验证失败。这正是 bootkit 难以"修改微软引导器"而倾向于"挂钩其运行时行为"的原因。
2.3 引导管理器运行时:一个极简操作系统
bootmgfw.efi 被 StartImage 执行后,就成为一个运行在 EFI 环境下的独立程序,它拥有:
- EFI Boot Services(内存、协议、事件)
- EFI Boot Services 中的文件访问 (
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL+EFI_FILE_PROTOCOL)------注意,此时还没有 NTFS 驱动,只有固件提供的 FAT 驱动,这就是为什么 BCD 与引导器必须放在 FAT32 的 ESP 上 - GOP 图形输出(用于渲染图形化启动菜单与错误界面)
- 一套内部库 ,微软称之为 Boot Library,导出符号多为
Bl*前缀(如BlImgAllocateImageBuffer、BlBdStart、BlFwGetBootOption等)
它启动后的主要工作,可以概括为"找 BCD,读 BCD,按 BCD 决策"。
2.4 BCD 是怎么被找到的
引导管理器定位 BCD 的优先级顺序如下:
- 固件传入的启动选项参数(可选数据) 。UEFI 启动项
Boot####的载荷中可以附带参数数据,某些部署场景(如网络启动、OEM 定制)会用这里传递替代的 BCD 路径。这是最高优先级,但普通 Windows 安装通常不带这个数据。 - 默认路径 。在没有显式指定时,引导管理器会尝试从自己所在分区的
\\EFI\\Microsoft\\Boot\\BCD读取。这里"自己所在分区"由加载它的设备路径推导------即引导管理器会去打开与自身映像同分区的那个简单文件系统句柄,再按路径打开文件。这就是为什么"BCD 和 bootmgfw.efi 必须在同一个 ESP 上"是硬性要求。 - 跨分区的显式 device 元素 。BCD 内部
{bootmgr}对象的device元素会再次指明引导管理器所在卷,path指明自身路径,这两个值主要用于bcdedit展示与修复工具校验,而不是定位 BCD 自身。
说得再直白一点:BCD 的路径是"算"出来的,不是"配"出来的。 引导管理器从"我自己被放在哪"反推出 BCD 的位置。这个设计带来一个重要推论:
如果你把 ESP 里的 bootmgfw.efi 单独复制到另一个 FAT 分区并让固件从那里启动,引导管理器会去那个分区的 \\EFI\\Microsoft\\Boot\\BCD 找配置,而不是原来的 ESP。这正是许多"多 ESP 混乱"故障的根源------机器上有两个 ESP(比如装了两块盘,各带一个),固件选中了没有正确 BCD 的那个,于是报 0xc000000f。
2.5 引导菜单与选择逻辑
读到 BCD 之后,引导管理器要决定"加载谁"。BCD 中与此相关的关键元素(位于 {bootmgr} 对象,GUID {9DEA862C-5CDD-4E70-ACC1-F32B344D4795}):

选择过程:
- 若
displayorder只列出一个条目,且timeout为 0(默认值),则不显示菜单,直接加载。这就是为什么绝大多数 Windows 机器开机看不到启动菜单------不是没有菜单,是它没机会显示。 - 若有多个条目(多系统、WinRE 项、VHD 项),则显示图形化菜单,等待
timeout秒。 - 倒计时结束或用户选择后,得到目标对象的 GUID,读取该对象的
device/path,加载对应 EFI 应用。
Windows 8 之后 F8 失效的原因 :传统上,Windows 在引导管理器阶段接受 F8 键,进入"高级启动选项"(安全模式、最后一次正确配置等)。但从 Windows 8 起,为了提高启动速度,引导管理器默认不再等待按键扫描------按键检测被移到了系统加载器与内核阶段。取而代之的是从 Windows 内部主动重启到高级启动 :Shift + 重启、shutdown /r /o /t 0、或设置中的"高级启动"。这些操作最终是通过写入 BCD 的 {globalsettings} 中一个一次性引导标志(以及设置 BootNext 指向 WinRE)实现的------所以它是一次性的:下一次正常启动不会进入恢复环境。
如果你想恢复"启动时按 F8 出菜单"的旧行为,命令是:
bcdedit /set {globalsettings} bootmenupolicy legacy
恢复默认:
bcdedit /set {globalsettings} bootmenupolicy standard
这个开关的本质是:legacy 模式下引导管理器会建立一个按键轮询循环并等待按键,代价是每次启动都增加一点延迟;standard 模式下跳过这个循环。在排查"安全模式都进不去"的场景时,把它设为 legacy 常常很有用。
2.6 引导管理器的三条分支
引导管理器最终会加载三个可能的 EFI 应用之一:
(1) \\Windows\\system32\\winload.efi------ 正常启动路径。 这是本文第四章的主角,对应 BCD 中的 "Windows Boot Loader" 对象。
(2) \\Windows\\system32\\winresume.efi------ 从休眠/快速启动恢复。 当 BCD 的 {bootmgr} 中 {default} 指向的对象带有 resumeobject 元素,且上一次关机写入了有效的休眠映像(hiberfil.sys 中含有有效的恢复数据),引导管理器会转去加载 winresume.efi。
这里有一个容易被忽略的重要事实:Windows 10 默认开启"快速启动"(Fast Startup,内部名 Hiberboot),而快速启动走的正是休眠恢复路径。 也就是说,你每次"关机再开机",走的都不是纯粹的冷启动路径,而是:
关机:注销所有用户 → 把内核态(内核 + 已加载驱动 + 部分系统状态)写入 hiberfil.sys → 断电(S5)
开机:固件初始化 → bootmgfw.efi → 检测到有效恢复数据 → winresume.efi → 直接从休眠映像恢复内核态
它跳过了"重新加载并初始化内核与全部驱动"这一大段工作,所以快。代价是:
- 双系统场景下,Windows 分区的文件系统处于"已挂载"状态,Linux 挂载 NTFS 可能造出不一致(这就是"双系统必须关掉快速启动"这条建议的技术根源)。
- 某些驱动/固件状态不会被真正重置,遇到"重启能好、关机再开就出问题"的怪现象,第一件事就是关掉快速启动验证。
- 快速启动只需要"精简版"休眠文件(
powercfg /h /type reduced,约物理内存 20%),而完整休眠支持需要 "full"(约 40%)。
(3) \\ EFI\\Microsoft\\Boot\\memtest.efi------ 内存诊断。 对应 {memdiag} 对象(GUID {B2721D73-1DB4-4C62-BF78-C548A880142D}),它会在引导管理器菜单的"工具"分支中出现(或由 mdsched.exe 触发一次性引导)。
另外,BCD 中还存在一类"启动扇区应用"(bootsector,{ntldr} 用于引导 Windows Vista 之前的系统)和"自定义应用"(如引导 Linux 的 GRUB 也可以作为一个条目被 Windows 引导管理器加载,虽然实践中通常是反过来:GRUB 链式加载 Windows)。
2.7 引导管理器阶段的故障特征
判断"故障发生在引导管理器阶段"的典型证据:
- 错误信息中出现了
File:指向\\EFI\\Microsoft\\Boot\\...或\\Windows\\system32\\winload.efi------ 后者虽指向 winload,但报错者常常是 bootmgfw(因为它负责加载 winload)。 - 屏幕上的错误界面是深色背景、白字、带错误码的那种(WinRE 风格的引导错误界面),而不是蓝屏。
- 错误码集中在
0xc000000f、0xc000000e、0xc000000d、0xc0000225。
最常见的三类根因:
根因一:BCD 文件本身丢失或不可读。 典型触发场景:ESP 被格式化、磁盘克隆后 ESP 未正确复制、第三方分区工具挪动了分区导致设备路径失效、磁盘出现坏扇区。
根因二:BCD 存在,但内容指向了错误的位置。 BCD 内部用"磁盘签名 + 分区偏移"(MBR)或"磁盘 GUID + 分区 GUID"(GPT)来定位分区。克隆磁盘、更换主板、改变磁盘控制器模式、把系统盘从一台机器搬到另一台,都可能让这些标识与物理现实不匹配。此时 BCD 文件是完好的,但它指向了一个不存在的分区。
根因三:固件与 Windows 的启动项不一致。 比如 NVRAM 里的 Boot#### 设备路径指向的分区 GUID,与当前磁盘上的分区 GUID 不匹配(常见于换了硬盘、重新分区、清除了 NVRAM)。这种情况下固件根本找不到 bootmgfw.efi,屏幕上通常是固件自己的提示("No bootable device" 或厂商 LOGO 后进入固件设置),还没到 Windows 的错误界面。
快速判别三者的方法:
# 在 WinRE 命令提示符中
diskpart
list disk
list volume # 确认是否存在 FAT32 的 ESP,盘符是多少
exit
dir S:\\EFI\\Microsoft\\Boot\\ # 若提示路径不存在 → 根因一/三
bcdedit /store S:\\EFI\\Microsoft\\Boot\\BCD /enum all
# 若提示无法打开存储 → 根因一(文件损坏)
# 若能打开但 device/osdevice 指向 unknown → 根因二
bcdedit /enum firmware # 查看固件层启动项与实际磁盘是否匹配
第三章 BCD:固件之外的启动数据库
3.1 BCD 本质上是一个注册表 hive
这是理解 BCD 最重要的一句话:BCD 文件就是 Windows 注册表 hive 文件,用的是同一个 regf格式,可以用 reg load挂载、可以用注册表编辑器查看、可以用任何 hive 解析库读写。
它的内部结构:
BCD (regf 文件)
└─ Objects
├─ {9dea862c-5cdd-4e70-acc1-f32b344d4795} ← {bootmgr}
│ ├─ Description
│ │ └─ Type = 0x10100002 ← 对象类型码
│ └─ Elements
│ ├─ 11000001 device (REG_BINARY, BCD 设备描述符)
│ ├─ 12000002 path (REG_SZ, UTF-16LE)
│ ├─ 12000004 description (REG_SZ)
│ ├─ 12000005 locale (REG_SZ)
│ └─ 14000006 inherit (REG_MULTI_SZ / 对象列表)
├─ {a5a30fa2-3d06-4e9f-b5f4-a01df9d1fcba} ← {fwbootmgr}
├─ {b2721d73-1db4-4c62-bf78-c548a880142d} ← {memdiag}
├─ {随机 GUID} ← Windows Boot Loader
│ ├─ Description → Type = 0x10200003
│ └─ Elements
│ ├─ 11000001 device
│ ├─ 21000001 osdevice
│ ├─ 12000002 path (\\Windows\\system32\\winload.efi)
│ ├─ 22000002 systemroot (\\Windows)
│ ├─ 23000003 resumeobject
│ └─ 25000020 nx
└─ ...
挂载方式(在 WinRE 或正常系统中,以管理员权限):
reg load HKLM\\BCD00000000 S:\\EFI\\Microsoft\\Boot\\BCD
regedit # 浏览 HKEY_LOCAL_MACHINE\\BCD00000000
reg unload HKLM\\BCD00000000
离线挂载的价值在于:当 bcdedit 因为某些元素损坏而拒绝打开存储时,直接操作 hive 层可以绕过校验做修复;同时也便于做取证(例如查看某台机器历史上配置过哪些启动项)。
3.2 对象类型码
每个对象在 Description\\Type 中记录一个 32 位类型码:

对应的常见 GUID:

3.3 元素类型码的编码规则
BCD 的元素类型是一个 32 位整数,按位域划分:

Class(消费者类别):

Format(数据格式):

于是,元素类型码的高 8 位就决定了它的"命名空间归属"。举例:
0x11000001= Class 1 (Library) + Format 1 (Device) + SubType 1 → Library 类的 device 元素0x21000001= Class 2 (Application) + Format 1 (Device) + SubType 1 → 应用类的 osdevice 元素0x25000020= Class 2 + Format 5 (Integer) + SubType 0x20 → 应用类的 nx 元素0x12000004= Class 1 + Format 2 (String) + SubType 4 → Library 类的 description 元素
这个编码规则非常实用 :看到任何一个陌生的元素码,你都能立即判断它的数据类型和适用范围,而不必查表。也就能理解为什么 bcdedit /set 时类型必须匹配------对 Integer 类型的元素赋字符串值会被拒绝(部分类型支持字面量,如 nx OptIn)。
3.4 常用元素速查表
Library 类(所有对象通用,前缀 0x1x):

Application 类(前缀 0x2x,仅对特定应用类型有意义):

说明: 上表类型码取自微软公开文档与 bcdedit /? TYPES 的实测输出,不同 Windows 版本间可能有增删。在生产环境使用前,建议先在本机运行 bcdedit /? TYPES <应用类型> 确认。
几个在排障中特别有用的元素:
-
safeboot(0x25000080) :设为Minimal后重启,等同于"强制安全模式"。当系统因驱动问题无法启动、而你又进不去高级启动菜单时,这是救命的命令:bcdedit /set {default} safeboot minimal
修复完成后务必清除:
bcdedit /deletevalue {default} safeboot
-
lastknowngood(0x26000025):使用"最后一次正确配置"。注意,LKG 的语义在现代 Windows 上已经弱化------它恢复的是上一次成功启动所用的 ControlSet,而不是完整的系统还原点。 -
nointegritychecks/testsigning:这两个会显著降低系统安全性(关闭驱动签名强制),且它们本身是被 BitLocker 的 BCD 验证配置文件所"监视"的(见下文)。排障时可以临时开,用完必须关。 -
hypervisorlaunchtype:设为Off会禁用 Hyper-V/虚拟化安全特性(VBS、Credential Guard、内存完整性)。在"装了 Hyper-V 或某些沙箱/安卓子系统后无法启动"的排障中,这是常用手段。
3.5 继承机制:inherit 与全局设置对象
BCD 采用了类似注册表/组策略的"继承"设计,避免在每个条目里重复配置。默认继承链:
{globalsettings} 所有对象都应继承的全局设置(调试、EMS、全局引导标志)
{bootloadersettings} 所有 OS Loader 条目应继承的设置
{resumeloadersettings} 所有 Resume Loader 条目应继承的设置
{emssettings} EMS(紧急管理服务,串口控制台)设置
{dbgsettings} 调试器全局设置(串口/USB/网络 KD 参数)
{hypervisorsettings} 虚拟机监控程序设置
{badmemory} 全局内存坏块列表(继承给所有条目)
{ramdiskoptions} RAM 磁盘选项(WinPE/WDS 场景)
一个 OS Loader 条目默认的 inherit 值通常是:
inherit = {bootloadersettings} {globalsettings} {resumeloadersettings} {emssettings} {dbgsettings} {hypervisorsettings} {badmemory}
实践意义 :当你在某个具体条目上 bcdedit /deletevalue 删不掉某个设置时,很可能它来自继承对象。用 bcdedit /enum all 可以看到 {globalsettings} 等对象自身的内容。这也是为什么"改了设置没生效"的常见原因------被继承值覆盖了,或反过来,你在具体条目上设的值被继承链中的同名元素掩盖。
{badmemory}与内存坏块 :Windows 会在检测到内存错误后,把出错的物理页记录到 {badmemory} 对象的 0x1700000a(BadMemoryList)中,并在以后启动时避开这些页。这个机制有时会造成"越修越少的可用内存",用 bcdedit /deletevalue {badmemory} badmemorylist 可以清空(前提是故障内存已更换)。
3.6 设备描述符:BCD 如何定位一个分区
BCD 中 device、osdevice 等元素的 Format 是 Device,其值不是简单的盘符,而是一个结构化的 设备描述符。它包含一个"设备类型 GUID",后跟类型特定的数据。
常见形态:

其中最值得关注的是 locate=。它把"BCD 指向固定的磁盘签名 + 分区偏移"变成了"启动时动态搜索"。
- MBR 磁盘:默认使用磁盘签名(MBR 偏移 0x1B8 处的 4 字节)+ 分区起始 LBA。克隆磁盘时签名可能重复或改变,导致 BCD 指向错误。
- GPT 磁盘:使用磁盘 GUID + 分区 GUID。更健壮,但如果用工具重新生成了分区表,同样会失效。
- 改为
locate=的效果:启动时 bootmgfw/winload 会扫描所有可访问的分区,找到第一个含有指定路径的卷。这在"磁盘标识变了但内容还在"的场景下极为有效,代价是启动略慢,且多系统环境下可能匹配到错误的分区。
bcdedit输出中 device 显示为 unknown的含义:说明 BCD 中记录的磁盘标识,在当前系统中找不到对应分区。这是诊断"BCD 指向错误分区"最直接的信号,常见于把系统盘换到另一台机器、或克隆之后。
3.7 bcdedit 之外的操作方式
导出/导入整个存储:
bcdedit /export C:\\BCD.bak # 导出系统 BCD
bcdedit /import C:\\BCD.bak # 导入
注意 /export 导出的仍是 hive 格式的二进制文件,不是可读文本;bcdedit /enum all > C:\\bcd.txt 才能得到可读快照。动手改 BCD 之前先做这两件事,是唯一值得养成的习惯。
针对离线存储操作:
bcdedit /store S:\\EFI\\Microsoft\\Boot\\BCD /enum all
bcdedit /store S:\\EFI\\Microsoft\\Boot\\BCD /set {default} safeboot minimal
在 WinRE 中,系统 BCD 未必是默认存储(WinRE 有自己的 BCD),所以离线操作时务必显式带 /store。
创建新条目(典型场景:手工添加第二个 Windows):
bcdedit /create /d "Windows 10 - 第二个安装" /application osloader
# 会返回一个新 GUID {xxxxxxxx-...}
bcdedit /set {新GUID} device partition=D:
bcdedit /set {新GUID} osdevice partition=D:
bcdedit /set {新GUID} path \\Windows\\system32\\winload.efi
bcdedit /set {新GUID} systemroot \\Windows
bcdedit /displayorder {新GUID} /addlast
固件层启动项(NVRAM):
bcdedit /enum firmware # 列出固件启动项(含 Windows Boot Manager 与其他 OS)
bcdedit /set {fwbootmgr} displayorder {bootmgr} /addfirst
{fwbootmgr} 对象是 NVRAM 中 Boot#### 变量在 BCD 里的镜像。操作它等价于操作固件启动顺序。这比进 UEFI 设置界面点鼠标方便,但同样受限于固件的变量写实现质量。
3.8 多系统场景下的 BCD
理解多系统,关键是分清"谁在管菜单":
模式 A:Windows 引导管理器管菜单(Windows 优先)。
NVRAM 第一启动项是 Windows Boot Manager → \\EFI\\Microsoft\\Boot\\bootmgfw.efi。要加 Linux,需要在 BCD 中增加一个指向 GRUB 的条目:
bcdedit /create /d "Ubuntu" /application bootsector
bcdedit /set {新GUID} device partition=S:
bcdedit /set {新GUID} path \\EFI\\ubuntu\\grubx64.efi
bcdedit /displayorder {新GUID} /addlast
模式 B:GRUB/rEFInd 管菜单(Linux 优先)。
NVRAM 第一启动项指向 \\EFI\\ubuntu\\grubx64.efi 或 \\EFI\\refind\\refind_x64.efi,由它检测 Windows 并生成 chainloader /EFI/Microsoft/Boot/bootmgfw.efi 条目。这种模式更常见,因为 Linux 安装程序默认会这样做(把 GRUB 设为第一启动项)。
模式 C:固件管菜单。 直接在 UEFI 设置里选择启动项,或用开机时的启动菜单键(F8/F11/F12 因厂商而异)。这个模式下两套 BCD/GRUB 配置互不干扰,最不容易出问题。
多系统的铁律:
- 不要让 Linux 的 GRUB 配置文件去"猜" Windows 的 ESP。 GRUB 的
os-prober在某些配置下会找错 ESP,表现为 GRUB 菜单里的 Windows 项启动后报0xc000000f。 - Windows 大版本更新会重写 NVRAM 启动项 ,把
Windows Boot Manager重新设为第一。这是"装完 Windows 更新后 GRUB 不见了"的原因,重新进 UEFI 设置调整顺序即可,数据不会丢。 - 两个 ESP 是灾难的开始。 双盘双系统时,如果两块盘各有一个 ESP,而固件选中了没有对应 BCD 的那个,就会启动失败。建议全机只保留一个 ESP。
- 快速启动与双系统不兼容:务必在 Windows 中关闭快速启动(控制面板 → 电源选项 → 选择电源按钮的功能 → 取消"启用快速启动"),否则 Linux 挂载 NTFS 时可能损坏数据。
3.9 BCD 与 BitLocker 的联动
BCD 内容会被纳入 BitLocker 的完整性保护。BitLocker 定义了一个"BCD 验证配置文件(BCD Validation Profile)"------一张允许存在/忽略的元素白/黑名单。默认的验证配置文件包含如下元素(前缀含义:全部 = 所有应用类型都校验,winload = 仅对 OS Loader 校验):

这张表的实践含义极其重要 :修改上述任何一个元素,都会改变 BitLocker 的度量值,从而在下一次启动时触发 BitLocker 恢复密钥界面。
这解释了一个经典困扰:
"我只是开了一下测试签名(bcdedit /set testsigning on),重启就要我输 BitLocker 恢复密钥!"
原因就在这里。testsigning(0x16000049)在验证配置文件中被监视。同理,bcdedit /set {default} nx AlwaysOff、/set debug on 也会触发。
正确做法:
# 修改 BCD 之前,先挂起 BitLocker
manage-bde -protectors -disable C:
<进行 BCD 修改 / 固件升级 / 硬件变更>
manage-bde -protectors -enable C:
挂起保护的实质是:在磁盘上放置一个明文的保护器("挂起"密钥),允许本次启动绕过 PCR 校验;重新启用后该明文保护器被删除,PCR 度量值被重新记录。这比"输入恢复密钥"优雅,因为它不需要人手、且能自动重新密封。
风险提示 :-disable 期间机器上存在明文密钥,应在受控环境下尽快完成操作并 -enable。
第四章 winload.efi:操作系统加载器
如果说 bootmgfw.efi 是"选谁来跑",那么 winload.efi 就是"把它跑起来所需的全部准备工作"。这是整条链路中工作量最大、也最容易出错的一环。
4.1 定位与加载
引导管理器确定目标条目后,读取其 device(0x11000001)与 path(0x12000002)元素。对标准 Windows 10 UEFI 安装,这两个值是:
device partition=C: (即 \\Device\\HarddiskVolumeN,Windows 所在卷)
path \\Windows\\system32\\winload.efi
引导管理器用固件的文件服务把这个文件读进内存,但它不是简单地跳转执行 ------它通过 ImgArchStartBootApplication 一类的内部例程,完成一个"受保护的启动应用移交":验证签名、准备执行环境、传递参数,然后进入 winload 的入口。
这里有一个安全上的重要设计:从 bootmgfw 到 winload 的这次加载,同样要过签名验证 。即便 Secure Boot 在固件层已经关闭,Windows 的引导管理器依然会用微软的密钥体系校验 winload.efi 的完整性。这就是所谓 Trusted Boot(可信启动):微软把自己的信任链从固件层延续到了操作系统加载器层,每一环都验证下一环。
如果这一步失败,你会看到:
Recovery
Your PC needs to be repaired
The application or operating system couldn't be loaded because a required file is missing or contains errors.
File: \\Windows\\system32\\winload.efi
Error code: 0xc0000428
0xc0000428 的含义是 STATUS_INVALID_IMAGE_HASH ------ 镜像哈希无效,即签名校验失败。这通常意味着:
- winload.efi 被篡改(恶意软件或错误的修补);
- 磁盘错误导致文件读取损坏;
- 系统文件版本与引导管理器期望的不一致(例如跨版本回滚、组件存储损坏)。
注意区分 0xc000000f与 0xc0000428:前者是"找不到/打不开"(路径或 BCD 问题),后者是"找到了但不可信"(内容问题)。两者修复方向完全不同------前者查 BCD 与分区,后者跑 sfc / DISM。
4.2 winload 的工作清单
进入 winload 之后,它要在没有操作系统、没有文件系统驱动、没有内存管理、没有进程概念的环境下,把整个内核运行环境搭起来。可以把它做的事分为九组:
(1)建立基础执行环境。 初始化自己的堆、异常处理、调试输出通道(串口/USB/网络 KD,由 BCD 的 {dbgsettings} 决定),初始化显示(沿用固件 GOP 提供的 framebuffer,绘制 Windows 启动 LOGO 或转圈动画)。
(2)加载并显示启动相关的图形与本地化资源。 字体(.ttf)、多语言字符串、启动动画资源(bootres.dll 中的位图/动画)。这也是为什么"启动界面变成乱码/方块"往往是资源文件损坏而非内核问题。
(3)加载并校验 SYSTEM 注册表 hive。 读取 \\Windows\\System32\\config\\SYSTEM(几十 MB 级的 hive),在内存中构建 \\Registry\\Machine\\System。这是后续几乎所有决策(加载哪些驱动、用什么启动类型、有哪些服务)的依据。
(4)解析启动配置。 读取 BCD 中该条目的全部元素、SYSTEM\\CurrentControlSet\\Control 下的内核调参数(即"控制向量",包含上百个内核调优项)、安全启动/代码完整性策略(SI Policy,来自 \\Windows\\System32\\CodeIntegrity\\SiPolicy.p7b 或 CiPolicies\\Active\\)。
(5)加载代码完整性模块与策略。 ci.dll(Code Integrity)随 winload 一同加载,用于后续验证每一个被加载的映像。
(6)加载 HAL 与内核映像。 按 BCD 的 kernel(0x22000011,默认 ntoskrnl.exe)与 hal(0x22000012,默认 hal.dll)元素,把两个 PE 映像装入内存、完成重定位与导入解析。
(7)加载引导必需的驱动。 包括:
- 微码更新
mcupdate.dll(CPU 微码补丁,由加载器在早期应用) - 调试传输
kdcom.dll或kdnet.dll+kd_02_*.dll(内核调试的通信后端) - HAL 依赖层 与早期总线/中断支持(
acpi.sys、pci.sys、intelppm.sys等) - 启动文件系统与磁盘栈 相关驱动(
disk.sys、partmgr.sys、storport/storahci/stornvme、ntfs.sys、fvevol.sys若启用 BitLocker、volsnap.sys) - 图形
bootvid.dll(启动时的图形/蓝屏渲染)、basicdisplay.sys或厂商 GOP 兼容驱动 - 安全相关
sgrmagent.sys(System Guard 运行时监视代理)、mssecflt.sys(安全事件组件微筛选器) - ELAM 驱动 (
WdBoot.sys等,见 4.9) - 其他 Start=0(boot start)驱动,按服务组顺序加载
(8)构建内存与页表结构。 这是 winload 最"内核味"的工作:建立物理内存描述符列表、构建初始页表(PML4/PDPT/PD/PT 层级)、把内核映像映射到高半核(典型 x64 Windows 内核基址在 0xFFFFF800'00000000 之后)、建立恒等映射的过渡区域、设置 LoaderBlock。
(9)移交控制权。 调用 ExitBootServices()、调用 SetVirtualAddressMap() 搬迁运行时服务、然后跳转到内核入口。
4.3 PE 映像是怎么被"加载"的
理解第(6)步的"加载一个 PE 映像",对理解驱动签名、导入依赖、以及某些蓝屏成因很关键。流程如下:
- 读头部 ,确认
Machine是IMAGE_FILE_MACHINE_AMD64、子系统是IMAGE_SUBSYSTEM_NATIVE(值为 1,驱动程序与内核用),Magic是0x20b(PE32+)。 - 申请内存 :按
SizeOfImage申请连续页。注意此时还没有虚拟内存管理器,用的是加载器自己的页分配器(在 UEFI 环境下基于AllocatePages)。 - 铺开各节 :按节头的
PointerToRawData/SizeOfRawData从文件拷贝,按VirtualAddress/VirtualSize放到内存中;VirtualSize大于SizeOfRawData的部分(.bss类未初始化数据)清零。 - 处理重定位 :如果实际加载基址 ≠
ImageBase,遍历.reloc表按类型(x64 上主要是IMAGE_REL_BASED_DIR64)修正。内核与 HAL 通常能被加载到首选基址,但驱动几乎都要重定位。 - 解析导入表 :对每个依赖模块,确认它已经在加载列表中;把导入地址表(IAT)中的每一项填成目标函数的实际地址。这里有个重要约束:驱动的导入依赖必须能被满足。 如果驱动 A 依赖驱动 B 的导出,而 B 还没加载或加载失败,A 的加载就失败------这是"驱动加载顺序组"(
ServiceGroupOrder+ 每个服务的Group/Tag)存在的根本原因。 - 校验映像 :调用
ci.dll验证签名(Authenticode + 目录签名.cat),并检查是否被安全策略(如 HVCI、WDAC 策略)允许。 - 登记进加载器列表 :把它加入到加载器维护的"加载顺序链表"(
LoadOrderListHead)与"启动驱动链表"中。这个链表最终会通过 LoaderBlock 交给内核,内核据此构建PsLoadedModuleList。
一个常见的蓝屏成因就来自第 6 步 :驱动文件本身完好,但它的目录签名 .cat 文件丢失或组件存储中的 catalog 损坏,导致校验失败,报 0xc0000428 或在内核阶段报 0xc0000221(STATUS_IMAGE_CHECKSUM_MISMATCH)。
4.4 SYSTEM hive 与 ControlSet 的选择
\\Windows\\System32\\config\\SYSTEM 是启动期最关键的一个文件。winload 把它整体读入内存,并在其中做几件决定性的事:
(1)选择 ControlSet。 SYSTEM hive 下通常有多个 ControlSet001、ControlSet002,以及一个易失的 CurrentControlSet 符号链接。选择逻辑由 SYSTEM\\Select 键决定:
Select
Current = 1 → 本次启动用 ControlSet001
Default = 1 → 下次默认用 ControlSet001
Failed = 2 → 上一次启动失败的是 ControlSet002
LastKnownGood = 2
- 正常启动时,
Current = Default。 - 若上一次失败(或用户选择了"最后一次正确配置"),
Current会被设为LastKnownGood。 - 启动成功后,内核会把当前使用的 ControlSet 复制一份作为新的 LastKnownGood。
(2)读取内核调参(控制向量)。 ControlSet00N\\Control 下的上百个值决定了:SystemStartOptions(BCD 传入的启动选项字符串)、Session Manager(BootExecute、内存管理参数、分页文件设置)、ServiceGroupOrder、GroupOrderList、Video、CrashControl、ProductOptions、MiniNT(WinPE 标记)等。
(3)枚举启动驱动。 遍历 ControlSet00N\\Services 下所有 Start = 0(SERVICE_BOOT_START)的服务,读出其 ImagePath、Group、Tag、Type、ErrorControl,构建加载队列。
如果 SYSTEM hive 损坏 ,你会看到 0xc0000001 或更典型的 0xc0000218(STATUS_REGISTRY_HIVE_RECOVERED / hive 损坏相关)。修复手段是从 \\Windows\\System32\\config\\RegBack\\ 恢复旧副本(注意:Windows 10 1803 之后,系统默认不再自动备份注册表到 RegBack,所以这个目录可能是空的),或使用系统还原点。
4.5 页表与内存布局:winload 最"硬核"的部分
在 x64 上,Windows 使用四级页表(PML4 → PDPT → PD → PT),每级 512 项 × 8 字节 = 4KB 一页,每级覆盖 9 位虚拟地址,共 48 位虚拟地址空间(Windows 实际只用其中的一部分),页大小 4KB(大页 2MB/1GB 用于特定映射)。
winload 在移交前建立的映射,大致要满足:
- 内核映像映射 :把 ntoskrnl.exe 按其
ImageBase(System区域,典型0xFFFFF800'00000000起,且启用 ASLR 时有随机偏移)映射到内核虚拟地址空间的高半部。 - 恒等映射 / 过渡映射:由于从 winload 跳转到内核时,CPU 必须先开启内核自己的 CR3,而跳转指令本身还在当前地址空间执行,需要有一段在两个地址空间中都有效的代码过渡。加载器会建立必要的临时映射,保证最后那条跳转指令不会"跳进虚空"。
- HAL 与所有已加载驱动的映射:同样映射到系统空间。
- 物理内存的全映射 :内核需要能访问任意物理页(用于 PFN 数据库、分页池等),加载器建立起物理内存描述符列表(Memory Descriptor List),描述每一段物理内存的类型(
LoaderFree、LoaderLoadedProgram、LoaderFirmwareTemporary、LoaderOsloaderStack、LoaderSystemBlock、LoaderBad等)。 - ACPI/固件表保留区:ACPI 表、SMBIOS、UEFI 运行时服务代码/数据区被标记为保留,内核不得覆盖。
- 帧缓冲区:GOP 的 framebuffer 被保留并映射,以便内核与 bootvid 继续往同一个地址画东西(这就是为什么从启动 LOGO 到登录界面的过渡可以不闪黑)。
一个关键细节:内核的 ASLR 是在这里做的。 每次启动,winload 会随机选择内核映像的加载偏移(KASLR 的一部分)。这使得 rootkit 无法硬编码内核地址,也是前文提到的 bootkit 必须通过挂钩 ExitBootServices / OslArchTransferToKernel 去"现找"ntoskrnl 基址的原因------因为地址每次都不一样。
4.6 加载器参数块(LoaderBlock):交接的"信封"
winload 与内核之间传递的是一个巨大的结构体,通常称为 LoaderBlock (内核侧符号 KeLoaderBlock,类型 LOADER_PARAMETER_BLOCK 的加载器版本)。它承载了内核在 Phase 0 之前无法自己获取的一切。主要字段族包括:

为什么 LoaderBlock 如此重要? 因为在它交接完成之前,内核是没有"自我认知"的:它不知道自己被加载到哪里、不知道内存多大、不知道有哪些驱动、不知道磁盘在哪。LoaderBlock 就是内核的"出生证明"。
同时它也成了攻防焦点:任何能在 OslArchTransferToKernel 之前篡改 LoaderBlock 的代码,都能改变内核看到的现实(插入伪造的驱动、修改内存描述符、伪造安全启动状态)。这解释了为什么现代防护(Secure Launch / DRTM、HVCI、System Guard)会在这条路径上层层设防。
4.7 最后的移交:三行代码的距离
移交过程的实质,可以压缩到三个动作:
// 概念化伪代码,非真实实现
Status = OslFwpKernelSetupPhase1(...); // 内含 gBS->ExitBootServices(ImageHandle, MapKey)
// 以及 gRT->SetVirtualAddressMap(...) 的准备工作
if (NT_SUCCESS(Status)) {
OslArchTransferToKernel(LoaderBlock, KernelEntry); // 不再返回
}
第一步: ExitBootServices。 前文已述。此处补充两点:
- 调用前必须先用
GetMemoryMap()拿到最新的MapKey。如果在两者之间发生了任何内存分配,MapKey过期,调用返回EFI_INVALID_PARAMETER,加载器需要重试(重新取映射、再调用)。这也是某些恶意驱动插入分配后容易造成启动失败的原因。 - 调用成功后,所有 Boot Services 函数指针立即失效。唯一还能用的只有 Runtime Services。
第二步: SetVirtualAddressMap。 遍历内存映射中标记为 Runtime 的描述符,把它们的 VirtualStart 填上内核分配的虚拟地址,通知固件"以后请按这些虚拟地址访问"。固件驱动借此修正自己的内部指针。此后 Windows 通过 gRT 调用 GetVariable/SetVariable/ResetSystem 等。
第三步:跳转。 加载 CR3(切换到内核页表)、设置栈、把 LoaderBlock 的地址按约定放进寄存器(x64 调用约定下通常是 RCX 或由加载器与内核约定的寄存器/全局),然后跳到 ntoskrnl 的入口点------也就是 KiSystemStartup。
从此,winload 的代码不再存在。 它所在的内存会被内核在 Phase 1 的 Phase1InitializationDiscard 中连同 INIT 段一起丢弃(释放回可用内存池)。
4.8 winload 阶段的度量与安全
(1)PCR 11 与 BitLocker 的解锁。
如果系统盘被 BitLocker 加密,winload 必须在加载内核之前把磁盘密钥解出来。流程:
- bootmgfw 从磁盘的 BitLocker 元数据区(位于分区头部的未加密区域)读出加密的 VMK(Volume Master Key)。注意:元数据区与引导文件都在未加密的"引导块"里,否则固件根本没有文件系统驱动能读加密区)。
- 向 TPM 请求解封(Unseal)VMK。TPM 只在当前 PCR 值与密封时记录的一致时才释放密钥。
- 用 VMK 解开 FVEK(Full Volume Encryption Key) ,此后磁盘栈(
fvevol.sys)可以透明解密读写。
PCR 校验配置文件(Platform Validation Profile) 决定了"哪些 PCR 必须一致":

可用 manage-bde -protectors -get C: 查看当前生效的配置:
TPM:
ID: {85825FF8-3733-48D0-B0EE-4D32D8AAFD7A}
PCR Validation Profile: 7, 11
(Uses Secure Boot for integrity validation)
这里有一个值得深入理解的安全权衡:
- PCR 7 只度量"Secure Boot 策略",不度量引导器二进制本身。 也就是说,只要 Secure Boot 处于开启状态、密钥库未被改动,任何合法签名的引导器都能让 PCR 7 保持不变。这正是"为什么用 PCR 7+11 比 0+2+4 更不容易在固件更新后进恢复模式"的原因,同时也是它的弱点:若某个被合法签名但存在漏洞的引导器(例如带有 CVE 的老版 shim)被换上,PCR 7 不会察觉。
- PCR 11 由 Windows 引导管理器扩展,用于"锁定"VMK 的派生。 关键点在于:引导管理器在把控制权交给 winload 之后,会往 PCR 11 扩展一个值,使得此后任何代码都无法再从 TPM 解出正确的 VMK。这保证了即使操作系统层被完全攻破,攻击者也拿不到那把密钥。
- PCR 0/2/4 度量具体固件与引导器哈希,更严格,但任何固件更新、引导管理器更新(微软的安全更新会替换 bootmgfw.efi)都会改变这些值,从而触发 BitLocker 恢复。这是"可用性与严格性"的典型取舍。
(2)PCR 12/13/14 与 ELAM。
- winload 把 ELAM(Early Launch Anti-Malware)策略的哈希写入 PCR 13,并在加载每个 boot-start 驱动时把驱动镜像的摘要也扩展进去。这构成了"启动模块详情"的完整记录。
- 若 ELAM 驱动判定某个启动驱动为恶意,除了阻止其加载外,还可以调用
Tbsi_Revoke_Attestation(),往 PCR 12 扩展一个未指定的值并递增事件计数器,从而主动破坏本机的可信状态,使远程证明服务器拒绝为这台机器签发健康证明。这是"即使攻击成功也无法伪装成健康"的设计。 - PCR 14 记录"启动权威(Boot Authorities)",即在验证内核驱动时被考虑的公钥集合。远程证明可以借此判断机器上是否存在"自定义内核签名者(Custom Kernel Signers)"策略------这是反作弊与高安全场景关注的点。
(3)ELAM 驱动本身。
ELAM(早启动反恶意软件)自 Windows 8 引入。其要点:
- 它是一个 boot-start 驱动,属于特殊的 Early-Launch 加载顺序组 (注意:这个组不在
ServiceGroupOrder的列表里,是加载器特判的)。 - 签名要求:必须由带有 Early Launch EKU(
1.3.6.1.4.1.311.61.4.1) 的代码签名证书签署(另加常规 Code Signing EKU1.3.6.1.5.5.7.3.3)。 - 它为每个后续的 boot-start 驱动返回一个分类:Good / Bad / Unknown。
- 策略存储在
HKLM\\SYSTEM\\CurrentControlSet\\Policies\\EarlyLaunch\\DriverLoadPolicy;默认策略下,Good、Unknown、以及"Bad 但被标记为 Critical(阻止会导致系统无法启动)"的驱动会被加载,普通 Bad 驱动被跳过。 - 恶意特征数据存储在
HKLM\\ELAMhive(文件为\\Windows\\System32\\config\\ELAM),由 winload 与 ELAM 驱动一同加载,且必须带签名供 ELAM 驱动校验。 - Windows 默认的 ELAM 驱动是 Windows Defender 的
WdBoot.sys。
4.9 两条变体路径:休眠恢复与 WinPE
(1) winresume.efi------ 休眠与快速启动。
与 winload 并列,winresume.efi 负责从 hiberfil.sys 恢复系统。它的工作要简单得多:
- 读取 BCD 中 resume 对象的
filedevice(0x21000001)/filepath(0x22000002),定位hiberfil.sys。 - 校验休眠映像的完整性(签名与校验和),若开启 BitLocker,同样要走 TPM 解封流程。
- 恢复内存映像:把休眠文件中保存的内核态页面写回物理内存,恢复处理器状态(CR3、CR4、GDT/IDT 寄存器、浮点/SIMD 状态等)。
- 跳转到内核的休眠恢复入口,由内核完成设备状态恢复。
快速启动时,休眠映像里保存的是什么? 只有内核态:内核映像自身的状态、已加载驱动的状态、以及部分系统缓存。用户会话被完全关闭(所有用户进程终止、用户配置注销),所以快速启动后你仍需重新登录、重新打开程序,但内核与驱动已经"预热"好了。
这也带来一个排障要点:如果问题出在驱动初始化(例如某个驱动偶发初始化失败),快速启动会把它"冻"进休眠文件里反复复现;而"重启"(restart)走的是完整冷启动,往往能解决问题。 这就是为什么技术支持的第一句话总是"你试过重启吗"------restart 与 shutdown+power on 在 Windows 10 上不是一回事。
(2)WinPE / 恢复环境。
WinRE 与安装环境走的是另一套结构:
- 引导管理器加载的仍是 winload.efi,但 BCD 条目带有
winpe(0x26000022)= Yes 标记。 systemroot指向\\Windows但device/osdevice是一个 RAM 磁盘 (ramdisk=[C:]\\sources\\boot.wim,{ramdiskoptions})。- winload 把 WIM 映像整体读入内存的一个 RAM 磁盘,然后从中启动一个精简的 Windows。
WinRE 的配置独立于主系统,其 BCD 通常位于 \\EFI\\Microsoft\\Recovery\\BCD(ESP 上),由 reagentc 管理:
reagentc /info # 查看 WinRE 状态与位置
reagentc /enable # 启用(会注册到主 BCD 的 recoverysequence)
reagentc /disable
BCD 中的 recoverysequence(0x14000008)与 recoveryenabled(0x16000009) :前者是"引导失败后自动尝试的恢复条目 GUID",后者是总开关。二者配合 {bootmgr} 的 displayorder/toolsdisplayorder,构成了"连续启动失败自动进 WinRE"的机制。
4.10 winload 阶段的错误码速查

排障的一般路径(按"从外到内、从便宜到昂贵"的顺序):
1. 确认硬件可见:diskpart → list disk / list volume
看不到盘 → 硬件/固件/控制器问题,先解决这个
2. 检查文件系统:chkdsk C: /f /r
3. 检查系统文件:sfc /scannow /offbootdir=C:\\ /offwindir=C:\\Windows
4. 修复组件存储:DISM /Image:C:\\ /Cleanup-Image /RestoreHealth
5. 重建引导文件:bcdboot C:\\Windows /s S: /f UEFI
6. 重建 BCD:ren BCD BCD.old 后再 bcdboot(bcdboot 会生成新的)
7. 最后手段:系统还原 / 重置此电脑(保留个人文件)/ 重装
第五章 内核启动:ntoskrnl.exe 与 hal.dll
5.1 先修正一个流传很广的说法
用户的原始描述是:内核启动:ntoskrnl.exe(系统内核主体)、hal.dll(硬件抽象层)。
这个说法在 Windows 10 之前是准确的,但在 Windows 10 2004(版本 2004 / 20H1)之后已经过时了。
事实是:从 Windows 10 2004 开始,HAL 的实现被静态链接进了 ntoskrnl.exe。磁盘上那个 hal.dll仍然存在,但它只是一个向后兼容的存根(stub)。
证据非常直观------比较文件大小:

为什么这么做?回顾历史:
- Windows NT 时代 :HAL 是真正独立的模块。安装介质上有多个 HAL 文件(
hal.dll、halacpi.dll、halapic.dll、halmacpi.dll、halmps.dll等),安装程序根据"是否有 ACPI""是否有 APIC""是否多处理器"选择其中一个,重命名为hal.dll。 - Windows 8:x86 平台也统一为单一 HAL(x64 平台一直只有一个)。多个 HAL 文件被合并为一个运行时自适应的实现。
- Windows 10 2004:HAL 与内核彻底合并,静态链接。
合并的动机是性能与安全:
- 减少跨模块调用开销。 HAL 函数(如
HalRequestSoftwareInterrupt、KeGetCurrentIrql相关路径、I/O 端口访问、时钟/中断控制)是内核最热的调用路径之一。静态链接后可以被内联、被优化,不必再通过导入表间接跳转。 - 消除一个攻击面。 HAL 作为一个独立映像,理论上可以被单独替换或挂钩。合并后不再有这个独立入口。
- 简化映像加载。 加载器少加载、少重定位、少校验一个映像。
那么 BCD 里的 hal元素(0x22000012)还有用吗? 有。加载器依然会按这个元素加载文件(保持行为兼容、也方便极端情况下的替换调试),但被加载的那个文件不再包含 HAL 主体代码。
这一修正在实践中的意义:
- 当你看到某些资料说"ntoskrnl.exe 和 hal.dll 是两个独立的内核组件"时,要知道那是 2019 年以前的模型。
- 当你用
lm命令在 WinDbg 里看已加载模块时,会看到hal依然作为一个模块存在------那是内核为兼容旧驱动(驱动可能导入 HAL 导出函数)而保留的导出转发层。 - 恶意软件分析时,如果你在内存里发现一个"看起来像 HAL 但不在预期位置"的模块,那才是可疑的;正常的 HAL 代码已经在 ntoskrnl 的
.text段里了。 - 蓝屏分析时,
hal.dll出现在调用栈中不代表 HAL 有问题,它可能只是被转发的内联函数符号。真正要看的是调用栈上更靠近栈顶的第三方驱动。
综上,对现代 Windows 10/11 更准确的描述是:
内核启动:ntoskrnl.exe
├─ 内核层(Kernel):线程调度、中断/异常分发、DPC、自旋锁、多处理器同步
├─ 硬件抽象层(HAL):已静态链接进内核,负责中断控制器、时钟、I/O 端口、多处理器启动等平台差异
└─ 执行体(Executive):对象管理、内存管理、I/O 管理、进程管理、安全引用监视器、PnP/电源、配置管理、缓存管理
(hal.dll 作为兼容存根被加载,提供旧驱动的导入转发)
5.2 入口点:KiSystemStartup
winload 跳转的目标是 ntoskrnl.exe 的入口函数 KiSystemStartup(不同版本中入口名与包装层有差异,这是引导处理器路径的统称)。
此刻的机器状态:
- CPU 处于 64 位长模式,分页已开启(内核自己的 CR3)
- 中断被禁用
- 只有一个处理器在跑(引导处理器 BSP)
- 没有线程调度(调度器数据结构还没建)
- 没有进程对象(连 Idle 都没有)
- 但 LoaderBlock 已经就绪,内存描述符、已加载模块列表、SYSTEM hive 都在
KiSystemStartup 及其后续调用链的骨架:
KiSystemStartup(Prcb / LoaderBlock)
├─ 初始化处理器状态:IDT、GDT、TSS、PCR(Processor Control Region)
├─ HalInitializeProcessor() ← 为当前 CPU 初始化 HAL 侧的 PCR 与处理器间中断向量
├─ KiInitializeKernel() ← 内核初始化主函数(每个 CPU 都会走一遍)
│ ├─ 引导处理器上:初始化共享的内核数据结构、链表头、空闲线程/进程对象
│ ├─ 读取并应用 KASLR 相关的映像基址信息
│ ├─ 检查是否启用虚拟化(BCD 的 hypervisorlaunchtype)与 CPU 是否支持硬件虚拟化
│ ├─ 首次调用:InitBootProcessor() ← 阶段 0 的总调度
│ └─ 后续处理器(AP):只调用 HalInitSystem()
└─ 当前执行流"蜕化"为 Idle 线程(KiIdleLoop)
"蜕化为 Idle 线程" 是一个精妙的设计:内核不需要专门创建一个 Idle 线程再调度过去------它直接让当前这段执行流,在完成初始化后,变成 0 号 CPU 的空闲线程。此后,当没有其他可运行线程时,CPU 就回到这条执行流上,执行 KiIdleLoop(不断执行 halt 指令等待中断)。
5.3 阶段 0(Phase 0):关中断下的骨架搭建
阶段 0 的核心特征:中断关闭、单处理器、不提供任何可被中断/重入的服务。 它的目标只有一个------建立"足以运行阶段 1"的最小数据结构集。
主调度函数是 InitBootProcessor()(早期版本为 ExpInitializeExecutive())。它按大致如下顺序推进:
(1) HalInitSystem(0)。 让 HAL 在任何进一步初始化之前拿到控制权。它的职责包括:为每个 CPU 准备中断控制器、配置用于 CPU 时间记账的间隔时钟定时器中断。注意 HAL 初始化分两次调用,HalInitSystem(0) 是阶段 0 版本,HalInitSystem(1) 在阶段 1 中调用以开启中断。
(2)处理 burnmemory启动选项。 若 BCD 中设置了 truncatememory(0x15000007),丢弃指定数量的物理内存。这是内核开发者用来模拟低内存环境的手段。
(3)NLS(国家语言支持)表的早期初始化。 仅初始化到足以做 Unicode ↔ ANSI/OEM 转换的程度------因为后续要用它来打印调试字符串、处理路径。
(4)建立系统根路径与崩溃字符串缓存。 在内核映像中搜索蓝屏所需的消息字符串位置并缓存下来。原因很实际:崩溃时系统状态不可靠,不能指望再去查表。
(5)初始化配额机制、读取"控制向量"。 控制向量是 HKLM\\SYSTEM\\CurrentControlSet\\Control 下的 150+ 个内核调优项,包括授权数据、版本信息、以及各种系统行为开关。
(6)调用各子系统的阶段 0 初始化:

(7)关键一步:创建 System 进程与阶段 1 线程。 进程管理器创建了名为 Idle 的初始进程对象,然后创建 System 进程(PID 4)和一个系统线程,线程入口是 Phase1Initialization。这个线程此时还不会运行------因为中断仍然关闭,调度器还不能抢占当前执行流。
(8)回到 KiInitializeKernel,分配 DPC 栈,然后进入 Idle 循环。 一旦进入 Idle 循环,调度器开始工作,那个等待中的 Phase1Initialization 线程就获得了 CPU,阶段 1 开始。
理解阶段 0 → 阶段 1 的切换,是理解"为什么某些东西不能在驱动入口里做"的关键。 阶段 0 时:没有线程调度、没有中断、除了非分页池几乎没有内存服务。所以任何驱动代码(它们最早在阶段 1 才被初始化)都不可能运行在阶段 0。这也是为什么 DriverEntry 里不能假设"系统已经完全就绪"------尤其是不能假设其他驱动已经加载、不能假设分页可用(若驱动被标记为在分页可用前加载)。
5.4 阶段 1(Phase 1):从骨架到可用系统
阶段 1 由 Phase1Initialization 线程驱动,此时中断已可开启、调度器已工作、可以创建线程、可以使用更多内存服务。
其推进顺序(不同版本差异较大,以下为逻辑主线):
Phase1InitializationDiscard:释放内核映像的 INIT 段。内核为了省内存,把只运行一次的初始化代码放在单独的 INIT 段,用完即弃。这也是为什么用 WinDbg 做早期断点时,某些初始化函数"过一会儿就找不到了"。- 提升线程优先级到 31(最高),防止被抢占------初始化不应被打断。
- 建立 NUMA / 处理器组拓扑 :在逻辑处理器与处理器组之间建立优化的映射关系(考虑 NUMA 局部性与距离),除非被 BCD 的
numproc/groupawareness等设置覆盖。 HalInitSystem(1):让 HAL 准备接收设备中断并开启中断。这是系统从"关中断的初始化态"进入"正常可中断运行态"的分界点。- 启动图形界面 :调用启动视频驱动(
bootvid.dll),显示 Windows 启动画面(默认黑底 + 转圈动画)。若使用了quietboot选项则跳过;若启用sos选项,会在屏幕上打印加载的每个驱动与版本信息、处理器数量、内存大小。 - 电源管理初始化 (
PoInitSystem)。 - 初始化系统时间 :调用
HalQueryRealTimeClock()读取 CMOS/RTC,记录为"系统启动时间"。 - 启动其余处理器 :
KeStartAllProcessors()+HalAllProcessorsStarted()。引导处理器通过发送 Startup IPI(SIPI) 逐个唤醒应用处理器(AP),每个 AP 从实模式的一段 trampoline 代码开始,切换到长模式,走各自的KiSystemStartup→HalInitializeProcessor→KiInitializeKernel(非引导路径,只调HalInitSystem)→ 进入 Idle 循环。实际启动的处理器数量受物理数量、SKU 授权、BCD 的numproc/onecpu选项共同限制。 - 对象管理器创建命名空间 :根
\\、\\ObjectTypes、\\Global??,创建\\DosDevices符号链接。 - 执行体创建对象类型:信号量、互斥体、事件、定时器、以及 I/O 管理器的设备、驱动、控制器、适配器、文件对象。
- 内核调试库完成初始化(若之前未触发)。
- 安全引用监视器 创建
\\Security目录,初始化审计结构。 - 创建
\\SystemRoot符号链接。 - 内存管理阶段 1 (
MmInitSystem(1)):创建\\Device\\PhysicalMemorysection 对象,创建系统工作线程,映射 NLS 表到系统空间,映射ntdll.dll到系统地址空间。 - I/O 管理器初始化与启动驱动加载 (
IoInitSystem→IopInitializeBootDrivers):这是阶段 1 中对外表现最明显的一步------按ServiceGroupOrder与每个服务的Group/Tag顺序,逐个初始化 winload 已经加载到内存中的 boot-start 驱动。每个驱动被初始化前,PnP 管理器会先咨询 ELAM 驱动,按其返回的分类决定是否放行(见 4.8 节)。 - 最后:创建会话管理器进程
smss.exe。 内核等待它最多约 5 秒,若smss在此期间崩溃,系统直接 bugcheck(这就是0xc000021a中"关键系统进程终止"的一类典型来源)。
5.5 从内核态到用户态:smss 之后
内核自身并不创建"Windows 桌面"。它只创建第一个用户态进程,之后的所有用户态都由用户态进程自己拉起来。链条如下:
内核 Phase 1
└─ 创建 smss.exe(Session Manager Subsystem,第一个用户态进程,不受会话管理)
├─ 执行 BootExecute:autochk(磁盘检查)、chkdsk 计划任务
├─ 处理 PendingFileRenameOperations(延迟的文件重命名/删除,Windows 更新的核心机制)
├─ 加载剩余注册表 hive(SAM、SECURITY、SOFTWARE、DEFAULT)
├─ 创建 \\KnownDlls、\\DosDevices 等对象目录
├─ 启动子系统:csrss.exe(Client/Server Runtime Subsystem,Win32 子系统用户态部分)
├─ 启动 wininit.exe(前一个会话/系统的初始化进程)
└─ smss 随后自行退出(它是一次性的引导进程)
wininit.exe
├─ 创建 services.exe(SCM,服务控制管理器)
│ └─ 按 Start=2 (AUTO_START) 加载自动启动服务与驱动
├─ 创建 lsass.exe(本地安全认证子系统:认证、LSA 策略、SAM/AD 访问)
├─ 创建 lsm.exe / 会话相关(版本相关)
└─ 创建 winlogon.exe
├─ 加载 LogonUI(凭据提供程序 UI)
├─ 认证成功后,由 userinit.exe 启动用户 shell
└─ explorer.exe(图形 shell)→ 桌面出现
理解这条链的实践价值:
- "explorer 没起来但能看到鼠标" → 问题在 userinit/shell 层,内核是好的。可以用任务管理器
Ctrl+Shift+Esc→ 运行新任务 →explorer.exe。 - "卡在欢迎界面很久" → 常见原因是 SCM 加载某个服务超时(服务启动超时默认 30 秒/个),或组策略脚本、网络位置感知等待网络。
- "登录后又注销" → 典型是 userinit 被恶意软件篡改,或
Shell注册表值被改(检查HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Shell应为explorer.exe)。 smss.exe有多个实例是正常的:一个主实例 + 每个会话一个子实例(子实例启动后会短暂存在)。
5.6 内核阶段的蓝屏定位
一旦进入内核,错误就以**停止码(Stop Code)**的形式呈现。按发生的子阶段归类:

0x7B值得单独说一句,因为它是"装完系统换 SATA 模式"或"新机器首次开机"最常见的蓝屏:
Windows 只会加载"启动所必需的"存储驱动。若安装时是 RAID/RST 模式,注册表里 Start=0 的驱动就是 RAID 驱动;此时把固件改成 AHCI,系统起来后找不到原来的控制器,磁盘栈建不起来,于是 0x7B。
修复办法(离线注入驱动):
# 在 WinRE 中,假设 Windows 在 D:
dism /image:D:\\ /add-driver /driver:E:\\Drivers\\iaStorVD.inf
# 或者启用通用 AHCI 驱动(多数情况已内置):
reg load HKLM\\OFFSYS D:\\Windows\\System32\\config\\SYSTEM
reg add HKLM\\OFFSYS\\ControlSet001\\Services\\msahci /v Start /t REG_DWORD /d 0 /f
reg add HKLM\\OFFSYS\\ControlSet001\\Services\\pciide /v Start /t REG_DWORD /d 0 /f
reg add HKLM\\OFFSYS\\ControlSet001\\Services\\iaStorV /v Start /t REG_DWORD /d 0 /f
reg unload HKLM\\OFFSYS
现代 Intel 平台上更常见的则是 VMD(Volume Management Device)控制器 开关导致的 0x7B------第 11/12 代酷睿及之后的部分笔记本,BIOS 中"Enable VMD controller" 开关一旦改变,就必须重装或注入对应驱动。这是 OEM 预装机器上非常典型的一类"重置 BIOS 后就蓝屏"。
第六章 端到端时间线与状态快照
6.1 完整时间线
把前五章串成一条时间线(典型 UEFI + Windows 10,NVMe SSD,快速启动开启,耗时为常见范围):

6.2 各阶段的"环境状态"对照表
这是理解"为什么某些事只能在某个阶段做"的核心表格:

这张表解释了很多"为什么":
- 为什么 BCD 必须在 FAT 分区上?→ 因为 bootmgfw 阶段只能用固件的 FAT 驱动。
- 为什么不能把 winload.efi 放在 NTFS 分区上?→ 可以,因为 winload 自带 NTFS 读取能力;但 bootmgfw 不行,所以 bootmgfw 与 BCD 必须在 ESP(FAT32)。
- 为什么驱动不能在
DriverEntry里做复杂初始化?→ 因为它们运行在 Phase 1,此时很多子系统才刚初始化。 - 为什么
ExitBootServices之后不能再分配引导服务内存?→ 因为 Boot Services 已经不存在了。 - 为什么内核还能读 UEFI 变量?→ 因为 Runtime Services 通过
SetVirtualAddressMap保留了。
第七章 故障定位手册:从现象到根因
7.1 第一原则:先定位"死在哪一段"
面对一台起不来的机器,最有价值的一步不是急着敲命令,而是判断控制流停在了链路的哪一段。判别依据只有三个:屏幕内容、错误码前缀、以及按键响应。

7.2 引导错误码(0xc0000xxx)决策表
这些是 bootmgfw / winload 阶段抛出的错误,出现在深色背景的 WinRE 风格界面上。它们不是蓝屏,也不是内核错误,处理手段与蓝屏完全不同。

7.3 标准修复流程(UEFI/GPT)
以下流程按"可逆性从高到低、破坏性从低到高"排列。每一步之间都应尝试重启验证,不要一次性全跑完。
准备:进入 WinRE
- 方法一:连续三次强制断电(在 Windows LOGO 出现时长按电源键 5--10 秒,重复三次),第四次开机自动进恢复环境。
- 方法二:从 Windows 安装 U 盘启动 → 语言选择后点"下一页" → 左下角"修复计算机" → 疑难解答 → 高级选项。
- 方法三:正常系统中
设置 → 恢复 → 高级启动 → 立即重新启动,或命令行shutdown /r /o /t 0。
第 1 步:确认磁盘与分区可见
diskpart
list disk
select disk 0
list volume
exit
要识别出来三样东西:
- ESP:FAT32,约 100 MB,通常无盘符。记下卷号。
- Windows 分区:NTFS,容量最大,通常有 Windows 目录。
- (可选)恢复分区:NTFS,约 500 MB--1 GB。
若 list disk 看不到系统盘 → 这是硬件/固件问题,停止软件修复,先解决磁盘可见性(控制器模式、线缆、驱动器本身)。
第 2 步:给 ESP 分配盘符
diskpart
list volume
select volume N ← ESP 的卷号
assign letter=S
exit
第 3 步:检查 BCD 内容
bcdedit /store S:\\EFI\\Microsoft\\Boot\\BCD /enum all /v
重点看:
{bootmgr}的device/path是否正确- Windows Boot Loader 条目的
device与osdevice是否指向真实分区,是否显示为unknown path是否为\\Windows\\system32\\winload.efi
第 4 步:检查文件系统与系统文件
chkdsk C: /f /r
sfc /scannow /offbootdir=C:\\ /offwindir=C:\\Windows
DISM /Image:C:\\ /Cleanup-Image /RestoreHealth
注意 WinRE 中的盘符可能与正常系统不同(常见 C: 变成 D:)。用 diskpart list volume 或 dir 确认,不要凭感觉。
第 5 步:重建引导文件(最有效的一步)
bcdboot C:\\Windows /s S: /f UEFI
这条命令会:复制整套 EFI 引导文件到 ESP、生成新的 BCD、并刷新 NVRAM 启动项。它通常能一次性解决 0xc000000f / 0xc0000225 / 0xc000014c 这一类问题。
第 6 步:顽固 BCD 的处理(保留备份)
cd /d S:\\EFI\\Microsoft\\Boot
attrib BCD -s -h -r
ren BCD BCD.old
bcdboot C:\\Windows /s S: /f UEFI
永远用 ren而不是 del。 BCD.old 保留了原配置,可以在事后离线挂载出来比对,确认"原来的配置到底哪里错了"------这在反复启动失败的场景里是重要的诊断线索。
第 7 步:bootrec 的适用边界
bootrec /fixmbr ← 仅 Legacy/MBR 有意义;UEFI/GPT 上不适用
bootrec /fixboot ← UEFI 环境常报 "Access is denied"(因为 ESP 上没有可写的引导扇区概念)
bootrec /scanos ← 扫描已安装的 Windows,UEFI 上有用
bootrec /rebuildbcd ← 尝试重建 BCD 条目,UEFI 上有用但不如 bcdboot 彻底
很多人不知道的一点 :bootrec /fixmbr 和 /fixboot 是为 Legacy BIOS 设计的。在 UEFI/GPT 机器上,/fixboot 报 "Access is denied" 是正常的、预期的 ,不代表失败。UEFI 场景下的正确工具是 bcdboot。
第 8 步:最后手段(按破坏性递增)
- 系统还原(
rstrui.exe,从 WinRE 运行,不影响个人文件) - 卸载最近的质量更新 / 功能更新
- 重置此电脑 → 保留个人文件
- 全新安装
7.4 几类典型故障的专项处置
案例一:换固态/克隆后起不来(0xc000000e / 0xc000000f)
根因:BCD 中记录的磁盘签名(MBR)或分区 GUID(GPT)与新磁盘不符;或克隆时 ESP 未被正确复制;或新盘 ESP 与旧盘 ESP 冲突。
处置:
# 1) 确认 Windows 分区与 ESP 都在
diskpart → list volume
# 2) 修复分区标识:若 device 显示 unknown,改为 locate
bcdedit /store S:\\EFI\\Microsoft\\Boot\\BCD /set {default} device locate=\\Windows
bcdedit /store S:\\EFI\\Microsoft\\Boot\\BCD /set {default} osdevice locate=\\Windows
# 3) 或直接重建(更干净)
bcdboot C:\\Windows /s S: /f UEFI
# 4) 若机器上有两个 ESP,把多余的那个删掉或确保固件选中正确的那个
案例二:BitLocker 恢复密钥反复出现
根因排序:修改了被 BCD 验证配置文件监视的元素(testsigning、nointegritychecks、nx、debug、hypervisordebug 等);更新了固件而 PCR 配置包含 0/2/4;更换了硬件;TPM 被清除。
处置原则:
# 事前预防:任何会改变度量的操作之前
manage-bde -protectors -disable C:
<做固件更新 / 改 BCD / 换硬件>
manage-bde -protectors -enable C:
# 事后:进入恢复界面输入恢复密钥,系统会自动重新密封 PCR 值
# 查看当前状态
manage-bde -status
manage-bde -protectors -get C:
案例三:双系统后 Windows 起不来(GRUB 菜单里的 Windows 项报 0xc000000f)
根因:os-prober 生成的 chainloader 路径指向了错误的 ESP,或 Windows 更新后 bootmgfw.efi 路径/分区变化。
处置:在 Linux 下确认 /boot/efi/EFI/Microsoft/Boot/bootmgfw.efi 是否真实存在;用 efibootmgr -v 查看 NVRAM 启动项;最稳妥的做法是在固件启动菜单(F8/F12)里直接选 "Windows Boot Manager",绕过 GRUB。
案例四:Intel VMD 导致的 INACCESSIBLE_BOOT_DEVICE(0x7B)
现象:OEM 笔记本(11 代酷睿及之后)在 BIOS 重置、清除 CMOS、或固件更新后,进系统就蓝屏 0x7B。
根因:BIOS 中 "VMD Controller" 开关被改变,而 Windows 只安装了原开关状态下对应的存储驱动。
处置:进 BIOS(开机按 F2)→ Advanced → VMD setup menu → 把 "Enable VMD controller" 改回原来的值 → F10 保存。若不知道原值,两种都试一次。这是纯配置问题,不需要重装。
案例五:更新固件后要求 BitLocker 恢复密钥(且 PCR 含 0/2/4)
根因:PCR 0(固件代码)随固件更新而改变,密封的 VMK 无法解封。
处置:输入恢复密钥进入系统后,用 manage-bde -protectors -get C: 确认 PCR 配置。如果长期困扰,可考虑(在有管理策略支持的环境中)把验证配置改为 7+11;但不要为了省事而关闭 PCR 校验------那等于放弃 TPM 对引导链的保护。
7.5 该看哪些日志

关于 bootstat.dat与"自动修复"的触发机制: Windows 维护一个启动成功/失败计数器。当连续启动失败达到阈值(且 bootstatuspolicy 未被设为 IgnoreAllFailures),引导管理器会转而加载 WinRE。你也可以在正常系统中手动查看与设置:
bcdedit /enum {current} | findstr /i bootstatuspolicy
bcdedit /set {current} bootstatuspolicy IgnoreAllFailures ← 禁止自动进 WinRE(不推荐长期使用)
bcdedit /set {current} bootstatuspolicy DisplayAllFailures ← 每次失败都提示
第八章 安全机制全景:七道防线
把散落在各章的安全机制串起来,Windows 10 的启动链实际上部署了七道防线:
第一道:硬件信任根(Boot Guard / PSP 硬件验证)
CPU 微码与 ACM 在固件执行前验证固件签名。失败 → 变砖或拒绝执行。防的是"刷恶意 BIOS"。
第二道:固件 Secure Boot
PK/KEK/db/dbx 四层密钥体系,验证每一个 EFI 映像。防的是"替换 bootmgfw.efi"。
第三道:Trusted Boot(微软自己的信任链)
bootmgfw 验证 winload.efi,winload 验证所有它加载的驱动(通过 ci.dll)。即便 Secure Boot 关闭,这一层依然工作。防的是"替换 winload 或注入未签名驱动"。
第四道:Measured Boot(TPM 度量)
PCR 0--7(固件)+ 11/12/13/14(Windows)记录所有被加载组件的摘要,事件日志可被远程验证。防的不是攻击本身,而是"攻击后伪装成健康"。
第五道:ELAM(早启动反恶意软件)
在 boot-start 驱动被初始化之前逐个分类(Good/Bad/Unknown),并可按策略阻止。防的是 rootkit 型驱动。
第六道:代码完整性 / HVCI / VBS
运行期持续强制签名策略(WDAC 可进一步限定允许的签发者);基于虚拟化的安全(VBS)把安全敏感的内核代码放到受 hypervisor 保护的隔离区域;HVCI(内存完整性)确保内核代码页不可被写入、不可执行未签名代码。
第七道:BitLocker(数据静态保护 + 引导链密封)
把磁盘密钥与 PCR 值绑定。任何改变引导链的行为都会导致密钥无法解封。防的是"离线攻击"(把硬盘拆下来读数据、或用 U 盘启动改文件)。
这七道防线的关键性质 :它们不是冗余的,而是分层的------每一道防的是不同的攻击路径:

一个常被问的问题:关掉 Secure Boot 会怎样?
- 第二道防线消失:任何 EFI 应用都能被固件加载,包括 bootkit。
- 但后面五道还在:bootmgfw 仍会验证 winload,winload 仍会验证驱动,ELAM 仍在工作,HVCI 仍在工作。
- BitLocker 会立刻要求恢复密钥------因为 PCR 7(Secure Boot 状态)变了。这就是为什么"关 Secure Boot 试试"之后,很多人反而遇到更多麻烦。
第九章 工具箱
9.1 引导修复类

9.2 诊断与取证类

9.3 值得记住的几条命令组合
"我什么都不会,先给我一套能跑的":
diskpart
list volume
select volume
assign letter=S
exit
bcdboot C:\\Windows /s S: /f UEFI
"启动太慢,我想知道慢在哪":
# 查看最近几次启动的总耗时与分阶段耗时
eventvwr.msc
→ 应用程序和服务日志 → Microsoft → Windows → Diagnostics-Performance → 操作
→ 事件 ID 100(启动性能),其中 BootTime / MainPathBootTime 等字段是毫秒数
"我要改 BCD,但不想触发 BitLocker 恢复":
manage-bde -protectors -disable C:
bcdedit /export C:\\BCD.bak
bcdedit /enum all > C:\\bcd-before.txt
<修改>
manage-bde -protectors -enable C:
"机器上有好几个 ESP/启动项,我晕了":
bcdedit /enum firmware # 固件层有哪些启动项
bcdedit /enum all /v # BCD 层有哪些对象与元素
diskpart → list volume # 有几个 FAT32 分区
第十章 常见误解澄清(Q&A)
Q1:UEFI 就是 BIOS 的新名字吗?
不是。BIOS 是一种固件实现;UEFI 是一套接口规范(外加 PI 规范定义内部分阶段流程)。现代"BIOS"通常指的是"符合 UEFI 规范的固件"。日常说"进 BIOS"其实是进固件的设置界面。
Q2:GPT 和 UEFI 必须一起用吗?
规范上不强制:UEFI 也能从 MBR 磁盘启动,Legacy BIOS 也能读 GPT(仅限某些实现)。但Windows 的硬性规则是:UEFI 启动必须配 GPT 磁盘,Legacy 启动必须配 MBR 磁盘。这条规则没有例外,也是"磁盘 0 使用了不支持的分区表类型"这类安装错误的根源。
Q3:ESP 上的 \\EFI\\BOOT\\bootx64.efi是干什么的?
它是固件的默认回退路径:当 NVRAM 中没有任何可用启动项时,固件会尝试在可移动介质上找这个文件。安装介质、PE 工具盘都依赖它。正常安装 Windows 的机器上,这个文件可能存在也可能不存在,不影响正常启动。
Q4: bootmgfw.efi和 bootmgr.efi是同一个东西吗?
在微软的发布体系里,bootmgfw.efi 是"面向固件启动管理器"的引导管理器(放在 ESP),而 bootmgr.efi 是其 EFI 变体的另一份称呼/副本(常见于 \\Windows\\Boot\\EFI\\,用于某些部署与修复路径)。功能上是同一个程序的不同安放位置/命名。真正要区分的是它们与 winload.efi(操作系统加载器)的差别。
Q5:Secure Boot 会阻止我装 Linux 吗?
不会。主流发行版(Ubuntu、Fedora、Debian、openSUSE 等)的 shim 都由微软第三方签名服务签署,Secure Boot 开启时也能正常启动。会被阻止的是未签名的自制引导器,此时需要自己注册密钥或关闭 Secure Boot。
Q6:关 Secure Boot 能让系统变快吗?
几乎不会。签名验证只发生几次,每次几十毫秒。它带来的风险(失去引导链保护、BitLocker 进恢复模式)远大于收益。
Q7:为什么我按 F8 进不了安全模式?
Windows 8 之后默认不再在引导管理器阶段等待 F8。替代方案:① Shift + 重启;② shutdown /r /o /t 0;③ 连续三次强制断电触发 WinRE;④ 把 bootmenupolicy 设为 legacy 恢复旧行为。
Q8:BCD 能用记事本编辑吗?
不能直接编辑------它是二进制的注册表 hive。但可以 reg load 挂载后用注册表编辑器修改。日常使用应当用 bcdedit。
Q9: bootrec /fixboot报 Access is denied 是不是完了?
不是。在 UEFI/GPT 机器上这是预期行为------ESP 上没有 /fixboot 想写的那种引导扇区。UEFI 环境的正确工具是 bcdboot。
Q10:快速启动和休眠是一回事吗?
共用同一个机制(hiberfil.sys + winresume.efi),但保存的内容不同:完整休眠保存内核态 + 全部用户会话;快速启动只保存内核态与驱动状态,用户会话在关机时被完全关闭。所以"快速启动后再开机"你仍需重新登录。
Q11:为什么"重启"能解决而"关机再开"解决不了?
重启走完整冷启动,所有驱动重新初始化;关机再开走快速启动路径,把上一次的内核状态恢复回来,问题被一起恢复。遇到偶发的驱动/设备异常,优先用"重启"而不是"关机再开"。
Q12:hal.dll 丢了系统会怎样?
Windows 10 2004 之后,hal.dll 只是兼容存根,但它仍被加载器按 BCD 的 hal 元素加载。文件缺失会导致加载失败(通常表现为 0xc0000001 或类似)。修复方式:从安装介质/健康机器复制同名文件,或 sfc / DISM 修复。但真正决定系统能否启动的是 ntoskrnl.exe 里已合并的 HAL 代码。
Q13:PCR 7+11 和 PCR 0+2+4+11 哪个更安全?
严格性上 0+2+4+11 更高(度量固件与引导器本体);可用性上 7+11 更好(固件/引导器安全更新不触发恢复)。微软默认选 7+11 是可用性优先的取舍。高安全场景可考虑更严格的配置,但要配套管理固件更新的流程(更新前挂起 BitLocker)。
Q14:我的机器开机要 40 秒,正常吗?
看硬件。NVMe SSD + 现代 CPU,冷启动到登录界面 10--20 秒属正常(固件阶段常占其中一半以上);机械硬盘 40--60 秒也不算异常。要优化,先量化:用事件查看器的 Diagnostics-Performance 事件 100 看 BootTime 分解,再决定是固件阶段(开 Fast Boot、减 Option ROM)还是系统阶段(减启动项、减自动启动服务)的问题。
Q15:双系统一定要关快速启动吗?
强烈建议关。快速启动后 Windows 的 NTFS 卷处于"已挂载"状态(内核认为自己还持有该卷),Linux 再挂载并写入可能造成元数据不一致,尤其在启用 NTFS-3G 写入支持时。同理,双系统共享的 NTFS 数据盘也受影响。
Q16: bcdedit /set testsigning on为什么会触发 BitLocker?
因为 testsigning(0x16000049)被列在 BitLocker 的 BCD 验证配置文件中,修改它改变了度量值。正确做法是先 manage-bde -protectors -disable C:,改完再 -enable。
Q17:删掉 ESP 会怎样?
立刻起不来。ESP 上没有你的个人文件,但它是唯一存放引导管理器与 BCD 的地方。恢复方式:diskpart 重建一个 FAT32 分区(100 MB 足够)→ bcdboot C:\\Windows /s S: /f UEFI。数据都在 Windows 分区里,不会丢。
Q18:UEFI 变量(NVRAM 启动项)会自己坏吗?
会。固件 bug、错误的变量写操作、CMOS 清除、固件更新都可能清空或破坏 NVRAM 启动项。症状是"固件找不到任何可启动项"。因为 Boot#### 里存的是设备路径(含分区 GUID),只要磁盘分区没变,bcdboot 就能重建它。
Q19:Secure Boot 的 2011 证书 2026 年到期后,我的电脑会开不了机吗?
不会开不了机。 已签名的文件仍然有效,固件不会检查证书的"有效期"。影响是安全冻结:将来用 2023 证书签署的新版 bootmgfw.efi、以及新的 dbx 更新,将不被只含 2011 证书的老机器信任。也就是说它无法再获得引导器安全更新。应对方式是确保 db 中包含 2023 系列证书(Windows Update 与固件更新通常会推送)。
Q20:我该不该自己注册 Secure Boot 密钥(PK/KEK/db)?
除非你明确知道自己在做什么(比如要运行自签名的自制引导器),否则不该。自注册密钥后会失去微软签名的第三方引导器兼容性,且不同厂商固件的实现质量参差不齐,容易把机器搞成需要清 CMOS 才能恢复的状态。
附录 A:关键 GUID 速查

附录 B:UEFI 引导期关键路径与文件名

附录 C:错误码全称对照

附录 D:BCD 元素类型码编码速算
元素类型码 = (Class << 28) | (Format << 24) | SubType
Class: 1=Library 2=Application 3=Device 5=OEM
Format: 1=Device 2=String 3=Object 4=ObjectList
5=Integer 6=Boolean 7=IntegerList
例:0x25000020 → Class=2(Application), Format=5(Integer), SubType=0x20 → nx
0x11000001 → Class=1(Library), Format=1(Device), SubType=0x01 → device
0x26000022 → Class=2(Application), Format=6(Boolean), SubType=0x22 → winpe
结语:把链路变成直觉
回到开篇那条链路:
固件初始化 → EFI\\Microsoft\\Boot\\bootmgfw.efi → BCD → winload.efi
→ ntoskrnl.exe(含已合并的 HAL)、hal.dll(兼容存根)
现在再看它,希望它不再是一串名词,而是一组可以被逐条追问的判断:
- 固件为什么能找到那个文件? 因为 NVRAM 的
Boot####里存了一条设备路径,用分区 GUID 而非盘符定位,最后一个节点是文件路径;找不到时固件会回退到\\EFI\\BOOT\\bootx64.efi。 - 它凭什么相信那个文件? 因为 db 里有微软的证书,dbx 里没有这个哈希;验证通过后,它的摘要被扩展进 PCR 4,Secure Boot 状态被扩展进 PCR 7。
- bootmgfw 怎么找到 BCD? 不是配置项告诉它的,是它从"自己被放在哪个分区"反推出来的
\\EFI\\Microsoft\\Boot\\BCD------所以引导管理器和 BCD 必须在同一个 ESP 上。 - winload 为什么能做那么多事? 因为它同时握着固件的 Boot Services(读文件、画屏幕)和微软自己的 NTFS 实现、代码完整性模块、以及 BitLocker 解封能力。
ExitBootServices之后发生了什么不可逆的事? Boot Services 全部失效,内存所有权交给操作系统,运行时服务被搬迁到虚拟地址------从此内核再也不能"回头"用固件的能力,只能通过 Runtime Services 读写变量、复位机器。- 内核为什么需要 LoaderBlock? 因为在它完成 Phase 0 之前,它不知道自己在哪里、内存有多大、加载了哪些驱动、磁盘在哪------LoaderBlock 是它全部的"先天知识"。
- hal.dll 现在是什么? 一个几十 KB 的兼容存根;真正的 HAL 自 Windows 10 2004 起已静态链接进 ntoskrnl.exe。这个变化反映了微软把平台差异收敛、把热路径内联的方向。
最后三条实践建议,按重要性排序:
- 动手改任何东西之前,先做两件事 :
bcdedit /export导出 BCD,manage-bde -protectors -disable C:挂起 BitLocker。这两条能把你从 90% 的"越修越糟"里救出来。 - 永远先判断"死在哪一段",再决定用什么工具 。屏幕上的内容就是最好的证据:固件提示、深色背景白字、蓝屏停止码,分别对应三个完全不同的故障域。用
bcdboot去修一个0x7B蓝屏,是浪费时间。 - UEFI 环境下的第一修复工具是
bcdboot,不是bootrec。后者是为 Legacy BIOS 设计的,在 UEFI 机器上/fixboot报 "Access is denied" 是正常现象,不要被它误导。
Windows 的启动链是三十多年操作系统演进沉积下来的结果------它同时保留着 NT 时代的设计(ARC 路径、ControlSet、阶段 0/阶段 1)、UEFI 时代的规范(设备路径、GOP、Secure Boot)、以及云计算与安全时代的机制(Measured Boot、ELAM、VBS、远程证明)。它不优雅,但它有极强的向后兼容性,而且每一个看起来"多余"的环节,几乎都能追溯到某个具体的历史问题。
理解它,不会让你开机更快。但当它坏掉的那天,你会知道自己该看哪里。