使用内核调试来调试设备基本组件可靠性测试失败

本文介绍如何使用常见的内核调试命令来调试设备基础功能可靠性测试失败。

设置符号

你可以在 Microsoft 公共符号服务器站点上找到 Windows Hardware Lab Kit 内容符号。 有关 Microsoft 公共符号服务器的详细信息,请参阅使用 Microsoft 符号服务器获取调试符号文件。 你可以通过运行 .sympath(设置符号路径)命令在内核调试器中设置符号。

示例:

在此示例中,.sympath 命令将在调试器中设置公共符号服务器路径。

.sympath SRV*c:\localsymbols*https://msdl.microsoft.com/download/symbols

!analyze -v

从内核调试器调查由系统 bug 检查引起的测试失败时,应在设置符号后发出的第一个命令是 !analyze。 此命令可标识 bug 检查代码、bug 检查原因以及显示故障组件的堆栈跟踪。 有关此命令的详细信息,请参阅使用 !analyze 扩展

检查测试进程的堆栈跟踪

设备基础功能可靠性测试通常在测试计算机上以 Te.ProcessHost.exe 或 Te.exe 的形式运行。 调查系统 bug 检查或测试挂起时,查看来自这些测试进程的堆栈跟踪很有用。 对于 bug 检查,堆栈跟踪可以帮助识别崩溃时正在测试的测试用例。 对于测试挂起,堆栈跟踪可识别任何阻止测试进行的测试线程。

你可以使用 !process 0 0 扩展列出测试计算机上正在运行的所有进程,以查找测试进程 EPROCESS 块地址。

然后,可以使用 !process /p /r 扩展从测试进程中获取完整的堆栈跟踪。

有关 !process 扩展的详细信息,请参阅 !process.process(设置进程上下文)

请注意,!process 输出包含进程中运行的每个线程的滴答计数。 调查测试挂起时,应仔细检查在堆栈中包含 WDTF 组件(即,堆栈中以“WDTF”开头的模块名称)且滴答计数较高的线程,因为这些线程可能会导致测试永久挂起并最终因超时而失败。

示例:

在此示例中,!process 0 0、!process /p /r 和 !process 扩展识别出一个滴答计数非常高的测试线程,该线程会阻止测试进行:

!process 0 0 Te.ProcessHost.exe 
    PROCESS fffffa80093c6340
    SessionId: 1 Cid: 1320 Peb: 7f6595b3000 ParentCid: 12a0
    DirBase: 21eee000 ObjectTable: fffff8a0035b0a00 HandleCount: 327. 
    Image: TE. ProcessHost.exe
.process /p /r fffffa80093c6340
!process fffffa80093c6340 


        THREAD fffffa800b2be8c0  Cid 0964.0eac  Teb: 000007f601ba6000 Win32Thread: 0000000000000000 WAIT: (UserRequest) UserMode Non-Alertable
            fffffa800b2a11d0  SynchronizationEvent
            fffffa800b300640  SynchronizationEvent
        Not impersonating
        DeviceMap                 fffff8a0014b9c80
        Owning Process            fffffa800b302940       Image:         TE.exe
        Attached Process          N/A            Image:         N/A
        Wait Start TickCount      210995         Ticks: 405945 (0:01:45:32.782)
        Context Switch Count      51             IdealProcessor: 2             
        UserTime                  00:00:00.015
        KernelTime                00:00:00.015
        Win32 Start Address WDTFInterfaces!TsSingleWorkerThread (0x000007fe3a567f28)
        Stack Init fffff8800eb5edd0 Current fffff8800eb5dee0
        Base fffff8800eb5f000 Limit fffff8800eb59000 Call 0
        Priority 9 BasePriority 8 UnusualBoost 0 ForegroundBoost 0 IoPriority 2 PagePriority 5
        Kernel stack not resident.
        Child-SP          RetAddr           Call Site
        fffff880`0eb5df20 fffff803`78b27f7c nt!KiSwapContext+0x76
        (Inline Function) --------`-------- nt!KiSwapThread+0xf4 (Inline Function @ fffff803`78b27f7c)
        fffff880`0eb5e060 fffff803`78aaf4ab nt!KiCommitThreadWait+0x23c
        fffff880`0eb5e120 fffff803`78b257a0 nt!KiWaitForAllObjects+0x3bb
        fffff880`0eb5e3c0 fffff803`78ecb3dc nt!KeWaitForMultipleObjects+0x4ae
        fffff880`0eb5e470 fffff803`78ecb853 nt!ObWaitForMultipleObjects+0x29c
        fffff880`0eb5e980 fffff803`78aff053 nt!NtWaitForMultipleObjects+0xe3
        fffff880`0eb5ebd0 000007fe`45d2315b nt!KiSystemServiceCopyEnd+0x13 (TrapFrame @ fffff880`0eb5ec40)
        00000083`7cdef148 000007fe`430912c6 ntdll!ZwWaitForMultipleObjects+0xa
        00000083`7cdef150 000007fe`368641b5 KERNELBASE!WaitForMultipleObjectsEx+0xe5
        00000083`7cdef430 000007fe`3a566793 WDTFAudioSimpleIoAction!CAudioImpl::RunIO+0x3d1
        00000083`7cdef520 000007fe`3a566ea0 WDTFInterfaces!CSimpleIOEx::PerformIO+0x10f
        00000083`7cdef5b0 000007fe`3a56706b WDTFInterfaces!CSimpleIOExWrap::PerformIO+0x28
        00000083`7cdef5e0 000007fe`3a553fe5 WDTFInterfaces!CMTest_Receiver::Run+0x77
        00000083`7cdefe20 000007fe`3a5578ac WDTFInterfaces!CSimpleIO_MTestEx::ActionThread+0x105
        00000083`7cdefeb0 000007fe`3a567f3e WDTFInterfaces!CMTEXThread::ThreadWorker+0xc
        00000083`7cdefee0 000007fe`4319167e WDTFInterfaces!TsSingleWorkerThread+0x16
        00000083`7cdeff20 000007fe`45d3c3f1 KERNEL32!BaseThreadInitThunk+0x1a
        00000083`7cdeff50 00000000`00000000 ntdll!RtlUserThreadStart+0x1d

将上下文切换到线程和帧以查看局部变量

若要从堆栈帧查看局部变量,必须执行以下操作:

  1. 使用 .thread(设置寄存器上下文)命令将上下文切换到线程。

  2. 使用 kn 命令转储堆栈和帧编号(请参阅堆栈和转储日志记录)。

  3. 使用 .frame(设置本地上下文)命令切换到帧上下文。

  4. 使用 dv(显示局部变量)命令查看局部变量。

注意

必须使用专用符号转储局部变量。

示例:

此示例使用 .thread、kn、.frame 和 dv 命令从堆栈帧转储局部变量:

3: kd> .thread fffffa8009da7b00
Implicit thread is now fffffa80`09da7b00

3: kd> kn
  *** Stack trace for last set context - .thread/.cxr resets it
# Child-SP          RetAddr           Call Site
00 fffff880`054a03a0 fffff801`05caaf7c nt!KiSwapContext+0x76
01 fffff880`054a04e0 fffff801`05ca8d9f nt!KiCommitThreadWait+0x23c
02 fffff880`054a05a0 fffff801`05f98841 nt!KeWaitForSingleObject+0x1cf
03 fffff880`054a0630 fffff801`061f253d nt!IopCancelAlertedRequest+0x71
04 fffff880`054a0670 fffff801`0604fc5d nt! ?? ::NNGAKEGL::`string'+0x1caaf
05 fffff880`054a0860 fffff801`060552b8 nt!ObpLookupObjectName+0x7a1
06 fffff880`054a0990 fffff801`06066ebe nt!ObOpenObjectByName+0x258
07 fffff880`054a0a60 fffff801`06067609 nt!IopCreateFile+0x37c
08 fffff880`054a0b00 fffff801`05c82053 nt!NtCreateFile+0x79
09 fffff880`054a0b90 000007fa`1a4930fa nt!KiSystemServiceCopyEnd+0x13
0a 00000038`d21bb478 000007fa`0677feef ntdll!NtCreateFile+0xa
0b 00000038`d21bb480 000007fa`0678e9a2 WDTFFuzzTestAction!DPETryOpenDevice+0x2b3
0c 00000038`d21bcde0 000007fa`0678e892 WDTFFuzzTestAction!CWDTFFuzz_MTestImpl::AttemptToOpenSurface+0x66
0d 00000038`d21bce50 000007fa`0678b84c WDTFFuzzTestAction!CWDTFFuzz_MTestImpl::FindAttackSurfaces+0x16e
0e 00000038`d21bf6c0 000007fa`0678a4d9 WDTFFuzzTestAction!CWDTFFuzz_MTestImpl::ActionThread+0x200
0f 00000038`d21bf760 000007fa`1a17167e WDTFFuzzTestAction!ActionThreadStart+0x9
10 00000038`d21bf790 000007fa`1a4ac3f1 KERNEL32!BaseThreadInitThunk+0x1a
11 00000038`d21bf7c0 00000000`00000000 ntdll!RtlUserThreadStart+0x1d

3: kd> .frame b
0b 00000038`d21bb480 000007fa`0678e9a2 WDTFFuzzTestAction!DPETryOpenDevice+0x2b3

3: kd> dv
szName = 0x00000038`d21bce90
bSync = 0n0
ppdevice = 0x00000038`d21bce10
messageBuffer = wchar_t [2048] "Attempting to open device : \DosDevices\root#multiportserial#0000#{05caff94-7b1e-420c-8c70-d8361bc4ee0a}"
oa = struct _OBJECT_ATTRIBUTES

!pnptriage

调试设备基础功能可靠性测试挂起时,可以使用 !pnptriage 命令列出活动的 PNP 线程。 请注意,!pnptriage 输出包含系统中运行的每个 PNP 线程的滴答计数。 调查测试挂起时,应仔细检查滴答计数较高的线程,因为这些线程可能会导致测试永久挂起并最终因超时而失败。

驱动程序调试扩展

以下内核调试器扩展可以调试运行设备基础功能可靠性测试时可能出现的驱动程序问题:!drvobj、!devnode、!devobj、!devstack 和 !irp。 有关这些扩展的详细信息,请参阅内核模式扩展

调试器扩展

Windows 调试工具附带额外的调试器扩展,这些扩展有助于排查对以下类型的驱动程序运行测试时可能发生的失败问题:USB、存储、NDIS、图形、内核模式驱动程序框架 (KMDF) 和用户模式驱动程序框架 (UMDF)。 有关这些扩展的详细信息,请参阅专用扩展。 有关 Windows 调试工具的详细信息,请参阅下载和安装 Windows 调试工具

使用 Windows HLK 排查设备基本组件可靠性测试问题