调试Windows11启动过程
本文的我复现《软件调试卷2:Windows平台调试》(以下简称《软件调试》)中相关实验的总结。《软件调试》中调试的是32位系统,我这里使用64位Windows 11系统进行复现。
由于笔者水平十分有限,文中难免出现错误,恳请广大读者斧正。
启动概览
系统上电之后,CPU首先执行固化在系统主板上的固件(firmware)代码,目的是检测和初始化基本硬件。
固件程序分为两种:
- UEFI
- BIOS
这里不多讨论两种固件程序的实现细节和区别,感兴趣的读者可以阅读《x86汇编:从实模式到保护模式》与《Rootkit和Bootkit:现代恶意软件逆向分析和下一代威胁》(以下简称《R&B》)。现在大多使用UEFI。
下图来自《R&B》:

现在我们使用windbg对Windows的启动过程进行调试。
Boot Loader和UEFI
UEFI是整个启动过程的开始。这里不会太深入的讲解UEFI,但是对UEFI有一点了解可以更好的理解接下来的内容。
Boot Loader
首先说说Boot Loader。
boot一词的词源是
Pull oneself up by one's bootstraps,译为"靠自己的靴子拉带把自己提起来"
-
字面意思:想象一个人抓住自己靴子上的带子,试图把自己提离地面。
-
引申含义:这是一个不可能完成的任务,后来被用来形容"不靠外界帮助,完全靠自己完成某事"。
计算机启动的过程非常像那个"提靴子"的动作:
- 硬件通电:CPU 刚通电时是一片空白,它不知道内存怎么用,不知道硬盘在哪里,甚至不知道屏幕怎么亮。
- 极小程序(Bootloader):CPU 只能执行固化在主板上一小块 ROM 里的代码(BIOS/UEFI)。这段代码非常小,功能有限。
- 自我加载 :这段小程序(Bootloader)的唯一任务,就是去硬盘上读取一个稍大一点的程序(如
bootmgr),把它加载到内存里。 - 接力棒 :这个稍大的程序(Bootmgr)再去加载更大的程序(如
winload.efi)。 - 最终启动:最终加载操作系统内核。
计算机必须依靠一个极其微小 的、预先写死的"种子"程序,去一步步把庞大的操作系统"拉"起来。这就像抓住自己的靴带把自己提起来一样,因此这个过程被称为 Booting (引导/启动)。
- UEFI固件执行UEFI平台初始化,执行CPU和芯片组初始化,并加载UEFI平台模块
- UEFI引导管理器枚举外部总线上的设备,加载UEFI设备驱动程序,然后加载引导应用程序
- Windows引导管理器(bootmgfw.efi)加载Windows引导加载程序
- Windows引导加载程序(winload.efi)加载Windows操作系统
步骤一和步骤二的代码驻留在主板的闪存芯片上(SPI闪存),步骤3和步骤4的代码从硬盘驱动器的特殊UEFI分区的文件系统中提取。
**操作系统加载程序实际上依赖于UEFI固件提供的EFI引导服务和EFI运行时服务来引导和管理系统。**这里提到了两个概念:
- EFI引导服务
- EFI运行时服务
在UEFI架构中,EFI引导服务 和EFI运行时服务 是固件提供给操作系统加载器(如bootmgfw.efi)的两套核心API接口,它们的定义与区别如下:
-
EFI 引导服务 (Boot Services) :
这是一套临时性的API接口,存在于系统启动阶段。它的核心任务是协助操作系统加载器(如Windows Boot Manager)完成启动准备工作,包括分配内存、加载驱动、读取文件等。一旦操作系统内核接管控制权,这些服务就用不了了。
-
EFI 运行时服务 (Runtime Services) :
这是一套永久性的API接口,在操作系统运行期间持续有效。它的核心任务是为操作系统提供底层的硬件访问能力,例如读取/设置硬件时钟、电源管理(关机/重启)、以及读写NVRAM中的EFI变量(如BCD配置)。
| 维度 | EFI 引导服务 (Boot Services) | EFI 运行时服务 (Runtime Services) |
|---|---|---|
| 生命周期 | 临时性 。在操作系统启动完成、调用ExitBootServices()后即失效。 |
永久性。从系统加电到关机,全程可用。 |
| 主要功能 | 内存分配、驱动加载、文件系统访问、事件与定时器。 | 时间管理、电源管理、EFI变量读写。 |
| 访问权限 | 仅操作系统加载器(如Bootmgr)在启动早期可用。 | 操作系统内核及驱动在运行期间均可调用。 |
| 设计目的 | 确保操作系统能安全、独立地接管硬件控制权,避免固件干扰。 | 提供标准化的底层硬件接口,屏蔽硬件差异。 |
使Windows在启动时断下
要调试win11的启动,首先我们要让Win11在启动的瞬间停下来,将控制权移交给调试器。
bcdedit的原理
为了达到这个目的,我们需要使用bcdedit.exe程序,它的全称为Boot Configuration Data Editor(启动配置数据编辑器)。它是一个命令行工具,用来修改硬盘上的一个数据库文件(BCD文件)。这个数据库里存放着Windows启动所需的参数(配置)。
BCD文件存放在EFI系统分区(ESP)中。我们无法在Windows资源管理器中看到它,因为它位于一个隐藏的、未分配盘符的特殊分区里(如下图):

当运行 bcdedit 修改了配置后,系统下次启动时就会按照修改后的BCD文件执行启动。
使用bcdedit修改BCD数据库文件
在《软件调试》和大部分资料中,都是给出下面的命令启用bootmgr和winload的调试引擎:
powershell
bcdedit /set {bootmgr} bootdebug on
经过实验证明,在win11中大概是行不通的。在win11中执行上述命令之后,大概率会得到一行报错:
txt
指定的设置命令无效。
运行 "bcdedit /?" 获得命令行帮助。
参数错误
参考微软相关文档以及实验证明,以一般的方式(管理员命令行)运行bcdedit程序,只有下面这条指令可以开启调试引擎,并且开启的还不是bootmgr的,而是bootmgr下一阶段的winload:
powershell
bcdedit /bootdebug on
为什么不能开启bootmgr的调试引擎呢?还是翻阅微软的文档:https://learn.microsoft.com/zh-cn/windows-hardware/drivers/devtest/bcdedit--debug
里面提到:
设置 BCDEdit 选项之前,可能需要禁用或暂停计算机上的 BitLocker 和安全启动。
我们遇到的 bcdedit /set {bootmgr} bootdebug on 报错,并非语法错误,而是 Windows 11 的 UEFI 安全机制在作祟。
在 Windows 11 中,BCD 存储被底层固件(UEFI)锁定了。任何试图直接修改 {bootmgr} 标识符的操作都会被系统拦截,导致"参数错误"。因此,调试器只能在 winload.efi 阶段才成功接管,完美错过了 bootmgr 阶段。
既然无法在系统运行时修改,我们必须**重启进入"高级启动模式"**来绕过这个锁。请按以下步骤操作:

命令的含义不多解释,做过Windows内核驱动开发的都懂。如果先前windbg是可以在Windows内核中断下来,后三条指令应该是可以不执行的。
现在重启虚拟机,虚拟机屏幕左上角中会一闪而过类似Windows bootmgr的字样(速度过快我没有截图下来),接着虚拟机黑屏,中断运行,WinDbg 就在 bootmgr 阶段成功断下了:


虚拟机启动后会等待调试器的连接,如果不需要进行调试了使用下面的命令关闭对bootmgr的调试引擎:

bootmgfw.efi
这是Windows启动最早的代码,UEFI就是将控制权移交给该模块的。bootmgfw被称为Windows引导管理器(参考《R&B》)。
先前的{bootmgr}(BCD 标识符)是一个逻辑概念,它是 BCD 配置数据库里的一个条目。而bootmgfw.efi(物理文件):是实体,它是被加载到内存中运行的代码本身。
txt
kd> lmDvm bootmgfw
Browse full module list
start end module name
00000000`10000000 00000000`10327000 bootmgfw (pdb symbols)
Loaded symbol image file: bootmgfw.efi
Image path: bootmgfw.efi
Image name: bootmgfw.efi
Browse all global symbols functions data Symbol Reload
Image was built with /Brepro flag.
Timestamp: 47A4166B (This is a reproducible build file hash, not a timestamp)
CheckSum: 002DEFCB
ImageSize: 00327000
Mapping Form: Loaded
File version: 10.0.28000.317
Product version: 10.0.28000.317
File flags: 0 (Mask 3F)
File OS: 40004 NT Win32
File type: 1.0 App
File date: 00000000.00000000
Translations: 0409.04b0
Information from resource tables:
CompanyName: Microsoft Corporation
ProductName: Microsoft® Windows® Operating System
InternalName: bootmgr.exe
OriginalFilename: bootmgr.exe
ProductVersion: 10.0.28000.317
FileVersion: 10.0.28000.317 (WinBuild.160101.0800)
FileDescription: Boot Manager
LegalCopyright: © Microsoft Corporation. All rights reserved.
bootmgfw.efi保存在\EFI\Microsoft\Boot\bootmgfw.efi中,简单来说就是在EFI系统分区(ESP)中。
还记得前面提到的UEFI保存在哪里吗?保存在SPI中。
有人可能会问,为什么不将bootmgfw.efi保存在C盘中。这是因为UEFI 固件(BIOS)启动电脑时,只能读取 FAT32 格式的分区。而Windows 系统盘(C盘)通常是 NTFS 格式,UEFI 读不懂。系统盘存放真正的 Windows 内核和系统文件。
txt
kd> k
# Child-SP RetAddr Call Site
00 00000000`0ffc95d8 00000000`101e4340 bootmgfw!DebugService2+0x5
01 00000000`0ffc95e0 00000000`10226be5 bootmgfw!DbgLoadImageSymbols+0x70
02 00000000`0ffc9630 00000000`102271a3 bootmgfw!BlBdStart+0x1c9
03 00000000`0ffc9700 00000000`1019f6d8 bootmgfw!BlBdInitialize+0x3ff
04 00000000`0ffc9880 00000000`1019d4f4 bootmgfw!ReinitializeLibrary+0xd4
05 00000000`0ffc98b0 00000000`1003f7eb bootmgfw!BlInitializeLibrary+0x54
06 00000000`0ffc98e0 00000000`1003f0ba bootmgfw!BmMain+0x5d3
07 00000000`0ffc9b10 00000000`0ffcd4ac bootmgfw!EfiEntry+0x6a
08 00000000`0ffc9b50 00000000`00000000 0xffcd4ac
我们以栈回溯为线索看看bootmgfw.efi做了什么。
EfiEntry
**EFI引导管理器在内存中创建bootmgfw.efi的运行时映像。该模块加载后,UEFI固件引导管理器跳转到bootmgfw.efi的入口点:EfiEntry。**这是操作系统引导过程的开始。
EfiEntry原型如下(由UEFI标准规范得到):
cpp
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle, // 应用程序UEFI映像的句柄,代表bootmgfw.efi自己
IN EFI_SYSTEM_TABLE *SystemTable // UEFI系统表的指针
);
EfiEntry是编译链接时的符号名,源码中通常叫 UefiMain或 ShellAppMain
还记得在Boot Loader一节中提到的UEFI的两种服务吗?在bootmgfw.efi的EfiEntry函数中,通过EFI_SYSTEM_TABLE获取这两套服务的指针。引导服务负责将winload.efi加载到内存并读取BCD配置,而运行时服务则确保Windows在启动后仍能通过标准接口与固件交互。
EfiEntry函数引用bootmgfw.efi,为winlaod.efi配置UEFI固件回调,这些回调连接winload.efi与UEFI运行时服务。
结合栈回溯,看看如何调用EfiEntry:
txt
kd> ub 0xffcd4ac
00000000`0ffcd48a 0f94c2 sete dl
00000000`0ffcd48d 8d541207 lea edx,[rdx+rdx+7]
00000000`0ffcd491 4c89e1 mov rcx,r12
00000000`0ffcd494 e861f80000 call 00000000`0ffdccfa
00000000`0ffcd499 41c644241801 mov byte ptr [r12+18h],1
00000000`0ffcd49f 498b542438 mov rdx,qword ptr [r12+38h]
00000000`0ffcd4a4 4c89e9 mov rcx,r13
00000000`0ffcd4a7 41ff542420 call qword ptr [r12+20h] ;这里应该是call EfiEntry?
kd> u 0xffcd4ac
00000000`0ffcd4ac 49898424a8000000 mov qword ptr [r12+0A8h],rax
00000000`0ffcd4b4 e8b5940000 call 00000000`0ffd696e
00000000`0ffcd4b9 498b9424a8000000 mov rdx,qword ptr [r12+0A8h]
00000000`0ffcd4c1 4531c9 xor r9d,r9d
00000000`0ffcd4c4 4531c0 xor r8d,r8d
00000000`0ffcd4c7 4c89e9 mov rcx,r13
00000000`0ffcd4ca e894fdffff call 00000000`0ffcd263
00000000`0ffcd4cf 498b8c24a0000000 mov rcx,qword ptr [r12+0A0h]
000000000ffcd4a7应该就是调用EfiEntry(观察传参和返回地址)。
ImageHandle参数指向bootmgfw.efi模块,该参数复杂继续引导过程并调用winload.efi。Systemtable指向UEFI配置表(EFI_SYSTEM_TABLE)的指针,它是 UEFI 应用程序与硬件固件之间沟通的唯一桥梁,其结构如下:

看看EfiEntry的反汇编:
txt
kd> uf bootmgfw!EfiEntry
bootmgfw!EfiEntry:
00000000`1003f050 48895c2408 mov qword ptr [rsp+8],rbx
00000000`1003f055 57 push rdi
00000000`1003f056 4883ec30 sub rsp,30h
00000000`1003f05a 0f1005a74ffdff movups xmm0,xmmword ptr [bootmgfw!EfiBootmgrDbxSvnGuid (00000000`10014008)]
00000000`1003f061 488bda mov rbx,rdx
00000000`1003f064 488bf9 mov rdi,rcx
00000000`1003f067 41b800000700 mov r8d,70000h
00000000`1003f06d 488d542420 lea rdx,[rsp+20h]
00000000`1003f072 488bcb mov rcx,rbx
00000000`1003f075 f30f7f442420 movdqu xmmword ptr [rsp+20h],xmm0
00000000`1003f07b e8643d0900 call bootmgfw!DbxSvnCheck (00000000`100d2de4)
00000000`1003f080 4885c0 test rax,rax
00000000`1003f083 790f jns bootmgfw!EfiEntry+0x44 (00000000`1003f094) Branch
bootmgfw!EfiEntry+0x35:
00000000`1003f085 48b90e00000000000080 mov rcx,800000000000000Eh
00000000`1003f08f 483bc1 cmp rax,rcx
00000000`1003f092 752d jne bootmgfw!EfiEntry+0x71 (00000000`1003f0c1) Branch
bootmgfw!EfiEntry+0x44:
00000000`1003f094 488bd3 mov rdx,rbx
00000000`1003f097 c605d3e92b0001 mov byte ptr [bootmgfw!DbxSvnChecked (00000000`102fda71)],1
00000000`1003f09e 488bcf mov rcx,rdi
00000000`1003f0a1 e8da600100 call bootmgfw!EfiInitCreateInputParametersEx (00000000`10055180) ;关键调用
00000000`1003f0a6 4885c0 test rax,rax
00000000`1003f0a9 7507 jne bootmgfw!EfiEntry+0x62 (00000000`1003f0b2) Branch
bootmgfw!EfiEntry+0x5b:
00000000`1003f0ab b80d0000c0 mov eax,0C000000Dh
00000000`1003f0b0 eb08 jmp bootmgfw!EfiEntry+0x6a (00000000`1003f0ba) Branch
bootmgfw!EfiEntry+0x62:
00000000`1003f0b2 488bc8 mov rcx,rax
00000000`1003f0b5 e85e010000 call bootmgfw!BmMain (00000000`1003f218) ;关键调用
bootmgfw!EfiEntry+0x6a:
00000000`1003f0ba 8bc8 mov ecx,eax
00000000`1003f0bc e89f6d0100 call bootmgfw!EfiGetEfiStatusCode (00000000`10055e60)
bootmgfw!EfiEntry+0x71:
00000000`1003f0c1 488b5c2440 mov rbx,qword ptr [rsp+40h]
00000000`1003f0c6 4883c430 add rsp,30h
00000000`1003f0ca 5f pop rdi
00000000`1003f0cb c3 ret
上面有两个关键调用:EfiInitCreateInputParametersEx和BmMain。
EfiInitCreateInputParametersEx
它有两个作用:
- 将EfiEntry参数转化为bootmgfw.efi所需要的格式。UEFI 固件传给 EfiEntry的参数是通用的(比如 ImageHandle和 SystemTable)。这个函数负责把这些底层参数,转换成 Bootmgr 内部结构体(如 BL_APPLICATION_ENTRY)所需的格式。
- 它会调用内部函数(如 EfiInitpCreateApplicationEntry),去扫描并读取BCD文件。它会根据 BCD 中的配置(比如你之前设置的 bootdebug on),初始化引导环境。
BmMain
BmMain 是 Windows 引导管理器(Bootmgfw)的核心 。BmMain 的主要工作是读取 BCD 配置,并决定下一步该做什么:
-
处理休眠:
- 检查 :硬盘上是否有休眠文件(
hiberfil.sys)? - 执行 :如果有,就加载
winresume.efi恢复系统,直接唤醒,跳过后续所有步骤。
- 检查 :硬盘上是否有休眠文件(
-
显示启动菜单:
- 检查:BCD 中是否配置了多个系统或 F8 调试菜单?
- 执行 :如果是,就在屏幕上显示启动菜单,等待用户选择。
-
处理错误:
- 检查:之前的启动是否失败过?
- 执行:如果是,进入**"自动修复"**模式(Windows Recovery Environment)。
-
加载内核:
- 如果上述情况都不满足 ,就进入正常启动 流程:
a. 从 BCD 中读取默认启动项
b. 加载对应的操作系统加载器(winload.efi)
c. 将控制权移交给它
- 如果上述情况都不满足 ,就进入正常启动 流程:
BmMain 执行路径如下:
text
BmMain
└──> BlInitializeLibrary // 初始化基础库
└──> ReinitializeLibrary // 重新初始化库
└──> BlBdInitialize // 初始化引导调试器
└──> BlBdStart // 启动调试器
└──> DbgLoadImageSymbols
└──> DebugService2
BmMain 是 Windows 启动过程中最后一段通用代码 ,之后的所有代码(winload.efi, ntoskrnl.exe)都高度依赖具体的 Windows 版本。它是连接"firmware"和"Windows"的桥梁。
BmMain函数会检查上次启动是否失败,如果失败就进入"自动修复"或"启动恢复"模式,尝试修复问题。这是通过检查BCD中一个特殊的标志位(LastBootSucceeded)来判断上去启动是否成功的。
LastBootSucceeded这个标志很有用。比如我们开发了一个开机自启的驱动程序,但是这个驱动程序存在缺陷会导致系统启动失败。那么我们就可以在驱动中检查LastBootSucceeded,如果上一次启动失败,我们的驱动就先不加载。这样可以让系统正常启动,便于排查启动失败的原因。
随后Bootmgfw会寻找WinLoad.exe文件,并校验文件的完整性,将其加载。BootMgfw还会做更新GDT、IDT,随后用调试平台的相关控制权移交给WinLoad。
**就在bootmgfw将控制权交给winload之前,其会执行一项重要的操作:开启并初始化分页模式。**具体是怎么开启的可以看我的这篇博客:https://blog.csdn.net/Oorchi/article/details/148702135。但是这篇博客是在32位环境下分析的,在64位环境下,保护模式是UEFI固件在bootmgfw被加载之前就开启了。UEFI 固件在初始化过程中,自己就会切换到保护模式。
WinLoad.efi
winload主要任务是把操作系统内核加载到内存中。winload.efi是连接固件(UEFI)与 Windows 内核(ntoskrnl.exe)的桥梁。它的核心任务不是运行系统,而是为内核运行准备必要的环境。
开启winload.efi断下来时,回溯一下:
txt
winload!DebugService2+0x5:
00000000`00d43185 cc int 3
kd> k
# Child-SP RetAddr Call Site
00 00000000`001a7b88 00000000`00b0166e winload!DebugService2+0x5
01 00000000`001a7b90 00000000`00cc99f0 winload!DbgLoadImageSymbols+0x62
02 00000000`001a7be0 00000000`00cc9fbf winload!BlBdStart+0x1c8
03 00000000`001a7cb0 00000000`00c39e41 winload!BlBdInitialize+0x40b
04 00000000`001a7e30 00000000`00c383ac winload!InitializeLibrary+0x4bd
05 00000000`001a7ed0 00000000`00aac1ab winload!BlInitializeLibrary+0x58
06 00000000`001a7f00 00000000`102a0d66 winload!OslMain+0x10b
07 00000000`001a7fe0 00000000`00000000 0x102a0d66
OslMain是winload的主要入口函数,相当于main()。- 它调用了
BlInitializeLibrary来初始化系统库。 BlInitializeLibrary:Boot Library的初始化入口。InitializeLibrary:更底层的库初始化,为后续操作(如调试、内存管理)做准备。BlBdInitialize:初始化启动调试引擎(Boot Debugger)。BlBdStart:启动调试通道,建立与主调试器(WinDbg)的连接。DbgLoadImageSymbols:加载镜像的调试符号(此时可能是 winload 自身)。DebugService2:一个内核调试服务函数,最终触发了int 3中断。
看看winload.efi模块:
txt
kd> lmDvm winload
Browse full module list
start end module name
00000000`00aab000 00000000`00e1b000 winload (pdb symbols) d:\windbgsymbols\winload_prod.pdb\B863EB290D0DE876EE414C943E2DE1542\winload_prod.pdb
Loaded symbol image file: winload.efi
Image path: winload.efi
Image name: winload.efi
Browse all global symbols functions data Symbol Reload
Image was built with /Brepro flag.
Timestamp: 1500371F (This is a reproducible build file hash, not a timestamp)
CheckSum: 00338E28
ImageSize: 00370000
Mapping Form: Loaded
File version: 10.0.26100.8036
Product version: 10.0.26100.8036
File flags: 0 (Mask 3F)
File OS: 40004 NT Win32
File type: 1.0 App
File date: 00000000.00000000
Translations: 0409.04b0
Information from resource tables:
CompanyName: Microsoft Corporation
ProductName: Microsoft® Windows® Operating System
InternalName: osloader.exe
OriginalFilename: osloader.exe
ProductVersion: 10.0.26100.8036
FileVersion: 10.0.26100.8036 (WinBuild.160101.0800)
FileDescription: OS Loader
LegalCopyright: © Microsoft Corporation. All rights reserved.
该模块最主要的工作就是加载各种模块到内存中:
-
加载内核:将 ntoskrnl.exe(内核文件)和 hal.dll(硬件抽象层)读入内存。
-
加载驱动:扫描注册表,将启动类型(Start值为 0)的驱动程序加载到内存。
-
传递数据:准备 LOADER_PARAMETER_BLOCK结构体,这个结构体包含了内存布局、ACPI 表等关键信息。
txt
kd> uf OslpMain
winload!OslpMain:
00000000`00ab377c 48895c2408 mov qword ptr [rsp+8],rbx
00000000`00ab3781 4889742418 mov qword ptr [rsp+18h],rsi
00000000`00ab3786 55 push rbp
00000000`00ab3787 488dac2460f6ffff lea rbp,[rsp-9A0h]
00000000`00ab378f 4881eca00a0000 sub rsp,0AA0h
00000000`00ab3796 488bd9 mov rbx,rcx
00000000`00ab3799 c685b809000000 mov byte ptr [rbp+9B8h],0
00000000`00ab37a0 488d4c2470 lea rcx,[rsp+70h]
00000000`00ab37a5 33d2 xor edx,edx
00000000`00ab37a7 41b8300a0000 mov r8d,0A30h
00000000`00ab37ad e8ce132a00 call winload!memset (00000000`00d54b80)
00000000`00ab37b2 be10000000 mov esi,10h
00000000`00ab37b7 4c8d05b2872d00 lea r8,[winload!SymCryptTestKey32 (00000000`00d8bf70)]
00000000`00ab37be 448bce mov r9d,esi
00000000`00ab37c1 488d1538c72a00 lea rdx,[winload!SymCryptAesBlockCipher_Fast (00000000`00d5ff00)]
00000000`00ab37c8 488d4c2470 lea rcx,[rsp+70h]
00000000`00ab37cd e872062200 call winload!SymCryptGcmExpandKey (00000000`00cd3e44)
00000000`00ab37d2 85c0 test eax,eax
00000000`00ab37d4 0f852d010000 jne winload!OslpMain+0x18b (00000000`00ab3907) Branch
winload!OslpMain+0x5e:
00000000`00ab37da 4889742448 mov qword ptr [rsp+48h],rsi
00000000`00ab37df 488d442453 lea rax,[rsp+53h]
00000000`00ab37e4 4889442440 mov qword ptr [rsp+40h],rax
00000000`00ab37e9 448d46fc lea r8d,[rsi-4]
00000000`00ab37ed 48c744243803000000 mov qword ptr [rsp+38h],3
00000000`00ab37f6 488d442450 lea rax,[rsp+50h]
00000000`00ab37fb 4889442430 mov qword ptr [rsp+30h],rax
00000000`00ab3800 488d1579872d00 lea rdx,[winload!SymCryptTestKey32+0x10 (00000000`00d8bf80)]
00000000`00ab3807 488d0582872d00 lea rax,[winload!SymCryptTestMsg3 (00000000`00d8bf90)]
00000000`00ab380e 4533c9 xor r9d,r9d
00000000`00ab3811 4889442428 mov qword ptr [rsp+28h],rax
00000000`00ab3816 488d4c2470 lea rcx,[rsp+70h]
00000000`00ab381b 488364242000 and qword ptr [rsp+20h],0
00000000`00ab3821 e84a0b2200 call winload!SymCryptGcmEncrypt (00000000`00cd4370)
00000000`00ab3826 448d4603 lea r8d,[rsi+3]
00000000`00ab382a 488d15077e2d00 lea rdx,[winload!`string'+0x48 (00000000`00d8b638)]
00000000`00ab3831 488d4c2450 lea rcx,[rsp+50h]
00000000`00ab3836 e8950f2a00 call winload!memcmp (00000000`00d547d0)
00000000`00ab383b 85c0 test eax,eax
00000000`00ab383d 0f85cf000000 jne winload!OslpMain+0x196 (00000000`00ab3912) Branch
winload!OslpMain+0xc7:
00000000`00ab3843 4889742448 mov qword ptr [rsp+48h],rsi
00000000`00ab3848 488d442453 lea rax,[rsp+53h]
00000000`00ab384d 4889442440 mov qword ptr [rsp+40h],rax
00000000`00ab3852 448d46fc lea r8d,[rsi-4]
00000000`00ab3856 48c744243803000000 mov qword ptr [rsp+38h],3
00000000`00ab385f 488d442450 lea rax,[rsp+50h]
00000000`00ab3864 4889442430 mov qword ptr [rsp+30h],rax
00000000`00ab3869 488d1510872d00 lea rdx,[winload!SymCryptTestKey32+0x10 (00000000`00d8bf80)]
00000000`00ab3870 488d442450 lea rax,[rsp+50h]
00000000`00ab3875 4533c9 xor r9d,r9d
00000000`00ab3878 4889442428 mov qword ptr [rsp+28h],rax
00000000`00ab387d 488d4c2470 lea rcx,[rsp+70h]
00000000`00ab3882 488364242000 and qword ptr [rsp+20h],0
00000000`00ab3888 e8c70c2200 call winload!SymCryptGcmDecrypt (00000000`00cd4554)
00000000`00ab388d 85c0 test eax,eax
00000000`00ab388f 756b jne winload!OslpMain+0x180 (00000000`00ab38fc) Branch
winload!OslpMain+0x115:
00000000`00ab3891 0fb7442450 movzx eax,word ptr [rsp+50h]
00000000`00ab3896 663b05f3862d00 cmp ax,word ptr [winload!SymCryptTestMsg3 (00000000`00d8bf90)]
00000000`00ab389d 755d jne winload!OslpMain+0x180 (00000000`00ab38fc) Branch
winload!OslpMain+0x123:
00000000`00ab389f 8a442452 mov al,byte ptr [rsp+52h]
00000000`00ab38a3 3a05e9862d00 cmp al,byte ptr [winload!SymCryptTestMsg3+0x2 (00000000`00d8bf92)]
00000000`00ab38a9 7551 jne winload!OslpMain+0x180 (00000000`00ab38fc) Branch
winload!OslpMain+0x12f:
00000000`00ab38ab 488d95b8090000 lea rdx,[rbp+9B8h]
00000000`00ab38b2 488bcb mov rcx,rbx
00000000`00ab38b5 e82a080000 call winload!OslPrepareTarget (00000000`00ab40e4)
00000000`00ab38ba 8bd8 mov ebx,eax
00000000`00ab38bc 85c0 test eax,eax
00000000`00ab38be 7810 js winload!OslpMain+0x154 (00000000`00ab38d0) Branch
winload!OslpMain+0x144:
00000000`00ab38c0 80bdb809000000 cmp byte ptr [rbp+9B8h],0
00000000`00ab38c7 7407 je winload!OslpMain+0x154 (00000000`00ab38d0) Branch
winload!OslpMain+0x14d:
00000000`00ab38c9 e892260100 call winload!OslExecuteTransition (00000000`00ac5f60)
00000000`00ab38ce 8bd8 mov ebx,eax
winload!OslpMain+0x154:
00000000`00ab38d0 e8cff00100 call winload!OslVsmScrubSecrets (00000000`00ad29a4)
00000000`00ab38d5 48833db392340000 cmp qword ptr [winload!EfiBS (00000000`00dfcb90)],0
00000000`00ab38dd 7505 jne winload!OslpMain+0x168 (00000000`00ab38e4) Branch
winload!OslpMain+0x163:
00000000`00ab38df e884c10300 call winload!BlFwReboot (00000000`00aefa68)
winload!OslpMain+0x168:
00000000`00ab38e4 4c8d9c24a00a0000 lea r11,[rsp+0AA0h]
00000000`00ab38ec 8bc3 mov eax,ebx
00000000`00ab38ee 498b5b10 mov rbx,qword ptr [r11+10h]
00000000`00ab38f2 498b7320 mov rsi,qword ptr [r11+20h]
00000000`00ab38f6 498be3 mov rsp,r11
00000000`00ab38f9 5d pop rbp
00000000`00ab38fa c3 ret
winload!OslpMain+0x180:
00000000`00ab38fc b9326d6367 mov ecx,67636D32h
00000000`00ab3901 e86e411800 call winload!SymCryptFatal (00000000`00c37a74)
00000000`00ab3906 cc int 3
winload!OslpMain+0x18b:
00000000`00ab3907 b9306d6367 mov ecx,67636D30h
00000000`00ab390c e863411800 call winload!SymCryptFatal (00000000`00c37a74)
00000000`00ab3911 cc int 3
winload!OslpMain+0x196:
00000000`00ab3912 b9316d6367 mov ecx,67636D31h
00000000`00ab3917 e858411800 call winload!SymCryptFatal (00000000`00c37a74)
00000000`00ab391c cc int 3
00000000`00ab391d cc int 3
00000000`00ab391e cc int 3
00000000`00ab391f cc int 3
00000000`00ab3920 48895c2408 mov qword ptr [rsp+8],rbx
00000000`00ab3925 48896c2418 mov qword ptr [rsp+18h],rbp
00000000`00ab392a 56 push rsi
00000000`00ab392b 57 push rdi
00000000`00ab392c 4156 push r14
00000000`00ab392e 4883ec20 sub rsp,20h
00000000`00ab3932 4533f6 xor r14d,r14d
00000000`00ab3935 488be9 mov rbp,rcx
00000000`00ab3938 4c89742448 mov qword ptr [rsp+48h],r14
00000000`00ab393d e802461800 call winload!BlGetApplicationEntry (00000000`00c37f44)
00000000`00ab3942 4c8d442448 lea r8,[rsp+48h]
00000000`00ab3947 ba02000022 mov edx,22000002h
00000000`00ab394c 488b4818 mov rcx,qword ptr [rax+18h]
00000000`00ab3950 e8c3511800 call winload!BlGetBootOptionString (00000000`00c38b18)
00000000`00ab3955 8bd8 mov ebx,eax
00000000`00ab3957 85c0 test eax,eax
00000000`00ab3959 0f888d000000 js winload!OslpInitializeSystemRoot+0xcc (00000000`00ab39ec) Branch
winload!OslpInitializeSystemRoot+0x3f:
00000000`00ab395f 488b7c2448 mov rdi,qword ptr [rsp+48h]
00000000`00ab3964 4883c8ff or rax,0FFFFFFFFFFFFFFFFh
winload!OslpInitializeSystemRoot+0x48:
00000000`00ab3968 48ffc0 inc rax
00000000`00ab396b 6644393447 cmp word ptr [rdi+rax*2],r14w
00000000`00ab3970 75f6 jne winload!OslpInitializeSystemRoot+0x48 (00000000`00ab3968) Branch
winload!OslpInitializeSystemRoot+0x52:
00000000`00ab3972 66837c47fe5c cmp word ptr [rdi+rax*2-2],5Ch
00000000`00ab3978 744e je winload!OslpInitializeSystemRoot+0xa8 (00000000`00ab39c8) Branch
winload!OslpInitializeSystemRoot+0x5a:
00000000`00ab397a 488d7002 lea rsi,[rax+2]
00000000`00ab397e 488d0c36 lea rcx,[rsi+rsi]
00000000`00ab3982 e8b9521c00 call winload!BlMmAllocateHeap (00000000`00c78c40)
00000000`00ab3987 48890562973400 mov qword ptr [winload!OslSystemRoot (00000000`00dfd0f0)],rax
00000000`00ab398e 4885c0 test rax,rax
00000000`00ab3991 7507 jne winload!OslpInitializeSystemRoot+0x7a (00000000`00ab399a) Branch
winload!OslpInitializeSystemRoot+0x73:
00000000`00ab3993 bb170000c0 mov ebx,0C0000017h
00000000`00ab3998 eb52 jmp winload!OslpInitializeSystemRoot+0xcc (00000000`00ab39ec) Branch
winload!OslpInitializeSystemRoot+0x7a:
00000000`00ab399a 4c8bc7 mov r8,rdi
00000000`00ab399d 488bd6 mov rdx,rsi
00000000`00ab39a0 488bc8 mov rcx,rax
00000000`00ab39a3 e898262900 call winload!wcscpy_s (00000000`00d46040)
00000000`00ab39a8 488b0d41973400 mov rcx,qword ptr [winload!OslSystemRoot (00000000`00dfd0f0)]
00000000`00ab39af 4c8d05f68f2a00 lea r8,[winload!`string' (00000000`00d5c9ac)]
00000000`00ab39b6 488bd6 mov rdx,rsi
00000000`00ab39b9 e8f2252900 call winload!wcscat_s (00000000`00d45fb0)
00000000`00ab39be 488bcf mov rcx,rdi
00000000`00ab39c1 e8b6541c00 call winload!BlMmFreeHeap (00000000`00c78e7c)
00000000`00ab39c6 eb07 jmp winload!OslpInitializeSystemRoot+0xaf (00000000`00ab39cf) Branch
winload!OslpInitializeSystemRoot+0xa8:
00000000`00ab39c8 48893d21973400 mov qword ptr [winload!OslSystemRoot (00000000`00dfd0f0)],rdi
winload!OslpInitializeSystemRoot+0xaf:
00000000`00ab39cf 488b151a973400 mov rdx,qword ptr [winload!OslSystemRoot (00000000`00dfd0f0)]
00000000`00ab39d6 488bcd mov rcx,rbp
00000000`00ab39d9 e8b27b1800 call winload!BlAppendUnicodeToString (00000000`00c3b590)
00000000`00ab39de 8bcb mov ecx,ebx
00000000`00ab39e0 84c0 test al,al
00000000`00ab39e2 bb170000c0 mov ebx,0C0000017h
00000000`00ab39e7 0f44cb cmove ecx,ebx
00000000`00ab39ea 8bd9 mov ebx,ecx
winload!OslpInitializeSystemRoot+0xcc:
00000000`00ab39ec 488b6c2450 mov rbp,qword ptr [rsp+50h]
00000000`00ab39f1 8bc3 mov eax,ebx
00000000`00ab39f3 488b5c2440 mov rbx,qword ptr [rsp+40h]
00000000`00ab39f8 4883c420 add rsp,20h
00000000`00ab39fc 415e pop r14
00000000`00ab39fe 5f pop rdi
00000000`00ab39ff 5e pop rsi
00000000`00ab3a00 c3 ret
winload最后的工作就是将CPU的控制权交给内核。winload会准备一个LOADER_PARAMETER_BLOCK的结构,传递给KiSystemStartup(内核模块的入口函数)。这个结构体中记录了winload在加载过程中获取的各种信息,KiSystemStartup会利用这些信息初始化内核。
ntkrnlmp.exe
现在重要来到了Windows内核中,我们看看Windows内核的模块:
txt
kd> lmDvm nt
Browse full module list
start end module name
fffff806`87000000 fffff806`88450000 nt (pdb symbols) d:\windbgsymbols\ntkrnlmp.pdb\24DE61DB3A27C5B383DFF181CB51F7E11\ntkrnlmp.pdb
Loaded symbol image file: ntkrnlmp.exe
Image path: ntkrnlmp.exe
Image name: ntkrnlmp.exe
Browse all global symbols functions data Symbol Reload
Image was built with /Brepro flag.
Timestamp: 4473D78D (This is a reproducible build file hash, not a timestamp)
CheckSum: 00C7F356
ImageSize: 01450000
Mapping Form: Loaded
File version: 10.0.26100.8036
Product version: 10.0.26100.8036
File flags: 0 (Mask 3F)
File OS: 40004 NT Win32
File type: 1.0 App
File date: 00000000.00000000
Translations: 0409.04b0
Information from resource tables:
CompanyName: Microsoft Corporation
ProductName: Microsoft® Windows® Operating System
InternalName: ntkrnlmp.exe
OriginalFilename: ntkrnlmp.exe
ProductVersion: 10.0.26100.8036
FileVersion: 10.0.26100.8036 (WinBuild.160101.0800)
FileDescription: NT Kernel & System
LegalCopyright: © Microsoft Corporation. All rights reserved.
Unable to enumerate kernel-mode unloaded modules, HRESULT 0x80004005
ntkrnlmp末尾的mp为Multi-Processor(多处理器)之意。
回溯栈:
txt
kd> k
# Child-SP RetAddr Call Site
00 fffff806`1afb3de8 fffff806`874a41bd nt!DebugService2+0x5
01 fffff806`1afb3df0 fffff806`87b764b0 nt!DbgLoadImageSymbols+0x8d
02 fffff806`1afb3e40 fffff806`87b4b576 nt!KdInitSystem+0x680
03 fffff806`1afb3fc0 00000000`00000000 nt!KiSystemStartup+0x1d6
KiSystemStartup
KiSystemStartup为内核的入口函数。它会将winload所传递的LOADER_PARAMETER_BLOCK结构体保存到KeLoaderBlock结构体中:
txt
kd> uf KiSystemStartup
nt!KiSystemStartup:
fffff806`87b4b3a0 4883ec38 sub rsp,38h
fffff806`87b4b3a4 4c897c2430 mov qword ptr [rsp+30h],r15
fffff806`87b4b3a9 4c8bfc mov r15,rsp
fffff806`87b4b3ac 48890d6da74700 mov qword ptr [nt!KeLoaderBlock (fffff806`87fc5b20)],rcx
fffff806`87b4b3b3 4c8b9188000000 mov r10,qword ptr [rcx+88h]
fffff806`87b4b3ba 41837a2400 cmp dword ptr [r10+24h],0
fffff806`87b4b3bf 750c jne nt!KiSystemStartup+0x2d (fffff806`87b4b3cd) Branch
我们看看LOADER_PARAMETER_BLOCK结构体(利用KeLoaderBlock的地址):
txt
kd> dt nt!_LOADER_PARAMETER_BLOCK fffff806`87fc5b20
+0x000 OsMajorVersion : 0x1662c070
+0x004 OsMinorVersion : 0xfffff806
+0x008 Size : 0
+0x00c OsLoaderSecurityVersion : 0
+0x010 LoadOrderListHead : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
+0x020 MemoryDescriptorListHead : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
+0x030 BootDriverListHead : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00001f49 ]
+0x040 EarlyLaunchListHead : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
+0x050 CoreDriverListHead : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
+0x060 CoreExtensionsDriverListHead : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
+0x070 TpmCoreDriverListHead : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
+0x080 KernelStack : 0
+0x088 Prcb : 0
+0x090 Process : 0
+0x098 Thread : 0
+0x0a0 KernelStackSize : 0
+0x0a4 RegistryLength : 0x1000000
+0x0a8 RegistryBase : (null)
+0x0b0 ConfigurationRoot : (null)
+0x0b8 ArcBootDeviceName : (null)
+0x0c0 ArcHalDeviceName : (null)
+0x0c8 NtBootPathName : (null)
+0x0d0 NtHalPathName : 0x00000000`00000001 ""
+0x0d8 LoadOptions : (null)
+0x0e0 NlsData : (null)
+0x0e8 ArcDiskInformation : (null)
+0x0f0 Extension : (null)
+0x0f8 u : <unnamed-tag>
+0x108 FirmwareInformation : _FIRMWARE_INFORMATION_LOADER_BLOCK
+0x148 OsBootstatPathName : (null)
+0x150 ArcOSDataDeviceName : (null)
+0x158 ArcWindowsSysPartName : (null)
+0x160 MemoryDescriptorTree : _RTL_RB_TREE
总的来说,KiSystemStartup会完成内核最底层的初始化。这包括设置中断描述符表 (IDT) 让 CPU 能响应硬件中断,配置全局描述符表 (GDT) 确立内存访问权限,以及初始化内核调试引擎。这一步是让内核能够"思考"和"感知"外界的基础。现代 CPU 通常是多核的。KiSystemStartup 在初始化完当前核心(通常是 0 号核心)后,会立即向其他核心发送信号,让它们也从休眠中醒来,并把自己初始化到待命状态。这确保了系统启动后所有算力都能立即投入使用。当底层的硬件准备工作全部完成后,它会调用 KiInitializeKernel。这个函数负责初始化进程、线程、内存管理等高层对象。完成后,系统就正式进入了"多任务"时代,开始加载系统进程(如 System进程)并准备启动用户界面的登录程序。
关于这个函数的代码和具体执行步骤,可以参考wrk中的源代码和《软件调试》一书。
两个初始化阶段
摘自《Windows内核原理与实现》:
内核的初始化主要是内核各个组件的初始化,但由于这些内核组件之间有紧密的耦合关系,所以它们的初始化并不是简单地顺序执行初始化。为了解决在初始化过程中的相互依赖性问题,内核的初始化分两个阶段进行,称为阶段 0 和阶段 1。大多数内核组件的初始化函数相应地带有一个整数参数,以指明一次调用是阶段 0 初始化还是阶段 1 初始化,而有些组件的初始化函数通过检查一个全局变量 InitializationPhase 的值来判断当前处于哪个阶段。
简单来说,阶段0初始化是为了建立阶段1初始化所需的各种基本数据。在阶段 0 初始化过程中,中断被禁止,因此处理器可以顺序地执行自己的初始化逻辑。
0阶段对应的过程是KiInitializeKernel。KiInitializeKernel 的所有操作都是在中断关闭 (IRQL = HIGH_LEVEL) 的状态下执行的,以确保绝对原子性。当它完成了所有关键的、不可中断的初始化工作后,便会降低中断请求级别 (IRQL),退出 Phase 0,将系统控制权交还给主处理器。此时,Phase 1 开始。在这个阶段,中断重新被打开,其他处理器核心被正式激活,系统的其余部分(如设备驱动程序、子系统)才会被逐步加载和启动。
另外,Phase 0创建了Idle和System这两个特殊进程。它们是最原始的进程。关于它们的更多讨论,请参考《软件调试》一书。
Phase 1 的核心任务是将内核从单一核心的基础框架扩展为完整的多处理器运行环境,并正式启动设备驱动、建立系统进程,为后续的用户界面加载做好准备。
这一阶段标志着系统从底层自举转向功能服务,它首先唤醒所有待命的核心处理器实现真正的多核并行,接着I/O管理器会加载并初始化关键的启动设备驱动程序,让硬件与内核建立连接,随后系统创建了第一个用户模式进程smss.exe,这标志着内核开始向用户态过渡,为后续的Win32子系统、登录管理器等组件的启动铺平了道路。
SMSS.exe
SMSS(Session Manager Subsystem)作为 Windows 启动链中承上启下的关键进程,它从内核手中接管系统,负责搭建用户态的运行环境。它的工作重心在于"环境配置"与"进程孵化",具体执行流程如下:
1. 系统环境初始化
SMSS 启动后的首要任务是根据注册表配置完善系统环境。它会读取注册表 HKLM\SYSTEM\CurrentControlSet\Control\Session Manager 下的配置,执行以下关键操作:
- 分页文件管理:根据配置创建或调整系统的虚拟内存页面文件(Pagefile.sys),为后续运行大型程序提供内存交换空间。
- 文件系统检查:检查注册表中标记为"脏"的卷(Dirty Volume),触发磁盘检查(Autochk)以确保文件系统的一致性。
- 已知DLL映射 :建立系统核心动态链接库(如
ntdll.dll,kernel32.dll)的 Section 对象,确保后续启动的进程能高效共享这些基础库。
2. 核心子系统与登录进程启动
完成基础配置后,SMSS 负责孵化 Windows 图形界面和交互所需的"双子星"进程,这是系统可视化的前提:
- 启动 CSRSS:创建客户端/服务器运行时子系统(CSRSS),这是 Windows 图形界面(Win32 子系统)的基础守护进程,负责控制台窗口管理和进程/线程的创建与删除。
- 启动 WinLogon:创建 Windows 登录进程(WinLogon),该进程专门负责处理用户的登录认证、桌面切换以及安全桌面的显示。
3. 会话管理与生命周期维护
SMSS 不仅仅负责启动,还负责后续会话的动态管理。当用户登录或远程桌面连接时,SMSS 会创建新的会话(Session),并为此会话分配专用的 csrss.exe 和 winlogon.exe 实例。它持续监控这两个关键进程的状态,一旦它们意外终止,SMSS 会根据系统策略决定是否触发蓝屏或重启,从而维护系统的稳定性。
Session是Windows系统和用户交互环境的逻辑容器。简单来说就是系统为每个用户分配的独立的空间。当用户登录时,系统会创建一个新的 Session,这个空间里包含了该用户专属的桌面、窗口站、环境变量以及访问权限。
下图来自《Windows内核原理与实现》:

总结
建议还是跟着张银奎老师的步伐,亲自调试一遍。先对整个过程有个大致的映像,以后需要在哪个步骤中做文章的时候,再仔细研究该步骤的细节。
下图摘自《软件调试》:
