调试利器:DebugDiag与!analyze命令的高效运用
在软件开发和调试过程中,快速准确地定位和解决问题至关重要。本文将介绍两款强大的调试工具:DebugDiag和!analyze扩展命令,它们能显著减少初始故障分析所需的时间。
DebugDiag自定义脚本
DebugDiag是一款功能强大的调试工具,它的自定义脚本功能和丰富的对象模型,让工程师能够创建复杂而强大的事后分析脚本。
脚本编写与保存
在编写脚本时,Manager对象的Write方法可用于向为用户准备的报告中写入数据。由于报告通过浏览器渲染,因此需要包含适当的HTML标签,以确保信息格式正确。
脚本编写完成后,将其保存为.asp扩展名,并放置在DebugDiag安装路径的Scripts文件夹中。例如,如果安装驱动器为C:\,则将脚本文件放在以下目录:
C:\Program Files\IIS Resources\DebugDiag\Scripts
脚本使用
保存脚本后,启动DebugDiag,点击“Advanced Analysis”选项卡,选择刚刚创建的分析脚本。可以选择多个转储文件,新的临界区分析脚本将依次分析每个选定的脚本。
DebugDiag的自定义脚本功能极大地提高了调试效率。如果没有这个工具,开发人员要么在每次调试会话中手动执行脚本的所有步骤,要么编写一个自定义调试器扩展来完成相同的工作。虽然DebugDiag暴露的对象模型相当全面,但并非涵盖所有情况,有时仍需编写一个调试扩展,以便随后从DebugDiag脚本中调用。
!analyze扩展命令
!analyze扩展命令旨在自动化故障分析,其最终目标是实现自动故障分析、检测和已知问题的分配。随着时间的推移,该命令的功能不断增强,现在能够自动分析大量复杂问题,如堆栈溢出、应用程序验证器故障等。
命令功能
!analyze扩展命令的一个重要特性是能够根据分析结果分配故障。例如,可以告诉该命令,特定模块中发生的任何故障应分配给特定的负责人。这在使用!analyze命令自动分析故障时非常有用,可减少手动进行初始分析和故障分配的时间。
分析故障应用程序
下面通过一个具体的死锁场景,展示如何使用!analyze扩展命令进行故障分析。
我们要分析的二进制文件位于以下位置:
- 源代码:
C:\AWD\Chapter10\Deadlock
- 二进制文件:
C:\AWDBIN\WinXP.x86.chk\10DeadLock.exe
分析步骤如下:
1. 在调试器下启动
10DeadLock.exe
,让其运行直到发生死锁。
2. 发生死锁后,进入调试器并转储所有线程。
3. 尝试识别一个疑似挂起的线程,并将当前线程切换到该线程。在本例中,线程0可能是潜在的问题线程。
4. 切换到潜在问题线程后,执行以下命令:
!analyze –v –hang
其中,
–v
开关使命令以详细模式输出结果,
–hang
开关指示命令执行挂起分析。
命令输出分析
执行命令后,会输出大量信息,下面对部分关键信息进行详细解释:
| 信息项 | 说明 |
| ---- | ---- |
| FAULTING_IP | 故障发生时指令指针的值。在本例中,是因为检测到死锁时进入调试器导致的(
kernel32!CtrlRoutine
)。 |
| EXCEPTION_RECORD | 以异常记录的形式提供有关发生异常的更多详细信息。在本例中,没有记录到异常记录(
ffffffff
)。 |
| ExceptionAddress | 异常发生的地址。在本例中,异常地址为
7c87533d
,对应
kernel32.dll
中的控制例程函数(
kernel32!CtrlRoutine
)。 |
| ExceptionCode | 导致故障的具体异常代码。在本例中,异常代码为
40010005
,对应Control-C异常,这是因为使用Control-C进入调试器而记录的。 |
| ExceptionFlags | 与异常关联的标志。在本例中,标志设置为
00000000
。 |
| NumberParameters | 与异常关联的参数数量。在本例中,参数数量为0。 |
| FAULTING_THREAD | 导致故障发生的线程。在本例中,故障线程为
00000d08
。 |
| BUGCHECK_STR | 对发生的“错误检查”的文本描述。在本例中,故障原因是“HANG”。 |
| PROCESS_NAME | 出现故障的进程名称(
10DeadLock.exe
)。 |
| ERROR_CODE | 导致故障的错误代码(NTSTATUS)。 |
| CRITICAL_SECTION | 起始线程中找到并分析的临界区地址。在本例中,起始线程为0,临界区地址为
01003320
。 |
| BLOCKING_THREAD | 在CRITICAL_SECTION字段中显示的临界区上阻塞的线程ID。在本例中,阻塞线程为
00000d08
。 |
| DERIVED_WAIT_CHAIN | 这是!analyze扩展命令挂起分析功能的核心。它尝试推导与CRITICAL_SECTION字段中列出的临界区相关的每个线程的等待链。输出示例如下:
Dl Eid Cid WaitType
-----------------------
x 0 85c.d08 Critical Section ->
x 1 85c.8f4 Critical Section -^
其中,
-->
表示线程正在等待临界区,
--^
表示线程拥有临界区。从输出可以看出,线程ID为
d08
的线程正在等待线程
8f4
拥有的临界区。 |
| WAIT_CHAIN_COMMAND | 显示可用于进一步分析有问题锁的等待链的命令。在本例中,要执行的命令是
~0s;k;;~1s;k;;
。 |
| DEFAULT_BUCKET_ID | 详细说明此特定故障所属的一般故障类别。在本例中,故障被确定为“APPLICATION_HANG_DEADLOCK”。 |
| PRIMARY_PROBLEM_CLASS | 指示故障被归类的主要问题类别。在本例中,主要问题类别也是“APPLICATION_HANG_DEADLOCK”。 |
| LAST_CONTROL_TRANSFER | 显示堆栈上最后两个函数调用。在本例中,地址为从
7c90e9c0
到
7c90eb94
,使用
ln
命令可以看到最后一个函数调用是从
ntdll!NtWaitForSingleObject
到
ntdll!KiFastSystemCallRet
。 |
| STACK_TEXT | 显示线程的完整堆栈跟踪。在本例中,显示的是线程0的堆栈跟踪。 |
| FOLLOWUP_IP | 显示最有可能导致故障的指令。从输出可以看出,调用
EnterCriticalSection
导致代码出现问题性挂起。 |
| SYMBOL_STACK_INDEX | 对应线程堆栈跟踪中导致故障的帧。在本例中,符号堆栈索引为4,对应堆栈上导致故障的帧:
10DeadLock!main
。 |
| FOLLOWUP_NAME | 显示应跟进此特定故障的人员。在本例中,跟进人员为“MachineOwner”,通常在找不到特定故障的跟进指令时使用。 |
| MODULE_NAME | 显示出现故障的模块(
10DeadLock
)。 |
| IMAGE_NAME | 显示故障发生的映像名称(
10DeadLock.exe
)。 |
| DEBUG_FLR_IMAGE_TIMESTAMP | 显示故障发生的映像的时间戳(
45c93d82
)。 |
| SYMBOL_NAME | 显示故障发生的符号名称(
10DeadLock!main
)。 |
| STACK_COMMAND | 显示为获取故障线程的堆栈跟踪而执行的命令。在本例中,命令序列为
~0s ; kb
。 |
| FAILURE_BUCKET_ID和BUCKET_ID | 显示故障所属的特定问题类别。此信息可由!analyze扩展命令用于确定在分析结果中显示哪些额外信息。 |
| FOLLOWUP | 显示此特定故障的最合适负责人。如果找不到负责人,则显示默认的“MachineOwner”。 |
通过执行!analyze扩展命令,可以快速获取大量原本需要手动花费大量时间才能收集到的信息。此外,该命令还具有为特定故障分配负责人的功能,下面将介绍如何自定义此功能。
调试利器:DebugDiag与!analyze命令的高效运用
故障跟进自定义
在上一部分对死锁应用程序运行!analyze扩展命令的结果中,跟进联系人默认显示为“MachineOwner”,因为该命令不知道要将此特定问题分配给谁。下面将介绍如何自定义此行为,以满足实际需求。
假设
10DeadLock.exe
应用程序的负责人是John Anderson(电子邮件:johna@fictionous.com),我们希望将John的名字与
10DeadLock.exe
映像中发生的任何故障关联起来。这可以通过一个名为
triage.ini
的文件来实现,该文件位于调试器安装路径下的
triage
文件夹中。
默认情况下,
triage.ini
文件包含一些关联规则,这些规则要么忽略指定的故障,要么将故障分配给默认负责人(如“MachineOwner”)。例如:
nt!Zw*=ignore
这行代码表示,在
nt
模块中以字母
Zw
开头的任何故障都将被忽略,!analyze扩展命令不会显示跟进负责人。可以看到,
triage.ini
文件支持使用通配符,以便更轻松地将一系列问题分配给特定实体。
为了将John Anderson指定为
10DeadLock.exe
的负责人,我们可以在
triage.ini
文件中添加以下行:
10DeadLock!*=John Anderson (johna@fictionous.com)
使用通配符
*
,可以告诉!analyze扩展命令将
10DeadLock
模块中发生的所有故障都分配给John。
再次在调试器下运行应用程序并进行挂起分析,以确保故障确实分配给了John。以下是!analyze扩展命令结果的简化版本:
0:000> !analyze -v -hang
*******************************************************************************
* *
* Exception Analysis *
* *
*******************************************************************************
FAULTING_IP:
kernel32!CtrlRoutine+bd
7c87533d 834dfcff or dword ptr [ebp-4],0FFFFFFFFh
…
…
…
FOLLOWUP_NAME: John Anderson (johna@fictionous.com)
…
.
..
…
Followup: John Anderson (johna@fictionous.com)
-----
可以看到,跟进联系人已从“MachineOwner”更改为“John Anderson”,这使得查看分析结果的人员更容易将故障分配给正确的负责人。
当拥有一个复杂的系统,其中大量开发人员使用许多不同的映像和模块时,指定负责人的功能非常有用。在测试过程中,系统中任何地方发生故障时,都可以使用!analyze扩展命令收集有关问题根源的信息,并立即确定应联系谁来处理此故障。甚至可以设想一个完全自动化的通知过程,自动解析!analyze扩展命令的输出,并向结果中指定的跟进人员发送电子邮件。
总结
本文介绍的两款调试工具——DebugDiag和!analyze扩展命令,能够显著减少初始故障分析所需的时间。
DebugDiag提供了一种机制,可用于自动化内存泄漏检测、崩溃分析和挂起分析,还可以编写自定义分析脚本。分析结果以格式良好且易于理解的报告形式呈现,所有相关信息都进行了分类,并以文本和图形形式显示。
!analyze扩展命令具有强大的分析能力,经过多年的发展,它能够分析大量复杂问题,并对已识别的问题进行自动故障分配。
通过合理运用这些工具,可以提高调试效率,更快地定位和解决软件中的问题,为软件开发和维护工作提供有力支持。
下面是使用mermaid绘制的一个简单流程图,展示了使用!analyze扩展命令进行故障分析的主要流程:
graph LR
A[启动应用程序] --> B[发生死锁]
B --> C[进入调试器并转储线程]
C --> D[识别疑似挂起线程]
D --> E[切换到疑似线程]
E --> F[执行!analyze –v –hang命令]
F --> G[分析命令输出结果]
G --> H{是否需要自定义跟进}
H -- 是 --> I[修改triage.ini文件]
H -- 否 --> J[结束分析]
I --> K[重新运行分析并确认结果]
K --> J
这个流程图清晰地展示了从启动应用程序到最终完成分析的整个过程,包括是否需要自定义故障跟进的判断环节。通过这种方式,可以更直观地理解和运用这些调试工具。
超级会员免费看
16万+

被折叠的 条评论
为什么被折叠?



