启用事后调试

用户模式异常处理

异常和断点

最常见的应用程序错误称为异常。 其中包括访问冲突、除以零错误、数值溢出、CLR 异常和许多其他类型的错误。 应用程序还可能导致断点中断。 当 Windows 无法运行应用程序(例如,无法加载必要的模块时)或遇到断点时,会出现这些情况。 断点可由调试器插入代码中,也可以通过 DebugBreak函数调用。

异常处理程序优先级

Windows 根据配置值和哪些调试器处于活动状态,以多种方式处理用户模式错误。 以下序列显示了用于用户模式错误处理的优先级:

  1. 如果用户模式调试器当前附加到故障进程,则所有错误都将导致目标进入此调试器。

    只要附加了用户模式调试器,就不会使用其他错误处理方法 -- 即使 使用 gn (Go With Exception Not Handled) 命令也是如此。

  2. 如果没有附加用户模式调试器,并且执行代码有自己的异常处理例程(例如 ,try - except),则此异常处理例程将尝试处理错误。

  3. 如果未附加用户模式调试器,并且 Windows 具有打开的内核调试连接,并且错误是断点中断,Windows 将尝试联系内核调试器。

    必须在 Windows 启动过程中打开内核调试连接。 如果想要阻止用户模式中断进入内核调试器,可以将 KDbgCtrl 实用工具与 -du 参数一起使用。 有关如何配置内核调试连接以及如何使用 KDbgCtrl 的详细信息,请参阅 “获取调试设置”。

    在内核调试器中,可以使用 gh (Go With Exception Handled) 忽略错误并继续运行目标。 可以使用 gn(未处理异常) 绕过内核调试器并转到步骤 4。

  4. 如果步骤 1、2 和 3 中的条件不适用,Windows 将激活 AeDebug 注册表值中配置的调试工具。 可以提前选择任何程序作为在此情况下使用的工具。 所选程序称为 事后调试器

  5. 如果步骤 1、2 和 3 中的条件不适用,并且未注册事后调试器,Windows 错误报告(WER)会显示一条消息,并提供解决方案(如果有)。 如果注册表中设置了适当的值,WER 还会写入内存转储文件。 有关详细信息,请参阅 使用 WER收集用户模式转储

DebugBreak 函数

如果已安装事后调试器,可以通过调用 DebugBreak 函数来故意从用户模式应用程序中断调试器。

指定事后图调试器

本部分介绍如何将 WinDbg 等工具配置为事后调试器。 配置后,每当应用程序崩溃时,都会自动启动事后调试器。

事后调试器注册表项

Windows 错误报告(WER)使用 AeDebug 注册表项中设置的值创建事后调试器进程。

HKLM\Software\Microsoft\Windows NT\CurrentVersion\AeDebug

有两个主要注册表值感兴趣,即调试器和自动调试器注册表值指定事后调试器的命令行。 自动注册表值指定是否自动启动事后调试器,或者是否首先显示确认消息框。

调试器(REG_SZ)

此REG_SZ值指定将处理事后调试的调试器。

除非调试器位于默认路径中的目录中,否则必须列出调试器的完整路径。

命令行通过包含 3 个参数的 printf 样式调用从调试器字符串生成。 虽然顺序是固定的,但不需要使用任何或所有可用参数。

DWORD (%ld) - 目标进程的进程 ID。

DWORD (%ld) - 事件句柄重复到事后调试器进程中。 如果事后调试器发出事件信号,WER 将继续执行目标进程,而无需等待事后调试器终止。 仅当问题得到解决时,才应发出该事件信号。 如果事后调试器在未发出事件信号的情况下终止,WER 将继续收集有关目标进程的信息。

void* (%p) - 在目标进程的地址空间中分配的JIT_DEBUG_INFO结构的地址。 该结构包含其他异常信息和上下文。

自动(REG_SZ) 此REG_SZ值始终 为 01

如果 自动 设置为 0,则会在启动事后调试过程之前显示确认消息框。

如果 自动 设置为 1,则立即创建事后调试器。

手动编辑注册表时,请仔细执行此操作,因为注册表的更改不当可能不允许 Windows 启动。

命令行用法示例

许多验尸调试器使用包含 -p 和 -e 开关的命令行来指示参数是 PID 和事件(分别)。 例如,通过 windbg.exe -I 创建以下值安装 WinDbg:

Debugger = "<Path>\WinDbg -p %ld -e %ld -g"
Auto = 1

使用 WER %ld %p 参数的方式有灵活性。 例如: 不需要指定 WER 参数周围或之间的任何开关。 例如,使用 procdump.exe -i 安装 Windows Sysinternals ProcDump 会创建以下值,且 WER %ld %ld %p 参数之间没有切换:

Debugger = "<Path>\procdump.exe" -accepteula -j "c:\Dumps" %ld %ld %p
Auto = 1

32 和 64 位调试器

在 64 位平台上,调试器(REG_SZ)和自动(REG_SZ)注册表值分别为 64 位和 32 位应用程序定义。 Windows 上的其他 Windows(WOW)密钥用于存储 32 位应用程序验尸后调试值。

HKLM\Software\Wow6432Node\Microsoft\Windows NT\CurrentVersion\AeDebug

在 64 位平台上,对 32 位进程使用 32 位事后调试器,对 64 位进程使用 64 位调试器。 这可以避免在 32 位进程中将 64 位调试器集中在 WOW64 线程而不是 32 位线程上。

对于许多事后调试器(包括 Windows 验尸调试器的调试工具)而言,这涉及到运行安装命令两次;一次使用 x86 版本,一次使用 x64 版本。 例如,若要将 WinDbg 用作交互式事后调试器,该 windbg.exe -I 命令将针对每个版本运行两次。

64 位安装:

C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\windbg.exe –I

这会使用这些值更新注册表项。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug
Debugger = "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\windbg.exe" -p %ld -e %ld –g

32 位安装:

C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\windbg.exe –I

这会使用这些值更新注册表项。

HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows NT\CurrentVersion\AeDebug
Debugger = "C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\windbg.exe" -p %ld -e %ld –g

配置事后调试器

Windows 调试工具

Windows 调试器的调试工具支持设置为事后调试器。 install 命令希望以交互方式调试进程。

WinDbg

若要将事后调试器设置为 WinDbg,请运行 windbg -II(必须大写。此命令在使用后将显示成功或失败消息。 若要同时处理 32 位和 64 位应用程序,请为 64 和 32 个调试器运行命令。

C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\windbg.exe –I
C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\windbg.exe –I

这是运行时 windbg -I 如何配置 AeDebug 注册表项。

Debugger = "<Path>\WinDbg -p %ld -e %ld -g"
Auto = 1

在示例中, <Path> 是调试器所在的目录。

如前所述,-p 和 -e 参数传递进程 ID 和事件。

-g 将 g (Go) 命令传递给 WinDbg,并继续从当前指令执行。

请注意 ,传递 g (Go) 命令存在重大问题。 此方法的问题在于,异常通常不会总是重复,因为重启代码时不再存在的暂时性条件。 有关此问题的详细信息,请参阅 .jdinfo (使用 JIT_DEBUG_INFO)。

若要避免此问题,请使用 .jdinfo 或 .dump /j。 此方法允许调试器位于相关代码故障的上下文中。 有关详细信息,请参阅 本主题后面的实时(JIT)调试

CDB

若要将事后调试器设置为 CDB,请运行 cdb -iae (安装 AeDebug) 或 cdb -iaec KeyString (使用命令安装 AeDebug)。

C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe -iae
C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\cdb.exe -iae

使用 -iaec 参数时,KeyString 指定要追加到命令行末尾的字符串,用于启动事后调试器。 如果 KeyString 包含空格,则必须用引号将其引起来。

C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe -iaec [KeyString]
C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\cdb.exe -iaec [KeyString]

此命令在成功时不显示任何内容,如果失败,则显示一条错误消息。

NTSD

若要将事后调试器设置为 NTSD,请运行 ntsd -iae (Install AeDebug) 或 ntsd -iaec KeyString (Install AeDebug with Command)。

C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\ntsd.exe -iae
C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\ntsd.exe -iae

使用 -iaec 参数时,KeyString 指定要追加到命令行末尾的字符串,用于启动事后调试器。 如果 KeyString 包含空格,则必须用引号将其引起来。

C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\ntsd.exe -iaec [KeyString]
C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\ntsd.exe -iaec [KeyString]

如果成功,此命令将不显示任何内容,并在失败时显示新控制台窗口的错误。

请注意 ,由于 -p %ld -e %ld -g 参数始终首先出现在事后调试器的命令行上,因此不应使用 -iaec 开关指定 -server 参数,因为 -server 将不起作用,除非它在命令行上出现第一个。 若要安装包含此参数的事后调试器,必须手动编辑注册表。

Visual Studio JIT 调试器

如果已安装 Visual Studio,vsjitdebugger.exe将注册为事后调试器。 Visual Studio JIT 调试器打算以交互方式调试进程。

Debugger = "C:\WINDOWS\system32\vsjitdebugger.exe" -p %ld -e %ld

如果 Visual Studio 已更新或重新安装,则会重新编写此条目,覆盖设置的任何备用值。

Window Sysinternals ProcDump

Windows Sysinternals ProcDump 实用工具也可用于事后转储捕获。 有关使用和下载 ProcDump 的详细信息,请参阅 ProcDump

与 .dump WinDbg 命令一样,ProcDump 能够以非交互方式捕获崩溃的转储。 捕获可能发生在任何 Windows 系统会话中。

ProcDump 在转储文件捕获完成时退出,然后 WER 报告失败并终止故障进程。

用于 procdump -i 安装 procdump 和 -you 为 32 位和 64 位验尸后调试卸载 ProcDump。

<Path>\procdump.exe -i

安装和卸载命令输出成功时修改的注册表值,以及失败时的错误。

注册表中的 ProcDump 命令行选项设置为:

Debugger = <Path>\ProcDump.exe -accepteula -j "<DumpFolder>" %ld %ld %p

ProcDump 使用所有 3 个参数 - PID、事件和JIT_DEBUG_INFO。 有关JIT_DEBUG_INFO参数的详细信息,请参阅 下面的实时(JIT)调试

捕获的转储大小默认为 Mini(process/threads/handles/modules/address space),没有大小选项集、MiniPlus (Mini plus MEM_PRIVATE pages)和 -mp set 或 Full(所有内存 - 等效于 -马 set 的“.dump /mA”)。

对于具有足够驱动器空间的系统,建议使用完整(-马)捕获。

将 -马 与 -i 选项一起使用以指定所有内存捕获。 (可选)提供转储文件的路径。

<Path>\procdump.exe -ma -i c:\Dumps

对于驱动器空间有限的系统,建议使用 MiniPlus (-mp) 捕获。

<Path>\procdump.exe -mp -i c:\Dumps

要将转储文件保存到的文件夹是可选的。 默认值为当前文件夹。 该文件夹应使用等于或优于用于 C:\Windows\Temp 的 ACL 进行保护。有关管理与文件夹相关的安全的详细信息,请参阅 事后调试期间的安全性。

若要卸载 ProcDump 作为事后调试器,并还原以前的设置,请使用 -u (卸载) 选项。

<Path>\procdump.exe -u

有关 ProcDump 的其他信息,请参阅 Microsoft Press 发布的 Mark Russinovich 和 Aaron Margosis 的 ProcDumpWindows SysInternals 管理员参考

恰时 (JIT) 调试

将上下文设置为故障应用程序

如前所述,使用 JIT_DEBUG_INFO 参数将上下文设置为导致崩溃的异常是非常可取的。 有关此内容的详细信息,请参阅 .jdinfo (使用 JIT_DEBUG_INFO)。

Windows 调试工具

此示例演示如何编辑注册表以运行使用 .jdinfo <地址> 命令显示其他异常信息的初始命令(-c),并将上下文更改为异常的位置(类似于 .ecxr 如何使用 .ecxr 将上下文设置为异常记录)。

Debugger = "<Path>\windbg.exe -p %ld -e %ld -c ".jdinfo 0x%p"
Auto = 1

%p 参数是目标进程的地址空间中JIT_DEBUG_INFO结构的地址。 %p 参数预追加了 0x,以便将其解释为十六进制值。 有关详细信息,请参阅 .jdinfo (使用JIT_DEBUG_INFO)。

若要调试 32 和 64 位应用的混合,请配置 32 位和 64 位注册表项(如上所述),将正确的路径设置为 64 位和 32 位WinDbg.exe的位置。

使用 .dump 创建转储文件

若要在发生包含JIT_DEBUG_INFO数据的故障时捕获转储文件,请使用 .dump /j <地址>。

<Path>\windbg.exe -p %ld -e %ld -c ".dump /j %p /u <DumpPath>\AeDebug.dmp; qd"

使用 /u 选项生成唯一文件名,以允许自动创建多个转储文件。 有关选项的详细信息,请参阅 .dump (创建转储文件)。

创建的转储将JITDEBUG_INFO数据存储为默认异常上下文。 不使用 .jdinfo 查看异常信息并设置上下文,而是使用 .exr -1 显示异常记录和 .ecxr 设置上下文。 有关详细信息,请参阅 .exr (显示异常记录).ecxr (显示异常上下文记录)。

Windows 错误报告 - q/qd

调试会话结束的方式确定Windows 错误报告是否报告了失败。

如果在调试器关闭之前使用 qd 分离调试会话,WER 将报告失败。

如果调试会话使用 q 退出(或者调试器关闭而不分离),WER 将不会报告失败。

追加 ;q;qd 到命令字符串的末尾以调用所需的行为。

例如,若要允许 WER 在 CDB 捕获转储后报告失败,请配置此命令字符串。

<Path>\cdb.exe -p %ld -e %ld -c ".dump /j 0x%p /u c:\Dumps\AeDebug.dmp; qd"

此示例允许 WER 在 WinDbg 捕获转储后报告失败。

<Path>\windbg.exe -p %ld -e %ld -c ".dump /j %p /u <DumpPath>\AeDebug.dmp; qd""

安全漏洞

如果正在考虑在与其他人共享的计算机上启用事后调试,请参阅 事后调试期间的安全性。