启用事后调试

用户模式异常处理

异常和断点

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

异常处理程序优先级

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

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

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

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

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

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

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

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

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

DebugBreak 函数

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

指定事后调试器

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

事后调试器注册表项

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

HKLM\软件\微软\\ Windows NT CurrentVersion\AeDebug

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

调试器 (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 %ld %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\软件\Wow6432Node\微软\Windows NT\CurrentVersion\AeDebug

在 64 位平台上,对 32 位进程使用 32 位事后调试器,对 64 位进程使用 64 位调试器。 这避免了 64 位调试器在 32 位进程中专注于 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 -I。 (必须 I 大写。) 此命令在使用后会显示成功或失败消息。 若要同时处理 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 (Install AeDebug) 或 cdb -iaecKeyString (Command) 安装 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 -iaecKeyString (使用 Command) 安装 AeDebug。

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 卸载 ProcDump 进行 32 位和 64 位事后调试。

<Path>\procdump.exe -i

install 和 uninstall 命令输出成功时修改的注册表值,以及失败时的错误。

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

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

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

捕获的转储大小默认为 Mini (进程/线程/句柄/modules/地址空间) 未设置大小选项,MiniPlus (Mini 加MEM_PRIVATE页) ) -mp set,或完全 (所有内存 -等效于 “.dump /mA”) 和 -ma set。

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

使用 -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 调试工具

此示例演示如何编辑注册表以运行初始命令 (-c) ,该命令使用 .jdinfo <address> 命令显示其他异常信息,并将上下文更改为异常的位置 (类似于使用 .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数据存储为默认异常上下文。 使用 .exr -1 显示异常记录,使用 .ecxr 来设置上下文,而不是使用 .jdinfo 查看异常信息和设置上下文。 有关详细信息,请参阅 .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""

安全漏洞

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