引用本文(DOI: 10.5281/zenodo.21615486)
本文保存于 Zenodo。以下同时提供始终指向最新版本的 DOI,以及固定于您正在阅读版本的 DOI。
小村 豪(2026)。《Windows 崩溃转储收集入门 - WER/ProcDump/WinDbg》。小村软件有限公司。https://doi.org/10.5281/zenodo.21615486 https://comcomponent.com/zh-CN/blog/2026/03/16/008-windows-app-crash-dump-collection-introduction/
- DOI(最新版本)
- 10.5281/zenodo.21615486
- DOI(此版本)
- 10.5281/zenodo.22282006
在 Windows 应用上,一旦出现“偶尔才崩溃”的症状,只靠日志追不下去的场面相当多。
尤其棘手的是下面这些情况。
- 只在客户环境才发生
- 拿到了异常消息,但缺少调用方的上下文
- 不只是 C# / .NET 的托管侧,还牵涉 COM、P/Invoke、原生 DLL、厂商 SDK
- 只有在长时间运行之后才崩溃
这种时候管用的就是崩溃转储。只要把崩溃时刻的进程状态落到文件里,事后就能读到异常代码、崩溃线程的堆栈、当时加载的模块,以及部分或全部内存。
在 Windows 上,先用 WER 的 LocalDumps,需要时再加上 Sysinternals 的 ProcDump,想进一步自己掌控时才用 MiniDumpWriteDump——按这个顺序考虑最容易理解。本文以 Windows 桌面应用、常驻应用、Windows 服务、设备联动工具等为前提,整理崩溃转储收集的第一步。
flowchart TB
accTitle: 考虑收集手段的顺序
accDescr: Windows 的崩溃转储收集,按先用 WER 的 LocalDumps、需要时用 Sysinternals 的 ProcDump、想进一步掌控时用 MiniDumpWriteDump 的顺序来考虑最容易理解,本图说明这一点。
w1["先用WER LocalDumps"] --> w2["需要时加上ProcDump"]
w2 --> w3["想进一步掌控就用MiniDumpWriteDump"]
图1:从标准功能开始,只在不够的地方逐步添加工具的顺序。
本文反复出现的术语
先把它们简短地固定下来。这里含糊不清的话,后面几章都会变得模糊。
| 术语 | 含义 |
|---|---|
| PDB | 构建时生成的调试信息文件。它是把地址还原成函数名和行号的对照表 |
| 符号 | 地址与名称之间的对应信息,由 PDB 或符号服务器提供。没有它,调用堆栈就只是一串地址 |
| first chance exception | 异常刚发生、应用的异常处理程序还没处理的阶段。只要应用 catch 住,处理就会照常继续 |
| second chance exception | 应用没能处理掉,作为未处理异常让进程走向终止的阶段。平时说“崩溃了”指的就是这个 |
| postmortem debugger | 崩溃时由操作系统自动启动的调试器。它是作为整台机器的崩溃时行为注册的 |
| 小型转储 / 完整转储 | 转储中包含的内存量不同。第 7 章会讲 |
1. 先说结论
先把最需要掌握的几点列出来。
- 首先 按应用单独配置 WER LocalDumps 最稳妥。不用额外工具,就能在崩溃后把转储留在本地。
- 复现率低的现场排查,或者想连 first chance exception / hang 一起看时,就用 ProcDump。
- 自研收集放到最后再考虑 正好合适。等真正需要时再评估
MiniDumpWriteDump就够了。 - 和转储同等重要的是 PDB 与发布二进制文件的保存。只有转储而没有符号,能读到的信息会大幅减少。
- 完整转储很强,但体积和混入机密信息的风险同样很大。保存位置、保留份数、访问权限、共享流程要先定好。
入门阶段推荐的配置,大致会落在下面这些地方。
| 环境 | 起步配置 |
|---|---|
| 开发机 / 验证机 | 按应用单独配置 WER LocalDumps,先用 DumpType=2 取完整转储 |
| 客户环境 / 现场机 | 看容量与机密要求选 DumpType=1 或 2。只在必要时追加 ProcDump |
| 长时间运行或 hang 排查 | 在 WER 之外再考虑 ProcDump 的 -h 或 -e 1 |
| 想连自定义 UI 和附带日志一起收 | 以独立进程为前提,用 MiniDumpWriteDump 做自研收集 |
总之就是 先 WER,再 ProcDump,最后自研。从反方向开始,设计通常都会变重。
图中实线表示始终成立的关系,虚线表示带条件的关系(成立条件写在详情页各关系的说明中)。关系的完整列表(共 21 条,附依据与确信度)以及主要概念的定义,汇总在知识地图详情页(日文)。数据:JSON-LD / Turtle
2. 崩溃转储能告诉我们什么
崩溃转储是“那一刻的快照”。与其说是监控录像,不如说更接近事故现场的静止画面。
因此,下面这类信息相当容易拿到。
- 是以哪个异常代码崩溃的
- 是哪条线程崩溃的
- 那一刻的调用堆栈
- 当时加载的模块
- 根据包含了多少内存,还能看到堆上的状态和对象的内容
另一方面,也有一些信息光靠转储容易不够。
- 走到这一步的时间线
- 从几小时前就开始的增长趋势
- 与通信或设备之间的外部状态
- 崩溃前的输入和业务上下文
所以实际工作中,不要想着只靠转储把事情做完,而要与日志和 heartbeat 组合起来,这是基本原则。
flowchart TB
accTitle: 转储与日志的组合
accDescr: 崩溃转储像事故现场的静止画面一样擅长记录那一刻的状态,而走到这一步的时间线和外部状态由日志或 heartbeat 补足,因此把两者组合起来使用是基本做法,本图说明这一点。
d1["崩溃转储〔瞬间的静止画面〕"] --> mix["组合起来排查"]
d2["日志或heartbeat〔时间线〕"] --> mix
d1 -.-> s1["擅长异常代码和堆栈"]
d2 -.-> s2["擅长走到这一步的经过"]
图2:静止画面式的转储和时间线式的日志,擅长的领域分得很清楚。
3. 收集方法全景
在 Windows 应用的转储收集中,入门阶段要掌握的方法有下面 4 种。
| 方法 | 适合的场景 | 优势 | 注意事项 |
|---|---|---|---|
| WER LocalDumps | 想先常设起来的崩溃收集 | Windows 自带,容易按应用单独配置 | 基本面向崩溃。hang 和细致的条件分支较弱 |
| ProcDump | 复现率低的排查、hang、first chance exception | 触发条件多,容易投入现场 | 会变成外部工具的运维 |
| 任务管理器的创建转储 | 想手动抓取当前状态 | 用 GUI 当场就能抓 | 不是自动收集 |
MiniDumpWriteDump |
想做自研的诊断功能 | 容易把附带日志和自定义元数据打包在一起 | 实现得粗糙反而会把自己搞坏 |
对新手来说最重要的是,比起“用什么抓”,先决定“在什么条件下”“抓到哪里”“抓多大”。
flowchart TB
accTitle: 比工具更该先决定的事
accDescr: 崩溃转储收集要先决定在什么条件下抓、抓到哪里、抓多大,然后再进入用什么抓这一工具选择,这一点很重要,本图说明这一点。
c1["在什么条件下抓"] --> firstq["先决定的3点"]
c2["输出到哪里"] --> firstq
c3["抓多大"] --> firstq
firstq --> tool["然后再选工具"]
图3:先定好条件、输出位置和大小,选工具就不会犹豫。
4. 起步首推 WER LocalDumps
4.1 最先要看的注册表值
Windows Error Reporting (WER) 提供了 LocalDumps,可以在崩溃后把用户模式转储保存到本地。因为不必分发额外工具,作为第一步相当好用。
基本的键在这里。
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps
也可以在这里放全局配置,但实际工作中集中到 按应用划分的子键 上更好管理。
flowchart TB
accTitle: LocalDumps 配置的放置位置
accDescr: 虽然也可以把全局配置直接放在 LocalDumps 键下,但实际工作中集中到 MyApp.exe 这样按应用划分的子键上更好管理,本图说明这一点。
key1["LocalDumps键"] --> g1["直接放在键下的全局配置"]
key1 --> a1["按应用划分的子键"]
a1 -.-> a2["实务中这边更好管理"]
图4:同样是这个键,集中到应用名的子键上就能缩小影响范围。
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\MyApp.exe
最先要看的值有 3 个。
| 值 | 含义 | 起步建议 |
|---|---|---|
DumpFolder |
转储的输出位置 | 单独划一个专用文件夹 |
DumpCount |
保留份数 | 从 5~10 左右开始 |
DumpType |
0=自定义、1=小型、2=完整 | 一开始用 2,容量吃紧就用 1 |
4.2 按应用单独配置的示例
举例来说,如果想让 MyApp.exe 把完整转储最多保留 10 份到 C:\CrashDumps\MyApp,可以先这样配置。
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\MyApp.exe" /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\MyApp.exe" /v DumpFolder /t REG_EXPAND_SZ /d "C:\CrashDumps\MyApp" /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\MyApp.exe" /v DumpCount /t REG_DWORD /d 10 /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\MyApp.exe" /v DumpType /t REG_DWORD /d 2 /f
这个示例有 4 个要点。
- 限定到
MyApp.exe,而不是全局 - 把输出位置分离到专用文件夹
- 起步先取完整转储
- 把保留份数限制为 10
4.3 确认真的抓到了
配置好之后,与其在生产环境等待自然发生,不如 先在验证环境完整地抓一次,这样更安全。
要确认的是这 4 点。
- 预期的文件夹里会不会出现
.dmp - 文件大小是否符合运维预期
- 能不能用 WinDbg 打开
- 事件查看器的 Application 日志里能不能看到 crash
flowchart TB
accTitle: 配置后的验证流程
accDescr: 配置好之后,在生产环境等待自然发生之前必须先在验证环境完整抓一次,确认文件夹里是否出现转储、大小是否符合预期、能否用 WinDbg 打开、事件查看器里能否看到 crash,本图说明这一流程。
v1["加入配置"] --> v2["在验证环境里故意让它崩溃"]
v2 --> v3["确认转储是否产生及其大小"]
v3 --> v4["确认能否用WinDbg打开"]
v4 --> v5["确认事件查看器的日志"]
图5:在等待生产环境自然发生之前,先在验证环境完整抓一次。
4.4 用于故意崩溃的最小代码
虽说要“完整抓一次”,可要是干等真实崩溃,验证就无从谈起。为验证准备一个 只负责故意崩溃的小 EXE,会快得多。
如果是 .NET,只抛出未处理异常的控制台应用就够了。.NET 的托管未处理异常会终止进程,所以直接就成为 WER 的对象。
// CrashTest.csproj: <TargetFramework>net8.0</TargetFramework>
using System;
using System.Threading;
internal static class Program
{
private static void Main()
{
Console.WriteLine($"PID={Environment.ProcessId} / 3 秒后崩溃。");
Thread.Sleep(3000);
throw new InvalidOperationException("intentional crash for dump collection test");
}
}
如果想验证原生侧,触发访问冲突 (0xC0000005) 更贴近实际。为了不被优化掉,要加上 volatile。
// crash_test.cpp / C++17 / MSVC
int main()
{
volatile int* p = nullptr;
*p = 1; // 这里会发生 STATUS_ACCESS_VIOLATION
return 0;
}
这里只有一个地方特别容易踩空。
LocalDumps 的子键名,必须与当前要崩溃的 EXE 文件名一致。 只建了 MyApp.exe 的键却去让 CrashTest.exe 崩溃,当然不会产生转储。验证时,要么临时建一个 CrashTest.exe 的键,要么用全局配置那边来试。
flowchart TB
accTitle: 子键名一致这个陷阱
accDescr: LocalDumps 的子键名如果与当前要崩溃的 EXE 文件名不一致就不会产生转储,因此验证时要么临时创建 CrashTest.exe 的键,要么用全局配置那边来试,本图说明这一点。
t1["只建MyApp.exe的键"] --> t2["让CrashTest.exe崩溃"]
t2 --> t3["不会产生转储"]
t3 -.-> t4["键名要与崩溃的EXE名一致"]
图6:子键名与EXE名不一致,是验证落空的经典陷阱。
Sysinternals 的 NotMyFault 也经常被当作“故意让它崩溃”的工具提起,但它是让 Windows 系统本身 crash / hang 从而 生成蓝屏转储 用的,还需要管理员权限。要验证用户模式应用的 LocalDumps,还是上面那种自己写的小 EXE 更安全可靠。
5. 什么时候用 ProcDump
WER 就够用的情况很多,但也有 ProcDump 更方便的场景。
- 不想在注册表里常设配置
- 只想监视已经在运行的进程
- 只想从下次启动开始监视
- 想看 first chance exception
- 想抓 hang
- 想按性能计数器或其他条件来抓
5.1 常用选项
只挑入门阶段常用的来看,ProcDump 记住下面这些就相当够打了。
| 选项 | 含义 |
|---|---|
-ma |
完整转储 |
-mp |
MiniPlus 转储 |
-mc <Mask> |
自定义转储。用十六进制指定 MINIDUMP_TYPE 的位掩码 |
-e |
在未处理异常时抓转储 |
-e 1 |
在 first chance / second chance 异常时抓转储 |
-h |
在窗口挂起时抓转储 |
-w |
等待目标进程启动 |
-x |
启动目标进程并监视 |
-n |
最大转储份数 |
-accepteula |
自动接受首次的 EULA 确认 |
5.2 代表性的命令示例
对已在运行的进程,在未处理异常时抓完整转储
procdump -accepteula -ma -e 1234 C:\CrashDumps\MyApp
等待下次启动,在未处理异常时抓完整转储
procdump -accepteula -ma -e -w MyApp.exe C:\CrashDumps\MyApp
自己启动进程并直接监视
procdump -accepteula -ma -e -x C:\CrashDumps\MyApp MyApp.exe
连 first chance exception 也想抓
procdump -accepteula -ma -n 3 -e 1 MyApp.exe C:\CrashDumps\MyApp
想抓挂起
procdump -accepteula -h MyApp.exe C:\CrashDumps\MyApp
5.3 为什么不把 -i 当成第一步
ProcDump 还有一种用法,是用 -i 把它注册成 postmortem debugger。这个用法很强,但因为 会介入整台机器的崩溃时行为,作为入门阶段的第一步稍微重了些。
所以一开始从 WER 的按应用单独配置,或者 ProcDump 的 -w / -x / 指定 PID 入手更好掌握。
flowchart TB
accTitle: 不把-i当成第一步的理由
accDescr: ProcDump 的 -i 是注册为 postmortem debugger 的强力用法,但会介入整台机器的崩溃时行为,因此入门阶段从 WER 的按应用单独配置或 ProcDump 的 -w、-x、指定 PID 入手更好掌握,本图说明这一点。
i1["用ProcDump的-i注册"] --> i2["介入整台机器的行为"]
i2 -.-> i3["作为入门第一步太重"]
i4["一开始用按应用划分的配置"] --> i5["WER的按应用单独配置"]
i4 --> i6["ProcDump的-w、-x或指定PID"]
图7:从影响范围能收敛在应用级别的入口开始。
6. 自研收集使用 MiniDumpWriteDump 时的思路
适合自研收集的,例如下面这些场景。
- 想在 UI 上放一个“保存诊断信息”的按钮
- 想把日志、配置、trace ID 与转储捆在一起
- 想把相关的子进程和辅助进程也一并打包
- 想在上传前加入自定义的脱敏或压缩
这里的核心 API 是 MiniDumpWriteDump。
不过它有点脾气。入门阶段特别不想弄错的是下面 2 点。
- 如果可以,从与转储目标不同的进程里调用
- DbgHelp 系列要按 single-threaded 的前提来使用
flowchart TB
accTitle: 自研收集中不想弄错的2点
accDescr: 用 MiniDumpWriteDump 做自研收集时,要守住如果可以就从与转储目标不同的进程里调用、以及把 DbgHelp 系列按 single-threaded 的前提来使用这 2 点,本图说明这一点。
md1["用MiniDumpWriteDump自研收集"] --> md2["从另一个进程调用"]
md1 --> md3["DbgHelp以single-threaded为前提"]
md2 -.-> md4["实现得粗糙反而会坏事"]
md3 -.-> md4
图8:自研收集的脾气,集中在调用位置和线程这2点上。
7. 小型转储 / 完整转储 / 中等大小该怎么选
在这里犹豫的人相当多。下面把实务中的选法列成表。
| 种类 | 适合的场景 | 优点 | 注意事项 |
|---|---|---|---|
| 小型转储 | 想先广泛铺开、想让共享更轻 | 体积小,容易传输 | 还原状态的深度较弱 |
| 完整转储 | 想优先查清原因,怀疑原生边界或堆有问题 | 能拿到的信息多 | 体积大,混入机密信息的风险高 |
| MiniPlus / Custom | 小型不够、完整又太重 | 能取得平衡 | 需要调参的知识 |
给新手的建议相当简单。
- 开发机 / 验证机就用完整转储
- 客户环境按运维条件在小型和完整之间选
- 怀疑内存损坏、原生 DLL、COM、P/Invoke、长时间运行后的状态异常,就偏向完整
flowchart TB
accTitle: 转储种类的起步选法
accDescr: 开发机或验证机用完整转储,客户环境按运维条件在小型和完整之间选,怀疑内存损坏、原生边界或长时间运行后的状态异常就偏向完整,本图说明这一选法。
e1{"在哪种环境抓"}
e1 -->|"开发机 / 验证机"| f1["完整转储"]
e1 -->|"客户环境"| f2["按运维条件选小型或完整"]
f2 -.->|"疑似损坏或原生边界"| f1
图9:拿不定主意就按环境决定,可疑程度越浓就越往完整那边靠。
7.1 MiniPlus / Custom 实际上该怎么指定
表格里只有第 3 行的指定方法稍微难懂一些,这里补充说明。
在 WER LocalDumps 的情况下,先把 DumpType 设为 0(自定义),再在 CustomDumpFlags 里填入 MINIDUMP_TYPE 的位组合。CustomDumpFlags 是只在 DumpType=0 时才会使用的值,默认为 0x00000121(MiniDumpWithDataSegs MiniDumpWithUnloadedModules MiniDumpWithProcessThreadData 的组合)。
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\MyApp.exe" /v DumpType /t REG_DWORD /d 0 /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\MyApp.exe" /v CustomDumpFlags /t REG_DWORD /d 0x121 /f
在 ProcDump 的情况下,-mp 是 MiniPlus,-mc <Mask> 是自定义。
procdump -accepteula -mp -e MyApp.exe C:\CrashDumps\MyApp
MiniPlus 虽然名字带个 Mini,内容却相当靠近完整转储。按文档所述,它包含 全部 private 内存,以及全部可读写的 image / mapped 内存,在此基础上 仅排除超过 512MB 的那块最大的 private 区域 来压缩体积。结果就定位成“详细程度接近完整转储,体积却只有完整转储的 10%~75%”。
不过有 2 点需要注意。
- 由于调试上的限制,CLR 进程即使指定
-mp,也会按完整转储 (-ma) 采集。 在 .NET 应用上指望 MiniPlus 减小体积,多半会落空 - 如果压缩体积的动机是“想减少机密信息的混入”,那么与其用 MiniPlus,不如直接从小型转储那边考虑更自然
flowchart TB
accTitle: MiniPlus的定位与限制
accDescr: MiniPlus 包含全部 private 内存以及可读写的 image、mapped 内存,仅排除超过 512MB 的那块最大 private 区域,从而收敛到完整转储的一成到七成多的体积,但在 CLR 进程上即使指定 -mp 也会按完整转储采集,本图说明这一点。
mp1["用MiniPlus采集"] --> mp2["private内存几乎全部包含"]
mp2 --> mp3["仅排除巨大的private区域"]
mp3 --> mp4["比完整转储小却同样详细"]
mp1 -.->|"CLR进程的情况"| mp5["会按完整转储采集"]
图10:MiniPlus的内容靠近完整转储,在.NET应用上压缩体积不起作用。
8. 运维上要先定好的事
转储收集在运维层面栽跟头的情况,比在实现层面多得多。下面列出希望先定好的事项。
8.1 PDB 与二进制文件要怎么留存
这一条最重要。
- 已发布的 EXE / DLL 的准确版本
- 与该版本对应的 PDB
- 是由哪个 commit / 哪条构建流水线产出的
- 安装包与发布物的版本信息
8.2 输出到哪里、留几份
完整转储会相当大。输出位置和保留方针从一开始就定好更安全。
- 不要一直放在系统盘根目录
- 分离到专用文件夹
- 用
DumpCount或-n设上限 - 把长期保存与临时保存分开
8.3 谁可以查看
完整转储里可能混入机密信息或个人信息。
- 明文配置
- 连接字符串
- 令牌或凭据
- 崩溃前正在处理的业务数据
- 文件路径和用户名
所以,在设计“怎么抓”的同时,也必须定好“谁可以接触”。
flowchart TB
accTitle: 运维上要先定好的3点
accDescr: 崩溃转储收集在运维层面比在实现层面更容易栽跟头,因此要先定好 PDB 与二进制文件的保存、输出位置与保留份数、以及谁可以查看这一访问方针,本图说明这一点。
op1["PDB与二进制文件的保存"] --> op4["先定好"]
op2["输出位置与保留份数"] --> op4
op3["谁可以查看"] --> op4
op4 -.-> op5["比起实现更容易在运维上栽跟头"]
图11:转储收集的失败,多半来自运维约定的缺失。
9. 抓到之后的最短分析路线
抓到转储之后,最先要做的事其实相当朴素。
9.1 安装 WinDbg
现在的 WinDbg 通过 Microsoft Store 或 winget 就很容易安装。
winget install Microsoft.WinDbg
9.2 打开转储
windbg -z C:\CrashDumps\MyApp\MyApp_YYMMDD_HHMMSS.dmp
这里的文件名是 ProcDump 生成的默认名称。ProcDump 的默认文件名是 PROCESSNAME_YYMMDD_HHMMSS.dmp,可以把 PROCESSNAME / PID / EXCEPTIONCODE / YYMMDD / HHMMSS 当作替换说明符使用。
另一方面,WER LocalDumps 会用与 ProcDump 不同的命名来生成文件。命名规则在 Microsoft Learn 上并没有写明,所以与其猜名字去找,不如按修改时间顺序查看输出文件夹更可靠。
dir /o-d "C:\CrashDumps\MyApp\*.dmp"
没有配置 DumpFolder 时,默认的输出位置是 %LOCALAPPDATA%\CrashDumps。不过 服务的崩溃会输出到各个运行账户的配置文件文件夹。System 服务是 %WINDIR%\System32\Config\SystemProfile,Network Service / Local Service 则在 %WINDIR%\ServiceProfiles 之下。觉得“怎么没有转储”时,先怀疑这里。
flowchart TB
accTitle: 找不到转储时该去哪里找
accDescr: 没有配置 DumpFolder 时的默认位置是 LOCALAPPDATA 下的 CrashDumps,而服务的崩溃会输出到各个运行账户的配置文件文件夹,因此觉得没有产生转储时要先怀疑那里,本图说明这一点。
fq1["找不到转储"] --> fq2{"当时以什么形式运行"}
fq2 -->|"普通应用"| fp1["LOCALAPPDATA下的CrashDumps"]
fq2 -->|"服务"| fp2["运行账户的配置文件目录下"]
fp2 -.-> fp3["SystemProfile或ServiceProfiles"]
图12:默认输出位置因账户而异,所以要从找的地方开始怀疑。
9.3 配置符号
先让 Microsoft 公开符号处于可用状态,之后再加上自己 PDB 的位置。
.symfix C:\Symbols\Microsoft
.sympath+ C:\Symbols\MyApp
.reload
9.4 先看自动分析
!analyze -v
在此基础上,
- 是哪个异常代码
- faulting module 是什么
- 自己的代码在堆栈上露出到什么程度
- 除异常线程之外,有没有可疑的等待或卡顿
按这个顺序依次查看。
flowchart TB
accTitle: 抓到之后的最短分析路线
accDescr: 安装 WinDbg、打开转储、配置 Microsoft 公开符号与自己的 PDB,先看 analyze -v 的自动分析,再依次确认异常代码、faulting module、自己代码的可见程度、其他线程的卡顿,本图说明这一流程。
an1["安装WinDbg"] --> an2["打开转储"]
an2 --> an3["配置符号与PDB"]
an3 --> an4["先看自动分析"]
an4 --> an5["依次读异常代码与堆栈"]
图13:打开、接通符号、从自动分析开始读,这是最短路线。
10. 常见的坑
10.1 转储抓到了,但没有 PDB
这种情况相当多。转储收集是成功了,但可读的材料不足。 在做收集配置的同一时间,把 PDB 的保存设计也一起做进去 会更好。
10.2 没有查看 DumpFolder 的 ACL
在服务或做了权限隔离的进程上,这里很容易落空。 先确认“那个进程是不是真的能写进去”。Microsoft Learn 也写明,如果使用默认以外的路径,就要确认“ACL 允许崩溃的进程写入”。
当前的 ACL 可以用 icacls 查看。
icacls C:\CrashDumps\MyApp
如果写入权限不足,就给运行账户加上 M(修改)。转储文件夹下面的文件也需要同样的权限,所以要加上 (OI) 和 (CI) 让它继承。
rem 示例:以 Network Service 运行的服务
icacls C:\CrashDumps\MyApp /grant "NT AUTHORITY\NETWORK SERVICE:(OI)(CI)M"
(OI) 指定把 ACE 继承到下面的文件,(CI) 指定继承到下面的文件夹。继承的范围会直接变成“谁能读到转储”,所以在授予之前,请先确认它与 8.3 里定下的方针有没有冲突。
flowchart TB
accTitle: DumpFolder的ACL确认流程
accDescr: 在服务或做了权限隔离的进程上写入容易落空,因此要用 icacls 查看当前的 ACL,不够就给运行账户授予带继承的修改权限,再把继承范围与查看方针对照,本图说明这一流程。
ac1["用icacls查看当前的ACL"] --> ac2{"运行账户能否写入"}
ac2 -->|"不能写"| ac3["授予带继承的修改权限"]
ac2 -->|"能写"| ac4["就这样运维"]
ac3 -.-> ac5["继承范围要与查看方针对照"]
图14:能否写入的确认与谁能读取的确认,在同一个地方一起做完。
10.3 持续把完整转储写到生产机的系统盘
这是容量方面的经典问题。 保留份数限制与输出位置分离 要从一开始就加上。
10.4 想只用 WER 把 hang 也全包了
WER LocalDumps 首先是对 crash 有效。 有些场景下,hang 和 first chance exception 更适合交给 ProcDump。
10.5 一直开着 -e 1,结果被异常淹没
first chance exception 很好用,但数量本来就多。 设置数量上限、只开短时间、限定目标 才现实。
11. 总结
对复现率低的故障来说,崩溃转储是相当有力的观测点。特别是 Windows 应用牵涉到 COM、P/Invoke、原生 DLL、长时间运行时,从一开始就定好“崩溃之后会留下什么”很有价值。
推荐的顺序很简单。
- 先按应用单独加上 WER LocalDumps
- 需要时再加上 ProcDump
- 想进一步掌控时,以独立进程为前提使用
MiniDumpWriteDump
按这个顺序推进,就不容易大幅走偏。
12. 参考资料
- 收集用户模式转储 - Win32 apps | Microsoft Learn
- ProcDump v11.1 - Sysinternals | Microsoft Learn
- MiniDumpWriteDump function (minidumpapiset.h) - Win32 | Microsoft Learn
- User-mode dump files - Windows drivers | Microsoft Learn
- 分析用户模式转储文件 - Windows drivers | Microsoft Learn
- 安装 Windows 调试器 - Windows drivers | Microsoft Learn
- Symbol path for Windows debuggers - Windows drivers | Microsoft Learn
- !analyze (WinDbg) - Windows drivers | Microsoft Learn
- Troubleshoot processes by using Task Manager - Windows Server | Microsoft Learn
- Enabling Postmortem Debugging - Windows drivers | Microsoft Learn
- MINIDUMP_TYPE enumeration (minidumpapiset.h) - Win32 | Microsoft Learn
- icacls - Windows commands | Microsoft Learn
- NotMyFault - Sysinternals | Microsoft Learn
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
Windows 应用崩溃时保留日志与转储的设计
整理当 Windows 应用因意外异常或程序缺陷而崩溃时,应如何组合使用常规日志、最终崩溃标记、WER LocalDumps 与监控进程,以便事后能够追溯原因。
用 WinDbg + SOS 解读崩溃转储 ── 采集之后的实务分析入门
本文说明如何用 WinDbg 与 SOS 扩展实际读取已采集的 Windows 崩溃转储。内容涵盖符号路径设置、以 !clrstack、!pe、!dumpheap -stat、!gcroot 追踪异常与内存泄漏的方法、原生崩溃的 !analyze -v,以及与 dotnet...
故障处理不止于恢复 ── 写给小型开发团队的 Postmortem(再发防止)实践模板
把故障处理停留在「修复、道歉,就此结束」,同样的故障就会反复发生。本文把 blameless postmortem 翻译给小型团队使用,整理出可在 1 小时内写完的模板、再发防止策略的强度判断表,以及实施的分级(triage)标准。
睡眠、休眠、Modern Standby 与长时间运行应用 ── 用设计防止「夜里意外停止」
本文从 S3 睡眠、休眠、Modern Standby 的区别入手,整理长时间运行的 Windows 应用为何会出现「早上一看已经停止」的原因,并讲解睡眠期间计时器与 TCP 连接的行为,以及如何用 SetThreadExecutionState 进行抑制。
MAX_PATH 与 Windows 路径・文件名的陷阱 ── 260 字符限制、保留名称、末尾句点、大小写
梳理导致「找不到文件」的经典原因 ── 路径与文件名的各种限制。本文解析 MAX_PATH=260 的构成、通过 LongPathsEnabled 启用长路径、CON 等保留名称、末尾句点的规范化处理,以及 Path.Combine 的陷阱。
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
Windows 技术主题
汇整 KomuraSoft LLC 关于 Windows 开发、故障调查与既有资产活用文章的主题中心。
与本主题相关的服务
本文与以下服务页面相关联,欢迎从最接近的入口查看。
故障调查 & 根本原因分析
把崩溃转储、日志、复现条件组合起来逐步缩小范围的思路,与故障调查、原因分析这一主题非常契合。尤其是只在现场发生的崩溃、或长时间运行之后才出现的故障,观测点的设计本身就很关键。
技术咨询 & 设计评审
如果希望连同生产环境要采集什么、转储与日志如何融入设计、访问权限与保存方针一起梳理清楚,那么以技术咨询、设计评审的形式推进会比较顺畅。
常见问题
汇总了咨询这一主题时常见的问题。
- 什么是崩溃转储?能从中看到哪些信息?
- 崩溃转储是把崩溃那一刻的进程状态保存成文件,是一张类似事故现场静止画面的快照。事后可以确认异常代码、崩溃的线程及其调用堆栈、当时加载的模块,并且随着包含的内存量增加,还能看到堆上对象的内容。另一方面,走到崩溃之前的时间线,以及与通信、设备之间的外部状态往往会不足,所以实际工作中基本上要与日志和 heartbeat 组合使用。
- WER 的 LocalDumps 在哪里配置?
- 在注册表的 HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps 下配置。实际工作中,与其使用全局配置,不如集中到 MyApp.exe 这样按应用划分的子键上更好管理。最先要看的值有三个:输出位置 DumpFolder、保留份数 DumpCount、种类 DumpType;不用分发额外工具,就能把崩溃后的转储留在本地。
- 小型转储和完整转储该选哪一个?
- 大致标准是:开发机、验证机用完整转储(DumpType=2);客户环境则看容量与机密要求,在小型转储(DumpType=1)和完整转储之间选。怀疑内存损坏、原生 DLL、COM、P/Invoke、长时间运行后的状态异常时,偏向完整转储更有利。不过完整转储体积大,也存在混入连接字符串、令牌等机密信息的风险,因此必须先定好保存位置、保留份数和访问权限。
- ProcDump 在什么时候用?
- WER LocalDumps 基本上面向崩溃,所以当你想连 hang 或 first chance exception 一起看、不想在注册表里常设配置、或者只想监视已经在运行的进程时,ProcDump 更合适。有代表性的选项包括:抓完整转储的 -ma、在未处理异常时采集的 -e、检测挂起的 -h、等待启动的 -w 等。以 first chance exception 为对象的 -e 1 容易产生大量转储,所以用 -n 加上上限、只短时间开启这类限定用法才现实。