应用程序验证工具 - 调试应用程序验证工具停止
调试器安装和设置
某些应用程序验证程序操作可能会导致引发异常。 必须将调试器设置为在第二次机会上捕获这些异常,因为应用程序验证程序本身将处理第一次机会异常。
引发的异常有三种类型:
如果堆选项检测到堆缓冲区溢出,则会生成访问冲突异常 (0xC0000005) 。 在某些情况下,“检查系统路径使用情况”选项也可能导致访问冲突。
当“检测无效句柄用法”选项检测到无效句柄操作时,将生成无效句柄异常 (0xC0000008) 。
当“检查足够的堆栈”选项检测到初始堆栈太短时,将生成堆栈溢出异常 (0xC00000FD) 。
为这些事件做好准备的一种方法是在命令行上启动调试器,如下所示:
windbg -xd av -xd ch -xd sov ApplicationCommandLine
或
cdb -xd av -xd ch -xd sov ApplicationCommandLine
如果已启动调试器,则可以使用 sxd (Set Exceptions) 命令捕获所有访问冲突、无效句柄和堆栈溢出作为第二次机会异常:
0:000> sxd av
0:000> sxd ch
0:000> sxd sov 1
理论上可以通过内核调试器控制应用程序验证程序。 但是,不建议这样做 - 它需要频繁使用 .process 和 .pagein 命令,但它提供的功能不会比使用用户模式调试器更多。
安装调试工具
若要下载最新版本的工具,请参阅 下载适用于 Windows 的调试工具。
为 User-Mode 调试配置硬件
用户模式调试通常在单台计算机上完成:调试器与失败的应用程序在同一台计算机上运行。
在这种情况下,不需要特定的硬件设置。 在本例中,术语“主机计算机”和“目标计算机”可互换。
为 User-Mode 调试配置软件
基本 User-Mode 配置 - 在开始用户模式调试之前,必须下载必要的符号文件并设置某些环境变量。
符号文件
必须下载正在调试的用户模式进程的符号文件。 如果这是已编写的应用程序,则应使用完整的符号文件生成它。 如果它是一个商业应用程序,符号文件可以在 Web 服务器上提供或下载,请联系制造商。
如果要执行远程调试,则符号文件位置取决于所使用的方法:
如果通过调试器执行远程调试,符号文件应位于具有调试服务器的计算机上。
如果通过 remote.exe 执行远程调试,符号文件应位于具有调试器的计算机上。
如果通过进程服务器或 KD 连接服务器执行远程调试,则符号文件应位于具有智能客户端的计算机上。
如果要从内核调试器控制用户模式调试器,则符号文件需要位于两台计算机上。
配置环境变量
调试器使用各种环境变量来指示许多重要设置。
有关调试器的详细信息,请参阅使用 Windows 调试入门
使用命令行使用调试器配置应用程序验证程序
若要配置应用程序验证程序,可以使用 CDB 或 NTSD 命令行。
使用以下命令行:
cdb OtherOptions -vf:Flags Target
其中,Target 是目标应用程序的名称,Flags 指定要应用于此目标所需的应用程序验证程序选项。
标志应是表示所需选项的位之和。 各个位值如下所示:
标志值 | 含义 |
---|---|
00000001 | 堆检查 |
00000004 | 处理检查 |
00000008 | 低资源 SIM 卡检查 |
00000020 | TLS 检查 |
00000040 | 脏堆栈 |
00000200 | 危险 API |
00001000 | 异常检查 |
00002000 | 内存检查 |
00020000 | 其他检查 |
00040000 | 锁定检查 |
使用 !avrf 进行调试
!avrf 扩展控制应用程序验证程序的设置,并显示应用程序验证程序生成的各种输出。 有关 !arvrf 扩展的其他信息,请参阅调试器文档中的 !avrf 。
语法
!avrf
不带任何参数的 !avrf 命令显示应用程序验证程序设置以及有关当前和以前的应用程序验证程序的信息中断(如果有)。
!avrf –vs { Length | -aAddress }
显示虚拟空间操作日志。 Length 指定要从最近开始显示的记录数。 Address 指定虚拟地址。 将显示包含此虚拟地址的虚拟操作记录。
!avrf -hp { Length | -a Address }
显示堆操作日志。 Address 指定堆地址。 将显示包含此堆地址的堆操作记录。
!avrf -cs { Length | -a Address }
显示关键节删除日志。 Length 指定要从最近开始显示的记录数。 Address 指定关键节地址。 指定地址时,将显示特定关键节的记录。
!avrf -dlls [ Length ]
显示 DLL 加载/卸载日志。 Length 指定要从最近开始显示的记录数。
!avrf -trm
显示所有终止线程和挂起线程的日志。
!avrf -ex [ Length ]
显示异常日志。 应用程序验证程序跟踪应用程序中发生的所有异常。
!avrf -threads [ ThreadID ]
显示有关目标进程中线程的信息。 对于子线程,还会显示父线程指定的堆栈大小和 CreateThread 标志。 提供线程 ID 将仅显示该特定线程的信息。
!avrf -tp [ ThreadID ]
显示线程池日志。 此日志可能包含各种操作的堆栈跟踪,例如更改线程关联掩码、更改线程优先级、发布线程消息、初始化 COM 以及从线程池回调中取消初始化 COM。 提供线程 ID 将仅显示该特定线程的信息。
!avrf -srw [ Address | Address Length ] [ -stats ]
显示精简读取器/编写器 (SRW) 日志。 指定地址将显示与该 SRW 锁地址相关的记录。 在指定 Length 以及 Address 时,将显示该地址范围内的所有 SRW 锁。 -stats 选项转储 SRW 锁统计信息。
!avrf -leak [ -m ModuleName ] [ -r ResourceType ] [ -a Address ] [ -t ]
显示未完成的资源日志。 这些资源在任何给定点上都可能是泄漏,也可能不是泄漏。 指定 ModuleName (包括扩展) 显示指定模块中所有未完成的资源。 指定 ResourceType 会显示该特定资源类型的未完成资源。 指定地址将转储具有该地址的未完成资源的记录。 ResourceType 可以是以下选项之一:
- 堆:使用 Win32 堆 API 显示堆分配
- 本地:显示本地/全局分配
- CRT:使用 CRT API 显示分配
- 虚拟:显示虚拟预留
- BSTR:显示 BSTR 分配
- 注册表:显示注册表项打开
- 电源:显示电源通知对象
- 句柄:显示线程、文件和事件句柄分配
!avrf –trace TraceIndex
显示指定跟踪索引的堆栈跟踪。 某些结构使用此 16 位索引号来标识堆栈跟踪。 此索引指向堆栈跟踪数据库中的某个位置。 如果要分析此类结构,则会发现此语法很有用。
!avrf -cnt
显示全局计数器的列表。
!avrf -brk [ BreakEventType ]
指定这是中断事件命令。 不使用其他参数时 !avrf -brk
,将显示中断事件设置。 BreakEventType 指定中断事件的类型编号。 有关可能类型的列表,请使用 !avrf -brk
。
!avrf -flt [ EventTypeProbability ]
指定这是错误注入命令。 不使用其他参数时 !avrf -flt
,将显示当前故障注入设置。 EventType 指定事件的类型编号。 Probability 指定事件失败的频率。 这可以是介于 0 和 1,000,000 (0xF4240) 之间的任意整数。
!avrf -flt break EventType
每次注入此错误时,应用程序验证程序都会闯入调试器。
!avrf -flt stacks Length
显示最近错误注入操作的堆栈跟踪的长度数。
!avrf -trg [ StartEnd | dll Module | all ]
指定这是目标范围命令。 不使用其他参数使用 -trg 时,将显示当前目标范围。 Start 指定目标范围或排除范围的起始地址。 End 指定目标范围或排除范围的结束地址。 Module 指定要作为目标或排除的模块的名称。 模块应包含完整的模块名称,包括 .exe 或 .dll 扩展。 不应包含路径信息。 指定 all 会导致重置所有目标范围或排除范围。
!avrf -skp [ StartEnd | dll Module | all | Time ]
指定这是排除范围命令。 Start 指定目标范围或排除范围的起始地址。 End 指定目标范围或排除范围的结束地址。 Module 指定要作为目标或排除的模块的名称。 模块应包含完整的模块名称,包括 .exe 或 .dll 扩展。 不应包含路径信息。 指定 all 会导致重置所有目标范围或排除范围。 指定时间会导致在执行恢复后以时间毫秒为单位抑制所有错误。
下面是调试器中 !avrf 命令提供的输出。
0:000> !avrf
Application verifier settings (816431A7):
- full page heap
- COM
- RPC
- Handles
- Locks
- Memory
- TLS
- Exceptions
- Threadpool
- Leak
- SRWLock
No verifier stop active.
Note: Sometimes bugs found by verifier manifest themselves as raised
exceptions (access violations, stack overflows, invalid handles),
and it is not always necessary to have a verifier stop.
!avrf 扩展注释
在没有参数的情况下使用 !avrf 扩展时,它会显示当前的应用程序验证程序选项。
!avrf 扩展使用调试器中的 Exts.dll。
如果应用程序验证程序已停止,则不带参数的 !avrf 扩展将显示停止的性质及其原因。
如果缺少 ntdll.dll 和 verifier.dll 的符号,则 !avrf 扩展将生成错误消息。
连续和不可连续停止
调试连续停止
下面是检测无效句柄用法选项引发的无效句柄异常的示例。
首先,将显示以下消息:
Invalid handle - code c0000008 (first chance)
===================================================
VERIFIER STOP 00000300: pid 0x558: invalid handle exception for current stack trace
C0000008 : Exception code.
0012FBF8 : Exception record. Use .exr to display it.
0012FC0C : Context record. Use .cxr to display it.
00000000 :
===================================================
This verifier stop is continuable.
After debugging it use 'go' to continue.
===================================================
Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=6a27c280 ecx=6a226447 edx=0012fa4c esi=00942528 edi=6a27c260
eip=6a22629c esp=0012facc ebp=0012faf0 iopl=0 nv up ei pl zr na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!DbgBreakPoint:
6a22629c cc int 3
请注意,该消息告知可以继续此应用程序验证程序停止。 了解已发生的情况后,可以继续运行目标应用程序。
首先,应使用 !avrf 扩展。 这会提供有关当前故障的信息:
0:000> !avrf
Global flags: 00000100
Application verifier global flag is set.
Application verifier settings (00000004):
- no heap checking enabled!
- handle checks
Page heap is not active for this process.
Current stop 00000300 : c0000008 0012fbf8 0012fc0c 00000000 .
Using an invalid handle (either closed or simply bad).
此显示的最后一行总结了问题。
此时可能需要查看一些日志。 完成后,使用 g (Go) 命令再次启动应用程序:
0:000> g
## Debugging a Non-Continuable Stop
Here is an example of an access violation that has been raised by the page heap option.
First, the following message appears:
Access violation - code c0000005 (first chance)
===================================================
VERIFIER STOP 00000008: pid 0x504: exception raised while verifying block header
00EC1000 : Heap handle
00F10FF8 : Heap block
00000000 : Block size
00000000 :
===================================================
This verifier stop is not continuable. Process will be terminated when you use the 'go' debugger command.
===================================================
Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=6a226447 edx=0012fab7 esi=00f10ff8 edi=00000008
eip=6a22629c esp=0012fb5c ebp=0012fb80 iopl=0 nv up ei pl zr na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!DbgBreakPoint:
6a22629c cc int 3
在这种情况下,消息会告知你无法继续此应用程序验证程序停止。 该错误太严重,无法继续运行进程,应用程序验证程序无法挽救进程。
!avrf 扩展可用于提供有关当前故障的信息:
0:000> !avrf
Global flags: 02000100
Application verifier global flag is set.
Page heap global flag is set.
Application verifier settings (00000001):
- full page heap
Page heaps active in the process (format: pageheap, lightheap, flags):
00941000 , 00a40000 , 3 (pageheap traces )
00b41000 , 00c40000 , 3 (pageheap traces )
00cb1000 , 00db0000 , 3 (pageheap traces )
00ec1000 , 00fc0000 , 3 (pageheap traces )
Current stop 00000008 : 00ec1000 00f10ff8 00000000 00000000 .
Corrupted heap block.
此显示的最后一行总结了问题。
此时可能还需要查看一些日志。 此时,可能需要使用 .restart (Restart Target Application) 命令。 或者,你可能更喜欢结束应用程序验证程序会话并开始修复代码中的 bug。
调试关键节错误
!cs 调试器扩展
!cs 可用于用户模式调试器和内核调试器,以显示有关当前进程中关键部分的信息。 有关 !cs 扩展的其他信息,请参阅调试器文档中的 !cs 。
需要具有类型信息的匹配符号,尤其是对于 ntdll.dll。
此扩展的语法为:
!cs [-s] - 转储当前进程中所有活动的关键部分。
!cs [-s] 地址 - 在此地址转储关键部分。
!cs [-s] -d address - 转储关键部分,对应于此地址处的 DebugInfo。
-s 将转储关键部分初始化堆栈跟踪(如果可用)。
示例:
使用关键节的地址转储有关关键节的信息
0:001> ! cs 0x7803B0F8
Critical section = 0x7803B0F8 (MSVCRT!__app_type+0x4)
DebugInfo = 0x6A262080
NOT LOCKED
LockSemaphore = 0x0
SpinCount = 0x0
使用关键节的地址转储有关关键节的信息,包括初始化堆栈跟踪
0:001> !cs -s 0x7803B0F8
Critical section = 0x7803B0F8 (MSVCRT!__app_type+0x4)
DebugInfo = 0x6A262080
NOT LOCKED
LockSemaphore = 0x0
SpinCount = 0x0
Stack trace for DebugInfo = 0x6A262080:
0x6A2137BD: ntdll!RtlInitializeCriticalSectionAndSpinCount+0x9B
0x6A207A4C: ntdll!LdrpCallInitRoutine+0x14
0x6A205569: ntdll!LdrpRunInitializeRoutines+0x1D9
0x6A20DCE1: ntdll!LdrpInitializeProcess+0xAE5
使用关键节的调试信息地址转储有关其信息
0:001> !cs -d 0x6A262080
DebugInfo = 0x6A262080
Critical section = 0x7803B0F8 (MSVCRT!__app_type+0x4)
NOT LOCKED
LockSemaphore = 0x0
SpinCount = 0x0
使用关键节的调试信息地址(包括初始化堆栈跟踪)转储有关关键部分的信息
0:001> !cs -s -d 0x6A262080
DebugInfo = 0x6A262080
Critical section = 0x7803B0F8 (MSVCRT!__app_type+0x4)
NOT LOCKED
LockSemaphore = 0x0
SpinCount = 0x0
Stack trace for DebugInfo = 0x6A262080:
0x6A2137BD: ntdll!RtlInitializeCriticalSectionAndSpinCount+0x9B
0x6A207A4C: ntdll!LdrpCallInitRoutine+0x14
0x6A205569: ntdll!LdrpRunInitializeRoutines+0x1D9
0x6A20DCE1: ntdll!LdrpInitializeProcess+0xAE
有关当前进程中所有活动关键部分的转储信息
0:001> !cs
-----------------------------------------
DebugInfo = 0x6A261D60
Critical section = 0x6A262820 (ntdll!RtlCriticalSectionLock+0x0)
LOCKED
LockCount = 0x0
OwningThread = 0x460
RecursionCount = 0x1
LockSemaphore = 0x0
SpinCount = 0x0
-----------------------------------------
DebugInfo = 0x6A261D80
Critical section = 0x6A262580 (ntdll!DeferedCriticalSection+0x0)
NOT LOCKED
LockSemaphore = 0x7FC
SpinCount = 0x0
-----------------------------------------
DebugInfo = 0x6A262600
Critical section = 0x6A26074C (ntdll!LoaderLock+0x0)
NOT LOCKED
LockSemaphore = 0x0
SpinCount = 0x0
.....
有关当前进程中所有活动关键部分的转储信息,包括初始化堆栈跟踪
0:001> !cs -s
...
-----------------------------------------
DebugInfo = 0x6A261EA0
Critical section = 0xA8001C (+0xA8001C)
NOT LOCKED
LockSemaphore = 0x0
SpinCount = 0x0
No stack trace saved
-----------------------------------------
DebugInfo = 0x6A261EC0
Critical section = 0x6A263560 (ntdll!RtlpDphTargetDllsLock+0x0)
NOT LOCKED
LockSemaphore = 0x0
SpinCount = 0x0
No stack trace saved
-----------------------------------------
DebugInfo = 0x6A261EE0
Critical section = 0xA90608 (+0xA90608)
NOT LOCKED
LockSemaphore = 0x7EC
SpinCount = 0x0
Stack trace for DebugInfo = 0x6A261EE0:
0x6A2137BD: ntdll!RtlInitializeCriticalSectionAndSpinCount+0x9B
0x6A20B0DC: ntdll!CsrpConnectToServer+0x1BE
0x6A20B2AA: ntdll!CsrClientConnectToServer+0x148
0x77DBE83F: KERNEL32!BaseDllInitialize+0x11F
0x6A207A4C: ntdll!LdrpCallInitRoutine+0x14
0x6A205569: ntdll!LdrpRunInitializeRoutines+0x1D9
0x6A20DCE1: ntdll!LdrpInitializeProcess+0xAE5
-----------------------------------------
DebugInfo = 0x6A261F00
Critical section = 0x77E1AEB8 (KERNEL32!BaseDllRegistryCache+0x18)
NOT LOCKED
LockSemaphore = 0x0
SpinCount = 0x0
Stack trace for DebugInfo = 0x6A261F00:
0x6A2137BD: ntdll!RtlInitializeCriticalSectionAndSpinCount+0x9B
0x6A207A4C: ntdll!LdrpCallInitRoutine+0x14
0x6A205569: ntdll!LdrpRunInitializeRoutines+0x1D9
0x6A20DCE1: ntdll!LdrpInitializeProcess+0xAE5
调试异常错误
异常日志记录目标进程中发生的所有异常。
可以使用 !avrf -ex Length 扩展命令来显示最后几个异常;Length 指定异常数。 如果省略 Length,则显示所有异常。
以下是示例:
0:000> !avrf -ex 4
=================================
Thread ID: 0000052c
Exception code: c0000008
Exception address: 6a226663
Exception record: 0012fb50
Context record: 0012fb64
Displayed 1 exception log entries.
调试处理错误
!htrace 可用于用户模式调试器和内核调试器,以显示进程中一个或所有句柄的堆栈跟踪信息。 如果为进程启用了句柄跟踪,则此信息可用 - 如果在应用程序验证程序中启用了句柄检查,则会自动启用此信息。 每次进程打开或关闭句柄或引用无效句柄时,都会保存堆栈跟踪。 有关 !htrace 扩展的其他信息,请参阅调试器文档中的 !htrace 。
此扩展的内核调试器语法为:
!htrace [ handle [process] ]
如果未指定句柄或为 0,则会显示有关进程中所有句柄的信息。 如果未指定 process,则将使用当前进程。
用户模式调试器语法为:
!htrace [handle]
用户模式调试器扩展始终显示有关当前被调试者进程的信息。
示例:
有关进程 815328b0 中处理 7CC 的转储信息
kd> !htrace 7CC 815328b0
Loaded \\...\kdexts extension DLL
Process 0x815328B0
ObjectTable 0xE15ECBB8
--------------------------------------
Handle 0x7CC - CLOSE:
0x8018FCB9: ntoskrnl!ExDestroyHandle+0x103
0x801E1D12: ntoskrnl!ObpCloseHandleTableEntry+0xE4
0x801E1DD9: ntoskrnl!ObpCloseHandle+0x85
0x801E1EDD: ntoskrnl!NtClose+0x19
0x77DBFCD6: KERNEL32!GetLocaleFileInfo+0x3D
0x77DBF942: KERNEL32!NlsProcessInitialize+0x11D
0x77E0C6DF: KERNEL32!NlsDllInitialize+0x35
0x6A20785C: ntdll!LdrpCallInitRoutine+0x14
0x6A205393: ntdll!LdrpRunInitializeRoutines+0x1D9
0x6A20DD80: ntdll!LdrpInitializeProcess+0xAF6
--------------------------------------
Handle 0x7CC - OPEN:
0x8018F44A: ntoskrnl!ExCreateHandle+0x94
0x801E3180: ntoskrnl!ObpCreateHandle+0x304
0x801E1563: ntoskrnl!ObOpenObjectByName+0x1E9
0x77DBFCD6: KERNEL32!GetLocaleFileInfo+0x3D
0x77DBF942: KERNEL32!NlsProcessInitialize+0x11D
0x77E0C6DF: KERNEL32!NlsDllInitialize+0x35
0x6A20785C: ntdll!LdrpCallInitRoutine+0x14
0x6A205393: ntdll!LdrpRunInitializeRoutines+0x1D9
0x6A20DD80: ntdll!LdrpInitializeProcess+0xAF6
--------------------------------------
Parsed 0x1CA stack traces.
Dumped 0x2 stack traces.
有关进程 815328b0 中所有句柄的转储信息
kd> !htrace 0 81400300
Process 0x81400300
ObjectTable 0xE10CCF60
--------------------------------------
Handle 0x7CC - CLOSE:
0x8018FCB9: ntoskrnl!ExDestroyHandle+0x103
0x801E1D12: ntoskrnl!ObpCloseHandleTableEntry+0xE4
0x801E1DD9: ntoskrnl!ObpCloseHandle+0x85
0x801E1EDD: ntoskrnl!NtClose+0x19
0x010012C1: badhandle!mainCRTStartup+0xE3
0x77DE0B2F: KERNEL32!BaseProcessStart+0x3D
--------------------------------------
Handle 0x7CC - OPEN:
0x8018F44A: ntoskrnl!ExCreateHandle+0x94
0x801E3390: ntoskrnl!ObpCreateUnnamedHandle+0x10C
0x801E7317: ntoskrnl!ObInsertObject+0xC3
0x77DE23B2: KERNEL32!CreateSemaphoreA+0x66
0x010011C5: badhandle!main+0x45
0x010012C1: badhandle!mainCRTStartup+0xE3
0x77DE0B2F: KERNEL32!BaseProcessStart+0x3D
--------------------------------------
Handle 0x7DC - BAD REFERENCE:
0x8018F709: ntoskrnl!ExMapHandleToPointerEx+0xEA
0x801E10F2: ntoskrnl!ObReferenceObjectByHandle+0x12C
0x801902BE: ntoskrnl!NtSetEvent+0x6C
0x80154965: ntoskrnl!_KiSystemService+0xC4
0x010012C1: badhandle!mainCRTStartup+0xE3
0x77DE0B2F: KERNEL32!BaseProcessStart+0x3D
--------------------------------------
Handle 0x7DC - CLOSE:
0x8018FCB9: ntoskrnl!ExDestroyHandle+0x103
0x801E1D12: ntoskrnl!ObpCloseHandleTableEntry+0xE4
0x801E1DD9: ntoskrnl!ObpCloseHandle+0x85
0x801E1EDD: ntoskrnl!NtClose+0x19
0x010012C1: badhandle!mainCRTStartup+0xE3
0x77DE0B2F: KERNEL32!BaseProcessStart+0x3D
--------------------------------------
Handle 0x7DC - OPEN:
0x8018F44A: ntoskrnl!ExCreateHandle+0x94
0x801E3390: ntoskrnl!ObpCreateUnnamedHandle+0x10C
0x801E7317: ntoskrnl!ObInsertObject+0xC3
0x77DE265C: KERNEL32!CreateEventA+0x66
0x010011A0: badhandle!main+0x20
0x010012C1: badhandle!mainCRTStartup+0xE3
0x77DE0B2F: KERNEL32!BaseProcessStart+0x3D
--------------------------------------
Parsed 0x6 stack traces.
Dumped 0x5 stack traces.
有关在当前进程中处理 7DC 的转储信息
kd> !htrace 7DC
Process 0x81400300
ObjectTable 0xE10CCF60
--------------------------------------
Handle 0x7DC - BAD REFERENCE:
0x8018F709: ntoskrnl!ExMapHandleToPointerEx+0xEA
0x801E10F2: ntoskrnl!ObReferenceObjectByHandle+0x12C
0x801902BE: ntoskrnl!NtSetEvent+0x6C
0x80154965: ntoskrnl!_KiSystemService+0xC4
0x010012C1: badhandle!mainCRTStartup+0xE3
0x77DE0B2F: KERNEL32!BaseProcessStart+0x3D
--------------------------------------
Handle 0x7DC - CLOSE:
0x8018FCB9: ntoskrnl!ExDestroyHandle+0x103
0x801E1D12: ntoskrnl!ObpCloseHandleTableEntry+0xE4
0x801E1DD9: ntoskrnl!ObpCloseHandle+0x85
0x801E1EDD: ntoskrnl!NtClose+0x19
0x010012C1: badhandle!mainCRTStartup+0xE3
0x77DE0B2F: KERNEL32!BaseProcessStart+0x3D
--------------------------------------
Handle 0x7DC - OPEN:
0x8018F44A: ntoskrnl!ExCreateHandle+0x94
0x801E3390: ntoskrnl!ObpCreateUnnamedHandle+0x10C
0x801E7317: ntoskrnl!ObInsertObject+0xC3
0x77DE265C: KERNEL32!CreateEventA+0x66
0x010011A0: badhandle!main+0x20
0x010012C1: badhandle!mainCRTStartup+0xE3
0x77DE0B2F: KERNEL32!BaseProcessStart+0x3D
--------------------------------------
Parsed 0x6 stack traces.
Dumped 0x3 stack traces.
调试堆错误
堆验证程序调试程序扩展
堆验证程序调试程序扩展是 !堆扩展 (NT 堆调试器扩展) 的一部分。 可以使用 !heap -? 获取简单的帮助 还是更广泛的 !堆 -p -? . 当前扩展不会自行检测是否为进程启用了页面堆并执行相应的操作。 目前,扩展用户需要知道页面堆已启用,并使用前缀为 !heap -p 的命令。 有关 !htrace 扩展的其他信息,请参阅调试器文档中的 !heap 。
!heap -p
转储进程中创建的所有整页堆的地址。
!heap -p -h ADDRESS-OF-HEAP
在 ADDRESS-OF-HEAP 处完整转储整页堆。
!heap -p -a ADDRESS
尝试确定 ADDRESS 处是否存在堆块。 此值不需要是块开头的地址。 如果对内存区域的性质没有任何线索,则命令非常有用。
堆操作日志
堆操作日志跟踪所有堆例程。 其中包括 HeapAlloc、HeapReAlloc 和 HeapFree。
可以使用 !avrf -hp Length
扩展命令显示最后几条记录;Length 指定记录数。
可以使用 !avrf -hp -a Address
显示影响指定地址的所有堆空间操作。 对于分配操作,将 Address 包含在分配的堆块中就足够了。 对于自由操作,必须提供块开头的确切地址。
对于日志中的每个条目,将显示以下信息:
- 调用的堆函数。
- 调用例程的线程的线程 ID。
- 调用中涉及的地址 - 这是分配例程返回或传递给免费例程的地址。
- 调用中涉及的区域的大小。
- 调用的堆栈跟踪。
最先显示最新的条目。
在此示例中,将显示两个最新条目:
0:001> !avrf -hp 2
alloc (tid: 0xFF4):
address: 00ea2fd0
size: 00001030
00403062: Prymes!_heap_alloc_dbg+0x1A2
00402e69: Prymes!_nh_malloc_dbg+0x19
00402e1e: Prymes!_malloc_dbg+0x1E
00404ff3: Prymes!_stbuf+0xC3
00401c23: Prymes!printf+0x43
00401109: Prymes!main+0xC9
00402039: Prymes!mainCRTStartup+0xE9
77e7a278: kernel32!BaseProcessStart+0x23
alloc (tid: 0xFF4):
address: 00ea07d0
size: 00000830
00403062: Prymes!_heap_alloc_dbg+0x1A2
00402e69: Prymes!_nh_malloc_dbg+0x19
00402e1e: Prymes!_malloc_dbg+0x1E
00403225: Prymes!_calloc_dbg+0x25
00401ad5: Prymes!__initstdio+0x45
00401f38: Prymes!_initterm+0x18
00401da1: Prymes!_cinit+0x21
00402014: Prymes!mainCRTStartup+0xC4
77e7a278: kernel32!BaseProcessStart+0x23
典型调试方案
可能会遇到几种故障情况。 其中一些人需要相当多的侦探工作才能获得全貌。
不可访问页面中的访问冲突
如果测试的应用程序访问超出缓冲区末尾,则启用整页堆时会发生这种情况。 如果它触及释放的块,也可能发生此情况。 若要了解发生异常的地址的性质,需要使用:
!heap –p –a ADDRESS-OF-AV
损坏的阻止消息
在分配 (分配的生存期内的几个时刻,用户免费、真正的免费) 页面堆管理器会检查块是否保留所有填充模式以及块标头是否具有一致的数据。 如果不是这种情况,你将得到验证程序停止。
例如,如果块是整页堆块 (,则如果你确定已为所有分配启用了整页堆) 则可以使用“!堆 –p –A ADDRESS”来了解块的特征。
如果块是浅色页堆块,则需要找出块标头的起始地址。 可以通过在报告地址下方转储 30-40 个字节来查找起始地址,并查找块标头 (ABCDAAAA、ABCDBBBB、ABCDAAA9、ABCDBBBA) 的神奇开始/结束模式。
标头将提供了解失败所需的所有信息。 特别是,magic 模式将指示块是分配的还是释放的,它是浅页堆还是整页堆块。 此处的信息必须与有问题的调用仔细匹配。
例如,如果调用 HeapFree 时使用块的地址加上四个字节,则会收到损坏的消息。 块标头看起来正常,但你必须注意到,标头末尾后的第一个字节 (0xDCBAXXXX magic 值后的第一个字节,) 具有不同的地址,然后是调用中的地址。
特殊填充指针
页面堆管理器使用显示为内核指针的值填充用户分配。 如果块被释放, (填充值为 F0) ,并且分配块但未发出将块归零的请求, (填充值为 E0(对于浅页堆)和 C0(对于整页堆) )时,就会发生这种情况。 非归零分配对于 malloc/新用户来说是典型的。 如果在F0F0F0F0、E0E0E0E0、C0C0C0C0等地址尝试读/写) 出现失败 (访问冲突,则很可能遇到其中一种情况。
F0F0F0F0处的读/写表示块在释放后已被使用。 不幸的是,你将需要一些侦探工作,找出哪个块导致了这种情况。 需要获取失败的堆栈跟踪,然后检查堆栈上函数的代码。 其中一个可能会对分配处于活动状态做出错误的假设。
E0E0E0E0/C0C0C0C0 处的读/写表示应用程序未正确初始化分配。 这还需要对当前堆栈跟踪中的函数进行代码检查。 下面是此类故障的示例。 在测试过程中,注意到在地址E0E0E0E0上执行 HeapFree 时发生访问冲突。 事实证明,测试分配了 一个 结构,没有正确初始化它,然后调用了 对象的析构函数。 由于某个字段不为 null (因此在调用 delete) ,该字段中E0E0E0E0。
页堆技术详细信息
为了检测堆损坏 (溢出或下溢) ,AppVerifier 将修改分配内存的方式,方法是使用完整的不可写页填充请求的内存,或者在分配的内存之前和之后使用特殊标记填充请求的内存。 为此,AppVerifier 将 Verifier.dll 加载到要验证的进程中,并将应用程序调用的某些 Win32 堆 API 重定向到相应的 Verifier.dll API。
在请求的内存中填充完整的不可写页面 (在页面堆属性部分中启用 FULL 设置,并且是默认设置) 时,AppVerifier 将消耗大量虚拟内存,但优点是发生溢出或下溢时,堆损坏事件会实时缓存。 请记住,此模式下的内存如下所示[AppVerifier Read-Only 堆页 (4k) ][受测应用程序请求的内存量]或如下所示[受测应用程序请求的内存量][AppVerifier Read-Only 堆页 (4k) ]。
堆检查将在分配的开头或末尾放置一个保护页,具体取决于向后属性。 如果“向后”设置为 False(默认值),则会在分配的末尾放置一个保护页,以捕获缓冲区溢出。 如果设置为 True,则会将防护页放置在分配的开头,以捕获缓冲区不足。
使用特殊标记填充请求的内存时, (清除堆属性) 中的“完整”检查框项,AppVerifier 将检查并在释放此内存时发出警报。 使用此方法main问题在于,在某些情况下,仅当释放内存时才会检测到内存损坏 (最小内存块量为 8 字节) ,因此,当在 3 字节变量或发生 5 字节溢出时,不会立即检测到内存损坏。
在下溢事件中,将尝试写入 Read-Only 页。 这将触发异常。 请注意,只有在调试器下执行目标应用程序时,才能捕获此异常。 请注意,整页堆模式也将检测这些错误,因为它使用填充+保护页。 使用浅页堆的原因是,如果计算机不能容忍整页堆的高内存约束。
对于内存密集型应用程序,或者当需要长时间使用 AppVerifier ((例如,压力测试) )时,最好运行正常 (轻型) 堆测试,而不是由于性能下降而运行完整模式。 但是,遇到问题时,请打开整页堆以进一步调查。
使用自定义堆 (绕过操作系统实现堆) 的堆的应用程序可能无法充分利用使用页面堆,甚至可能在启用后出现故障。
调试内存错误
内存验证程序调试器扩展
虚拟空间操作日志跟踪以任何方式修改进程的虚拟空间的所有例程。 其中包括 VirtualAlloc、VirtualFree、MapViewOfFile 和 UnmapViewOfFile。
可以使用 !avrf -vs Length
扩展命令显示最后几条记录;Length 指定记录数。
可以使用 !avrf -vs -a Address 显示影响指定地址的所有虚拟空间操作。 对于分配,将 Address 包含在分配的块中就足够了。 对于免费,必须提供区域开头的确切地址。
对于日志中的每个条目,将显示以下信息:
- 调用的函数
- 调用例程的线程的线程 ID
- 调用中涉及的地址 - 这是分配例程返回或传递给免费例程的地址
- 调用中涉及的区域的大小
- AllocationType 参数 (内存操作的类型)
- 请求的保护类型
- 调用的堆栈跟踪
示例
首先显示最新的条目。
在以下示例中,显示两个最新条目:
0:001> !avrf -vs 2
VirtualFree (tid: 0xB4): addr:04bb0000 sz:00400000 op:8000 prot:0
00aa1ac2: verifier!VsLogCall+0x42
00aa19c1: verifier!AVrfpNtFreeVirtualMemory+0x30
68925d17: kernel32!VirtualFreeEx+0x35
6892611c: kernel32!VirtualFree+0x13
75ef6525: mshtml+0x116525
75ef68af: mshtml+0x1168AF
6a20787c: ntdll!LdrpCallInitRoutine+0x14
6a211c6f: ntdll!LdrUnloadDll+0x39A
689275c1: kernel32!FreeLibrary+0x3B
77b22d69: ole32!CoQueryReleaseObject+0x1E6
77b02bd2: ole32!SetErrorInfo+0x1ED
VirtualFree (tid: 0xB4): addr:04bb0000 sz:00001000 op:4000 prot:0
00aa1ac2: verifier!VsLogCall+0x42
00aa19c1: verifier!AVrfpNtFreeVirtualMemory+0x30
68925d17: kernel32!VirtualFreeEx+0x35
6892611c: kernel32!VirtualFree+0x13
75ef65ae: mshtml+0x1165AE
75ef68af: mshtml+0x1168AF
6a20787c: ntdll!LdrpCallInitRoutine+0x14
6a211c6f: ntdll!LdrUnloadDll+0x39A
689275c1: kernel32!FreeLibrary+0x3B
77b22d69: ole32!CoQueryReleaseObject+0x1E6
77b02bd2: ole32!SetErrorInfo+0x1ED
从输出中可以看出,线程0xB4先取消提交页面,然后释放整个虚拟区域。
下面是影响地址0x4BB1000的所有操作的显示:
0:001> !avrf -vs -a 4bb1000
Searching in vspace log for address 04bb1000 ...
VirtualFree (tid: 0xB4): addr:04bb0000 sz:00400000 op:8000 prot:0
00aa1ac2: verifier!VsLogCall+0x42
00aa19c1: verifier!AVrfpNtFreeVirtualMemory+0x30
68925d17: kernel32!VirtualFreeEx+0x35
6892611c: kernel32!VirtualFree+0x13
75ef6525: mshtml+0x116525
75ef68af: mshtml+0x1168AF
6a20787c: ntdll!LdrpCallInitRoutine+0x14
6a211c6f: ntdll!LdrUnloadDll+0x39A
689275c1: kernel32!FreeLibrary+0x3B
77b22d69: ole32!CoQueryReleaseObject+0x1E6
77b02bd2: ole32!SetErrorInfo+0x1ED
VirtualFree (tid: 0xB4): addr:04bb1000 sz:00001000 op:4000 prot:0
00aa1ac2: verifier!VsLogCall+0x42
00aa19c1: verifier!AVrfpNtFreeVirtualMemory+0x30
68925d17: kernel32!VirtualFreeEx+0x35
6892611c: kernel32!VirtualFree+0x13
75ef65ae: mshtml+0x1165AE
75ef68af: mshtml+0x1168AF
6a20787c: ntdll!LdrpCallInitRoutine+0x14
6a211c6f: ntdll!LdrUnloadDll+0x39A
689275c1: kernel32!FreeLibrary+0x3B
77b22d69: ole32!CoQueryReleaseObject+0x1E6
77b02bd2: ole32!SetErrorInfo+0x1ED
VirtualAlloc (tid: 0xB4): addr:04bb0000 sz:00010000 op:1000 prot:4
00aa1ac2: verifier!VsLogCall+0x42
00aa1988: verifier!AVrfpNtAllocateVirtualMemory+0x37
68925ca3: kernel32!VirtualAllocEx+0x61
68926105: kernel32!VirtualAlloc+0x16
75ef63f3: mshtml+0x1163F3
VirtualAlloc (tid: 0xB4): addr:04bb0000 sz:00400000 op:2000 prot:4
00aa1ac2: verifier!VsLogCall+0x42
00aa1988: verifier!AVrfpNtAllocateVirtualMemory+0x37
68925ca3: kernel32!VirtualAllocEx+0x61
68926105: kernel32!VirtualAlloc+0x16
75ef63d9: mshtml+0x1163D9
若要读取此输出,请记住,从最近的一个开始转储条目。 因此,此日志显示线程0xB4分配了提交页面的大区域。 稍后,它取消提交页面,然后释放整个虚拟区域。