调试 Windows 驱动程序分步实验室(Echo 内核模式)

此实验室引入了 WinDbg 内核调试器。 使用 WinDbg 调试 echo 内核模式示例驱动程序代码。

实验室目标

本实验室包括介绍调试工具、教授常见调试命令、说明断点的使用以及演示如何使用调试扩展的练习。

在此实验室中,您将使用实时内核调试连接来探索以下操作:

  • 使用 Windows 调试器命令
  • 使用标准命令(调用堆栈、变量、线程、IRQL)
  • 使用高级驱动程序调试命令 (!commands)
  • 使用符号
  • 在实时调试中设置断点
  • 查看调用堆栈
  • 显示即插即用设备树
  • 使用线程和进程上下文

用户和内核模式调试

使用 Windows 调试器时,可以执行两种类型的调试:

用户模式 - 应用程序和子系统在用户模式下在计算机上运行。 在用户模式下运行的进程在其自己的虚拟地址空间中运行。 他们被限制直接访问系统的许多部分,其中包括系统硬件、未分配给他们使用的内存,以及系统中可能影响系统完整性的其他部分。 由于在用户模式下运行的进程与系统和其他用户模式进程有效隔离,因此它们不会干扰这些资源。

内核模式 - 操作系统和特权程序在内核模式下运行。 内核模式代码有权访问系统的任何部分。 它不像用户模式代码那样受限制。 它可以访问在用户模式或内核模式下运行的任何其他进程的任意部分。 大部分核心 OS 功能和许多硬件设备驱动程序在内核模式下运行。

本练习介绍在用户模式和内核模式调试期间经常使用的调试命令。 本练习还介绍了调试扩展(有时称为 "bang" !commands)用于内核模式调试。

实验室设置

您需要以下硬件才能完成实验:

  • 运行 Windows 10 的笔记本电脑或台式计算机(主机)
  • 运行 Windows 10 的第二台笔记本电脑或台式计算机(目标)
  • 用于连接两台计算机的网络中心或路由器和网络电缆
  • 访问 Internet 以下载符号文件

需要以下软件才能完成实验:

  • Visual Studio
  • 适用于 Windows 10 的 Windows 软件开发工具包 (SDK)
  • 适用于 Windows 10 的 Windows 驱动程序工具包 (WDK)
  • 适用于 Windows 10 的示例 echo 驱动程序

实验室包含以下部分:

连接到内核模式 WinDbg 会话

在本部分中,在主机和目标系统上配置网络调试。

此实验室中的计算机需要配置为使用以太网网络连接进行内核调试。

此实验室使用两台计算机。 Windows 调试器在 主机 系统上运行,内核模式驱动程序框架(KMDF)回显驱动程序在 目标 系统上运行。

使用网络中心或路由器和网络电缆连接这两台计算机。

示意图,说明通过网络中心或路由器连接的两台计算机。

若要使用内核模式应用程序并使用 WinDbg,建议通过以太网传输使用 KDNET。 有关如何使用以太网传输协议的信息,请参阅 内核模式下的 WinDbg 入门。 有关设置目标计算机的详细信息,请参阅为手动部署驱动程序准备计算机以及自动设置 KDNET 网络内核调试

使用以太网配置内核模式调试

若要在目标系统上启用内核模式调试,请执行以下操作:

  1. 在主机系统上,打开命令提示符窗口并输入 ipconfig 以确定其 IPv4 地址。

    Windows IP Configuration
    Ethernet adapter Ethernet:
       Connection-specific DNS Suffix  . :
       Link-local IPv6 Address . . . . . : fe80::c8b6:db13:d1e8:b13b%3
       Autoconfiguration IPv4 Address. . : 169.182.1.1
       Subnet Mask . . . . . . . . . . . : 255.255.0.0
       Default Gateway . . . . . . . . . :
    
  2. 记录主机系统的 IP 地址:______________________________________

  3. 在目标系统上,打开命令提示符窗口,并使用 ping 命令确认两个系统之间的网络连接。

    ping 169.182.1.1
    

    使用记录的主机系统的实际 IP 地址,而不是示例输出中显示的 169.182.1.1。

    Pinging 169.182.1.1 with 32 bytes of data:
    Reply from 169.182.1.1: bytes=32 time=1ms TTL=255
    Reply from 169.182.1.1: bytes=32 time<1ms TTL=255
    Reply from 169.182.1.1: bytes=32 time<1ms TTL=255
    Reply from 169.182.1.1: bytes=32 time<1ms TTL=255
    
    Ping statistics for 169.182.1.1:
        Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
    Approximate round trip times in milli-seconds:
        Minimum = 0ms, Maximum = 1ms, Average = 0ms
    

通过完成以下步骤,在目标系统上启用内核模式调试。

重要

在使用 BCDEdit 更改启动信息之前,可能需要在测试计算机上暂时挂起 Windows 安全功能,例如 BitLocker 和安全启动。 测试完成后重新启用这些安全功能。 禁用安全功能时,请适当地管理测试计算机。 安全启动通常在 UEFI 中禁用。 若要访问 UEFI 设置,请使用系统、恢复和高级启动。 重启后,选择“故障排除”、“高级选项”、“UEFI 固件设置”。 请谨慎使用,因为错误地设置 UEFI 选项或禁用 BitLocker 可能会导致系统不可操作。

  1. 在目标计算机上,以管理员身份打开命令提示符窗口。 输入此命令以启用调试:

    bcdedit /set {default} DEBUG YES
    
  2. 输入此命令以启用测试签名:

    bcdedit /set TESTSIGNING ON 
    
  3. 输入此命令以设置主机系统的 IP 地址。 使用前面记录的主机系统的 IP 地址,而不是显示的主机系统的 IP 地址。

    bcdedit /dbgsettings net hostip:192.168.1.1 port:50000 key:2steg4fzbj2sz.23418vzkd4ko3.1g34ou07z4pev.1sp3yo9yz874p
    

    警告

    若要提高连接的安全性并降低随机客户端调试器连接请求的风险,请使用自动生成的随机密钥。 有关详细信息,请参阅自动设置 KDNET 网络内核调试

  4. 输入此命令以确认正确设置了 dbgsettings 的值:

    bcdedit /dbgsettings
    
    key                     2steg4fzbj2sz.23418vzkd4ko3.1g34ou07z4pev.1sp3yo9yz874p
    debugtype               NET
    hostip                  169.168.1.1
    port                    50000
    dhcp                    Yes
    The operation completed successfully.
    

    注意

    如果收到来自防火墙的消息,并且想要使用调试器,请选择所有三个框。

    Windows 安全警报对话框的屏幕截图,指示 Windows 防火墙阻止了应用的一些功能。

  5. 在主计算机上,以管理员身份打开命令提示符窗口。 此实验室使用 Windows 驱动程序工具包 (WDK) 中 WinDbg.exe 的 x64 版本,该版本已作为 Windows 工具包安装的一部分安装。 更改为默认 WinDbg 目录,默认位置如下所示。

    cd C:\Program Files(x86)\Windows Kits\10\Debuggers\x64 
    

    此实验室假定两台计算机在目标和主机上运行 64 位版本的 Windows。 如果情况并非如此,最好的办法是在主机上运行与目标运行相同位数的工具。 例如,如果目标运行 32 位 Windows,请在主机上运行 32 位版本的调试器。 有关详细信息,请参阅 选择 32 位或 64 位调试工具

  6. 使用以下命令通过远程用户调试来打开 WinDbg。 密钥和端口的值与之前在目标计算机上使用 BCDEdit 设置的值匹配。

    WinDbg –k net:port=50000,key=2steg4fzbj2sz.23418vzkd4ko3.1g34ou07z4pev.1sp3yo9yz874p
    
  7. 重启目标系统。

  8. 在一两分钟内,应在主机系统上显示调试输出。

    Microsoft (R) Windows Debugger Version 10.0.17074.1002 AMD64
    Copyright (c) Microsoft Corporation. All rights reserved.
    
    Using NET for debugging
    Opened WinSock 2.0
    Waiting to reconnect...
    Connected to target 169.182.1.1 on port 50005 on local IP 169.182.1.2
    You can get the target MAC address by running .kdtargetmac command.
    Connected to Windows 10 16299 x64 target at (Wed Feb 28 17:16:23.051 2018 (UTC - 8:00)), ptr64 TRUE
    Kernel Debugger connection established.  (Initial Breakpoint requested)
    Symbol search path is: srv*
    Executable search path is: 
    Windows 10 Kernel Version 16299 MP (4 procs) Free x64
    Product: WinNt, suite: TerminalServer SingleUserTS
    Built by: 16299.15.amd64fre.rs3_release.170928-1534
    Machine Name:
    Kernel base = 0xfffff800`9540d000 PsLoadedModuleList = 0xfffff800`95774110
    Debug session time: Wed Feb 28 17:16:23.816 2018 (UTC - 8:00)
    System Uptime: 0 days 0:00:20.534
    

“调试器命令”窗口是 WinDbg 中的主要调试信息窗口。 可以输入调试器命令并在此窗口中查看命令输出。

调试器命令窗口拆分为两个窗格。 在较小的窗格中输入命令,即窗口底部的命令输入窗格,并在窗口顶部的较大窗格中查看命令输出。

在命令条目窗格中,使用向上键和向下键滚动浏览命令历史记录。 出现命令时,可以对其进行编辑或按 Enter 运行命令。

内核模式调试命令和技术

在本部分中,使用调试命令显示有关目标系统的信息。

某些调试命令使用调试器标记语言(DML)显示文本,可以选择以快速收集更多信息。

  1. 在主机系统上,使用 WinDBg 中的 Ctrl+Scroll Lock 闯入目标系统上运行的代码。 目标系统可能需要一些时间才能做出响应。

    调试器中的主屏幕,其中显示了实时内核连接的命令窗口输出。

  2. 在调试器命令窗口中输入以下命令以启用 DML:

    0: kd> .prefer_dml 1
    DML versions of commands on by default
    
  3. 可以使用 .hh 命令访问参考命令帮助。 输入以下命令以查看 .prefer_dml的命令参考帮助:

    0: kd> .hh .prefer_dml
    

    调试器帮助文件显示了 .prefer_dml 命令的帮助。

    调试器帮助应用程序的屏幕截图,其中显示了 .prefer-dml 命令的帮助。

  4. 若要显示目标系统上的详细版本信息,请在 WinDbg 窗口中输入 vertarget(显示目标计算机版本) 命令:

    0: kd> vertarget
    Windows 10 Kernel Version 9926 MP (4 procs) Free x64
    Product: WinNt, suite: TerminalServer SingleUserTS
    Built by: 9926.0.amd64fre.fbl_awesome1501.150119-1648
    Machine Name: ""
    Kernel base = 0xfffff801`8d283000 PsLoadedModuleList = 0xfffff801`8d58aef0
    Debug session time: Fri Feb 20 10:15:17.807 2015 (UTC - 8:00)
    System Uptime: 0 days 01:31:58.931
    
  5. 若要验证你正在使用正确的内核模式进程,请在 WinDbg 窗口中输入 lm (列出加载的模块) 命令以显示加载的模块:

    0: Kd> lm
    start             end                 module name
    fffff801`09200000 fffff801`0925f000   volmgrx    (no symbols)
    fffff801`09261000 fffff801`092de000   mcupdate_GenuineIntel   (no symbols)
    fffff801`092de000 fffff801`092ec000   werkernel   (export symbols)       werkernel.sys
    fffff801`092ec000 fffff801`0934d000   CLFS       (export symbols)       CLFS.SYS
    fffff801`0934d000 fffff801`0936f000   tm         (export symbols)       tm.sys
    fffff801`0936f000 fffff801`09384000   PSHED      (export symbols)       PSHED.dll
    fffff801`09384000 fffff801`0938e000   BOOTVID    (export symbols)       BOOTVID.dll
    fffff801`0938e000 fffff801`093f7000   spaceport   (no symbols)
    fffff801`09400000 fffff801`094cf000   Wdf01000   (no symbols)
    fffff801`094d9000 fffff801`09561000   CI         (export symbols)       CI.dll
    ...
    

    在本实验中,用“...”表示省略的输出。

  6. 要获取特定模块的详细信息,请使用 v(详细)选项:

    0: Kd> lm v m tcpip
    Browse full module list
    start             end                 module name
    fffff801`09eeb000 fffff801`0a157000   tcpip      (no symbols)           
        Loaded symbol image file: tcpip.sys
        Image path: \SystemRoot\System32\drivers\tcpip.sys
        Image name: tcpip.sys
        Browse all global symbols  functions  data
        Timestamp:        Sun Nov 09 18:59:03 2014 (546029F7)
        CheckSum:         00263DB1
        ImageSize:        0026C000
        Translations:     0000.04b0 0000.04e4 0409.04b0 0409.04e4
    
    Unable to enumerate user-mode unloaded modules, Win32 error 0n30
    

    未设置符号路径和加载符号,因此调试器中能提供的信息非常有限。

下载并构建 KMDF echo 驱动程序

在本部分中,下载并生成 KMDF echo 驱动程序。

通常,在使用 WinDbg 时,您将使用您自己的驱动程序代码。 为了熟悉 WinDbg 操作,此实验室使用 KMDF 模板“Echo”示例驱动程序。 源代码可用于帮助了解 WinDbg 中显示的信息。 此示例还用于说明如何单步执行本机内核模式代码。 此方法对于调试复杂的内核模式代码问题非常有用。

下载并生成 Echo 示例驱动程序:

  1. 首先,从 GitHub 下载并提取 KMDF Echo 示例

    KMDF Echo 示例位于 常规 文件夹。

    GitHub windows-driver-samples 页面的屏幕截图,其中突出显示了常规文件夹和下载 zip 按钮。

    1. 在一个 zip 文件中下载驱动程序示例:驱动程序示例

    2. 将 zip 文件下载到本地硬盘驱动器。

    3. 选中并按住或右键单击 zip 文件,然后选择全部解压缩。 指定新文件夹,或浏览到现有文件夹以存储提取的文件。 例如,可以将 C:\DriverSamples\ 指定为要在其中提取文件的新文件夹。

    4. 提取文件后,转到以下子文件夹:C:\DriverSamples\general\echo\kmdf

  2. 在 Microsoft Visual Studio 中,选择 文件>打开>项目/解决方案...,然后转到包含提取文件的文件夹,例如,C:\DriverSamples\general\echo\kmdf。 双击 kmdfecho 解决方案文件,将其打开。

    在 Visual Studio 中,找到解决方案资源管理器。 如果此窗口尚未打开,请从 视图 菜单中选择 解决方案资源管理器。 在解决方案资源管理器中,你会看到一个包含三个项目的解决方案。

    Visual Studio 的屏幕截图,其中显示了从 kmdfecho 项目加载的 device.c 文件。

  3. 设置示例的配置和平台。 在解决方案资源管理器中,选择并按住或右键单击 解决方案“kmdfecho”(3 个项目),然后选择 Configuration Manager。 确保这三个项目的配置和平台设置相同。 默认情况下,配置设置为 Win10 调试,平台设置为所有项目的 Win64。 如果对一个项目进行任何配置或平台更改,请对其余三个项目进行相同的更改。

  4. 需要修改驱动程序示例,以使用不与现有驱动程序重叠的值。 请参阅 从示例代码到生产驱动程序 - 示例中的更改,以了解如何创建一个能够与 Windows 中已安装的真实驱动程序共存的独特驱动程序示例。

  5. 设置运行时库。 打开 echo 驱动程序属性页面,找到 C/C++>代码生成。 将运行时库更改为多线程调试(/MTd)。 有关生成选项的详细信息,请参阅 /MD、/MT、/LD(使用 Run-Time 库)

    Visual Studio 中 echo 属性页面的屏幕截图,其中突出显示了运行时库设置。

  6. 在驱动程序属性中,确保驱动程序签名>签名模式被设置为测试签名

    Visual Studio 中 echo 属性页面的屏幕截图,其中突出显示了签名模式设置。

  7. 在 Visual Studio 中,选择生成>生成解决方案

    生成窗口应显示一条消息,指示所有三个项目的生成成功。

提示

如果遇到生成错误消息,请使用生成错误号来找到解决方案。 例如,MSBuild error MSB8040 描述了如何使用 Spectre 已缓解的库。

  1. 在文件资源管理器中,转到包含示例提取文件的文件夹。 例如,如果这是前面指定的文件夹,请转到 C:\DriverSamples\general\echo\kmdf。 在该文件夹中,编译的驱动程序文件的位置因在 Configuration Manager 中选择的配置和平台设置而异。 如果保留默认设置不变,则编译的驱动程序文件将保存到名为 <\x64\Debug 的文件夹,以便进行 64 位调试生成。

    转到包含 Autosync 驱动程序的生成文件的文件夹:C:\DriverSamples\general\echo\kmdf\driver\AutoSync\x64\Debug

    文件夹应包含以下文件:

    文件 描述
    Echo.sys 驱动程序文件。
    Echo.inf 一个信息(INF)文件,其中包含安装驱动程序所需的信息。

    此外,echoapp.exe 文件已生成,应位于此处:C:\DriverSamples\general\echo\kmdf\exe\x64\Debug

    文件 描述
    EchoApp.exe 与 echo.sys 驱动程序通信的命令提示符可执行文件测试文件。
  2. 找到 USB 指纹驱动器或设置网络共享,将生成的驱动程序文件和测试 EchoApp 从主机复制到目标系统。

在下一部分中,将代码复制到目标系统,并安装和测试驱动程序。

在目标系统上安装 KMDF echo 驱动程序示例

在本部分中,使用 DevCon 工具安装 echo 示例驱动程序。

安装驱动程序的计算机称为 目标计算机测试计算机。 通常,此计算机与开发并生成驱动程序包的计算机分开。 开发和生成驱动程序的计算机称为 主计算机

将驱动程序包移动到目标计算机并安装驱动程序的过程称为 部署 驱动程序。

在部署测试签名驱动程序之前,请通过启用测试签名来准备目标计算机。 还需要在 WDK 安装中找到 DevCon 工具,并将其复制到目标系统。

若要在目标系统上安装驱动程序,请执行以下步骤。

在目标系统上,启用测试签名的驱动程序:

  1. 打开“Windows 设置”。

  2. 在“更新和安全”中,选择“恢复”。

  3. 在“高级启动”下,选择“立即重新启动”。

  4. 在计算机重启时,选择 启动选项。 在 Windows 10 中,选择故障排除>高级选项>启动设置,然后选择重新启动

  5. 选择 禁用驱动程序签名强制,然后按 F7 键。

  6. 重启目标计算机。

在主机系统上,转到 WDK 安装中的 Tools 文件夹,找到 DevCon 工具。 例如,查看以下文件夹:C:\Program Files (x86)\Windows Kits\10\Tools\x64\devcon.exe

在生成的驱动程序包的目标上创建文件夹,例如,C:\EchoDriver。 将 devcon.exe 复制到目标系统。 在主机系统上找到 .cer 证书。 它位于包含生成的驱动程序文件的文件夹中的主机计算机上的同一文件夹中。 复制前面在主计算机上描述的内置驱动程序中的所有文件,并将其保存到目标计算机上创建的同一文件夹。

在目标计算机上,选择并按住或右键单击证书文件,然后选择 安装,然后按照提示安装测试证书。

如果需要有关设置目标计算机的更详细说明,请参阅 为手动驱动程序部署准备计算机

以下说明演示如何安装和测试示例驱动程序。 下面是用于安装驱动程序的 devcon 工具的一般语法:

devcon install <INF file> <hardware ID>

安装此驱动程序所需的 INF 文件 echo.inf。 inf 文件包含用于安装 echo.sys的硬件 ID。 对于回声示例,硬件 ID 是 root\ECHO

在目标计算机上,以管理员身份打开命令提示符窗口。 转到驱动程序包文件夹,然后输入以下命令:

devcon install echo.inf root\ECHO

如果收到有关无法识别 devcon 的错误消息,请尝试将路径添加到 devcon 工具。 例如,如果将它复制到名为 C:\Tools的文件夹,请尝试使用以下命令:

c:\tools\devcon install echo.inf root\ECHO

此时会显示一个对话框,指示测试驱动程序是未签名的驱动程序。 选择“仍然安装此驱动程序”以继续

Windows 安全警告的屏幕截图,指出 Windows 无法验证驱动程序软件的发布者。

提示

 如果安装有任何问题,请查看以下文件了解详细信息。 %windir%\inf\setupapi.dev.log

成功安装示例驱动程序后,即可对其进行测试。

在目标计算机上,在命令提示符窗口中,输入 devmgmt 打开设备管理器。 在“设备管理器”的“视图” 菜单上,按类型选择“设备”。 在设备树中,在 示例设备 节点中找到 示例 WDF 回显驱动程序

设备管理器树的屏幕截图,其中突出显示了示例 WDF echo 驱动程序。

输入 echoapp 以启动测试 echo 应用程序,从而确认驱动程序是否正常。

C:\Samples\KMDF_Echo_Sample> echoapp
DevicePath: \\?\root#sample#0005#{cdc35b6e-0be4-4936-bf5f-5537380a7c1a}
Opened device successfully
512 Pattern Bytes Written successfully
512 Pattern Bytes Read successfully
Pattern Verified successfully
30720 Pattern Bytes Written successfully
30720 Pattern Bytes Read successfully
Pattern Verified successfully

使用 WinDbg 显示有关驱动程序的信息

本部分将设置符号路径,并使用内核调试器命令来显示 KMDF echo 示例驱动程序的相关信息。

查看有关驱动程序的信息:

  1. 在主机系统上,如果关闭调试器,请在管理员命令提示符窗口中使用以下命令再次打开它。

    WinDbg -k net:port=50000,key=2steg4fzbj2sz.23418vzkd4ko3.1g34ou07z4pev.1sp3yo9yz874p
    
  2. 使用 Ctrl+Break(Scroll Lock)中断在目标系统上运行的代码。

  3. 若要将符号路径设置为 WinDbg 环境中的Microsoft符号服务器,请使用 .symfix 命令。

    0: kd> .symfix
    
  4. 要添加本地符号位置以使用本地符号,请依次使用 .sympath+.reload /f 来添加路径。

    0: kd> .sympath+ C:\DriverSamples\general\echo\kmdf
    0: kd> .reload /f
    

    具有 /f 强制选项的 .reload 命令将删除指定模块的所有符号信息并重新加载符号。 在某些情况下,此命令还会重新加载或卸载模块本身。

必须加载正确的符号才能使用 WinDbg 提供的高级功能。 如果未正确配置符号,则尝试使用依赖于符号的功能时,会收到指示符号不可用的消息。

0:000> dv
Unable to enumerate locals, HRESULT 0x80004005
Private symbols (symbols.pri) are required for locals.
Type “.hh dbgerr005” for details.

有许多方法可用于处理符号。 在许多情况下,您可以将计算机配置为在需要时从Microsoft提供的符号服务器中访问符号。 此实验室使用此方法。 如果环境中的符号位于其他位置,请修改步骤以使用该位置。 有关详细信息,请参阅 Windows 调试器的符号路径

若要进行源代码调试,必须生成经过检查的(调试)版本的二进制文件。 编译器创建符号文件(.pdb 文件)。 这些符号文件显示调试器二进制指令与源行的对应方式。 调试器还必须能够访问实际源文件本身。

符号文件不包含源代码的文本。 为了便于调试,链接器最好不要优化代码。 如果代码已优化,则对局部变量的源调试和访问更加困难,有时几乎是不可能的。 如果在查看局部变量或源行时遇到问题,请设置以下生成选项:

set COMPILE_DEBUG=1
set ENABLE_OPTIMIZER=0
  1. 在调试器的命令区域中输入以下命令以显示有关回显驱动程序的信息:

    0: kd> lm m echo* v
    Browse full module list
    start             end                 module name
    fffff801`4ae80000 fffff801`4ae89000   ECHO       (private pdb symbols)  C:\Samples\KMDF_ECHO_SAMPLE\echo.pdb
        Loaded symbol image file: ECHO.sys
        Image path: \SystemRoot\system32\DRIVERS\ECHO.sys
        Image name: ECHO.sys
    ...  
    

    有关详细信息,请参阅 lm

  2. 由于此实验室之前设置了 prefer_dml,因此输出中的某些元素是可供选择的热链接。 在调试输出中选择“浏览所有全局符号”链接,以便显示以字母“a”开头的项目符号信息。

    0: kd> x /D Echo!a*
    
  3. 请注意,echo 示例不包含任何以字母“a”开头的符号,因此请键入 x ECHO!Echo* 以显示与 echo 驱动程序相关的所有以“Echo”开头的符号的信息。

    0: kd> x ECHO!Echo*
    fffff801`0bf95690 ECHO!EchoEvtIoQueueContextDestroy (void *)
    fffff801`0bf95000 ECHO!EchoEvtDeviceSelfManagedIoStart (struct WDFDEVICE__ *)
    fffff801`0bf95ac0 ECHO!EchoEvtTimerFunc (struct WDFTIMER__ *)
    fffff801`0bf9b120 ECHO!EchoEvtDeviceSelfManagedIoSuspend (struct WDFDEVICE__ *)
    ...
    

    有关详细信息,请参阅 x(检查符号)

  4. !lmi 扩展显示有关模块的详细信息。 输入 !lmi echo。 输出应类似于此示例中显示的文本:

    0: kd> !lmi echo
    Loaded Module Info: [echo] 
             Module: ECHO
       Base Address: fffff8010bf94000
         Image Name: ECHO.sys
    … 
    
  5. 使用 !dh 扩展显示标头信息,如以下示例所示:

    0: kd> !dh echo
    
    File Type: EXECUTABLE IMAGE
    FILE HEADER VALUES
         14C machine (i386)
           6 number of sections
    54AD8A42 time date stamp Wed Jan 07 11:34:26 2015
    ...
    
  6. 输入以下内容以更改默认调试位掩码,以便目标系统中的所有调试消息都显示在调试器中:

    0: kd> ed nt!Kd_DEFAULT_MASK 0xFFFFFFFF
    

    使用0xFFFFFFFF掩码时,某些驱动程序会显示其他信息。 如果要减少显示的信息量,请将掩码设置为0x00000000。

    0: kd> ed nt!Kd_DEFAULT_MASK 0x00000000
    

    使用 dd 命令确认掩码已设置为显示所有调试器消息。

    0: kd> dd nt!kd_DEFAULT_MASK 
    fffff802`bb4057c0  ffffffff 00000000 00000000 00000000
    fffff802`bb4057d0  00000000 00000000 00000000 00000000
    fffff802`bb4057e0  00000001 00000000 00000000 00000000
    fffff802`bb4057f0  00000000 00000000 00000000 00000000
    fffff802`bb405800  00000000 00000000 00000000 00000000
    fffff802`bb405810  00000000 00000000 00000000 00000000
    fffff802`bb405820  00000000 00000000 00000000 00000000
    fffff802`bb405830  00000000 00000000 00000000 00000000
    

显示即插即用设备树信息

在本部分中,显示有关 echo 示例设备驱动程序的信息以及它在即插即用设备树中的位置。

有关即插即用设备树中的设备驱动程序的信息可用于故障排除。 例如,如果设备驱动程序未驻留在设备树中,则设备驱动程序安装可能存在问题。

有关设备节点调试扩展的详细信息,请参阅 !devnode

  1. 在主机系统上,若要查看即插即用设备树中的所有设备节点,请输入 !devnode 0 1 命令。

    0: kd> !devnode 0 1
    Dumping IopRootDeviceNode (= 0xffffe0005a3a8d30)
    DevNode 0xffffe0005a3a8d30 for PDO 0xffffe0005a3a9e50
      InstancePath is "HTREE\ROOT\0"
      State = DeviceNodeStarted (0x308)
      Previous State = DeviceNodeEnumerateCompletion (0x30d)
      DevNode 0xffffe0005a3a3d30 for PDO 0xffffe0005a3a4e50
        InstancePath is "ROOT\volmgr\0000"
        ServiceName is "volmgr"
        State = DeviceNodeStarted (0x308)
        Previous State = DeviceNodeEnumerateCompletion (0x30d)
        DevNode 0xffffe0005a324560 for PDO 0xffffe0005bd95ca0…
    …
    
  2. 使用 Ctrl+F 在生成的输出中查找设备驱动程序的名称,echo

    WinDbg 中搜索术语“echo”的“查找”对话框的屏幕截图。

  3. 应加载 echo 设备驱动程序。 使用 !devnode 0 1 echo 命令显示与回显设备驱动程序关联的即插即用信息,如以下示例所示:

    0: Kd> !devnode 0 1 echo
    Dumping IopRootDeviceNode (= 0xffffe0007b725d30)
    DevNode 0xffffe0007b71a630 for PDO 0xffffe0007b71a960
      InstancePath is "ROOT\SAMPLE\0000"
      ServiceName is "ECHO"
      State = DeviceNodeStarted (0x308)
      Previous State = DeviceNodeEnumerateCompletion (0x30d)
    …
    
  4. 上一条命令显示的输出包括与运行中的驱动程序实例相关联的 PDO,在本例中为 0xffffe0007b71a960。 输入 !devobj <PDO address> 命令以显示与回显设备驱动程序关联的即插即用信息。 使用计算机上 !devnode 显示的 PDO 地址,而不是此处所示的地址。

    0: kd> !devobj 0xffffe0007b71a960
    Device object (ffffe0007b71a960) is for:
     0000000e \Driver\PnpManager DriverObject ffffe0007b727e60
    Current Irp 00000000 RefCount 0 Type 00000004 Flags 00001040
    Dacl ffffc102c9b36031 DevExt 00000000 DevObjExt ffffe0007b71aab0 DevNode ffffe0007b71a630 
    ExtensionFlags (0x00000800)  DOE_DEFAULT_SD_PRESENT
    Characteristics (0x00000180)  FILE_AUTOGENERATED_DEVICE_NAME, FILE_DEVICE_SECURE_OPEN
    AttachedDevice (Upper) ffffe000801fee20 \Driver\ECHO
    Device queue is not busy.
    
  5. !devnode 0 1 命令显示的输出包括与驱动程序正在运行实例关联的 PDO 地址。在本示例中,该地址为 0xffffe0007b71a960。 输入 !devstack <PDO address> 命令以显示与设备驱动程序关联的即插即用信息。 请使用计算机上 !devnode 所显示的 PDO 地址,而不是本示例中显示的地址。

    0: kd> !devstack 0xffffe0007b71a960
      !DevObj           !DrvObj            !DevExt           ObjectName
      ffffe000801fee20  \Driver\ECHO       ffffe0007f72eff0  
    > ffffe0007b71a960  \Driver\PnpManager 00000000  0000000e
    !DevNode ffffe0007b71a630 :
      DeviceInst is "ROOT\SAMPLE\0000"
      ServiceName is "ECHO"
    

输出显示你有一个相当简单的设备驱动程序堆栈。 echo 驱动程序是 PnPManager 节点的子节点。 PnPManager 是根节点。

\Driver\ECHO
\Driver\PnpManager

此图显示了更复杂的设备节点树。

示意图,说明包含大约 20 个节点的设备节点树。

有关更复杂的驱动程序堆栈的详细信息,请参阅 驱动程序堆栈设备节点和设备堆栈

使用断点和源代码

在本部分中,设置断点并单步执行内核模式源代码。

若要能够单步执行代码并实时检查变量的值,请启用断点并设置源代码的路径。

断点会在特定代码行处停止代码执行。 从这一点开始向前执行代码,对特定的代码部分进行调试。

若要使用调试命令设置断点,请使用以下 b 命令之一。

命令 描述
bp 设置一个将一直处于活动状态的断点,直到其所在模块被卸载。
bu 设置一个断点,该断点在卸载模块时未解析,并在重新加载模块时重新启用。
bm 为符号设置一个断点。 此命令适当地使用 bubp,并允许使用通配符(*)对匹配的每个符号(如类中的所有方法)设置断点。

有关详细信息,请参阅 WinDbg中的 源代码调试。

  1. 在主机系统上,使用 WinDbg UI 确认调试>源模式是否已在当前 WinDbg 会话中启用。

  2. 输入以下命令,将本地代码位置添加到源路径:

    .srcpath+ C:\DriverSamples\KMDF_Echo_Sample\driver\AutoSync
    
  3. 输入以下命令,将本地符号位置添加到符号路径:

    .sympath+ C:\DriverSamples\KMDF_Echo_Sample\driver\AutoSync
    
  4. 使用 x 命令检查与 echo 驱动程序关联的符号,以确定用于断点的函数名称。 可以使用通配符或 Ctrl+F 查找 DeviceAdd 函数名称。

    0: kd> x ECHO!EchoEvt*
    8b4c7490          ECHO!EchoEvtIoQueueContextDestroy (void *)
    8b4c7000          ECHO!EchoEvtDeviceSelfManagedIoStart (struct WDFDEVICE__ *)
    8b4c7820          ECHO!EchoEvtTimerFunc (struct WDFTIMER__ *)
    8b4cb0e0          ECHO!EchoEvtDeviceSelfManagedIoSuspend (struct WDFDEVICE__ *)
    8b4c75d0          ECHO!EchoEvtIoWrite (struct WDFQUEUE__ *, struct WDFREQUEST__ *, unsigned int)
    8b4cb170          ECHO!EchoEvtDeviceAdd (struct WDFDRIVER__ *, struct 
    …
    

    输出显示,echo 驱动程序的 DeviceAdd 方法是 ECHO!EchoEvtDeviceAdd

    或者,查看源代码以查找断点的函数名称。

  5. 使用驱动程序名称后跟要在其中设置断点的函数名称(例如 AddDevice)并以感叹号分隔,通过 bm 命令来设置断点。 此实验室使用 AddDevice 来监视正在加载的驱动程序。

    0: kd> bm ECHO!EchoEvtDeviceAdd
      1: fffff801`0bf9b1c0 @!"ECHO!EchoEvtDeviceAdd"
    

    可以将不同的语法与设置变量结合使用,例如 <module>!<symbol><class>::<method>'<file.cpp>:<line number>'或跳过多次 <condition> <#>。 有关详细信息,请参阅 WinDbg 和其他 Windows 调试器中的条件断点

  6. 列出当前断点,确认通过输入 bl 命令设置了断点:

    0: kd> bl
    1 e fffff801`0bf9b1c0     0001 (0001) ECHO!EchoEvtDeviceAdd
    

    此处显示的输出中的“e”表示启用断点编号 1 以进行触发。

  7. 输入 g(go)命令,在目标系统上重启代码执行。

  8. 在目标系统上,在 Windows 中,使用图标打开 Device Manager,或输入 mmc devmgmt.msc。 在“设备管理器”中,展开示例节点。

  9. 选择并按住或右键单击 KMDF echo 驱动程序条目,然后从菜单中选择“禁用”。

  10. 再次选择并按住或右键单击 KMDF echo 驱动程序条目,然后从菜单中选择“启用”。

  11. 在主机系统上,启用驱动程序后,AddDevice 调试断点应触发。 目标系统上的驱动程序代码的执行应停止。 当触发断点时,应在 AddDevice 例程开始时停止执行。 调试命令输出显示 Breakpoint 1 hit

    WinDbg 的屏幕截图,其中显示了示例代码局部变量和命令窗口。

  12. 通过输入 p 命令或按 F10 来逐行执行代码,直到到达 AddDevice 例程的末尾。 如下所示,突出显示大括号字符 (})。

    代码窗口的屏幕截图,其中大括号字符突出显示了 AddDevice 例程的开头。

在下一部分中,检查执行 DeviceAdd 代码后变量的状态。

可以使用以下命令修改现有断点:

命令 描述
bl 列出断点。
bc 清除列表中的断点。 使用 bc * 清除所有断点。
bd 禁用断点。 使用 bd * 禁用所有断点。
be 启用断点。 使用 be * 启用所有断点。

您还可以在 WinDbg 界面中修改断点。

还可以设置在访问内存位置时触发的断点。 使用以下语法的 ba(中断访问)命令:

ba <access> <size> <address> {options}
选项 描述
e 执行:当 CPU 从地址中提取指令时
r 读/写:当 CPU 读取或写入地址时
w write:当 CPU 写入地址时

在任何给定时间只能设置四个数据断点。 需要确保正确对齐数据方可触发断点。 Word 必须以可被 2 整除的地址结尾,dword 必须可被 4 整除,quadword 必须可被 0 或 8 整除。

例如,若要在特定内存地址上设置读/写断点,可以使用类似于此示例的命令。

ba r 4 0x0003f7bf0

可以使用以下命令通过括号中显示的关联键盘快捷方式单步执行代码。

  • 中断 (Ctrl+Break)。 只要系统正在运行并且正在与 WinDbg 通信,此命令就会中断系统。 内核调试器中的序列为 Ctrl+C。
  • 运行到光标处(F7 或 Ctrl+F10)。 将光标放在要中断执行的源代码或反汇编窗口中,然后按 F7。 代码执行到此处为止。 如果代码执行流未到达游标指示的点,则 WinDbg 不会中断。 如果未执行 IF 语句,则可能会出现这种情况。
  • 运行 (F5)。 运行,直到遇到断点或发生错误检查等事件。
  • 单步跳过 (F10)。 此命令将使代码逐条执行语句或指令。 如果遇到调用,则代码执行会传递调用而不输入调用例程。 如果编程语言为 C 或 C++ 且 WinDbg 处于源模式,则可以使用 调试>源模式打开或关闭源模式。
  • 单步执行 (F11)。 该命令与单步跳过类似,只是调用的执行会进入被调用例程。
  • 单步跳出 (Shift+F11)。 该命令可让代码执行到当前例程(调用栈中的当前位置)并退出。 这个命令在你见惯了常规操作后会非常有用。

有关详细信息,请参阅 WinDbg中的 源代码调试。

查看变量和调用堆栈

在本部分中,显示有关变量和调用堆栈的信息。

此实验室假定使用前面所述的过程停止了 AddDevice 例程。 若要查看此处显示的输出,请根据需要重复前面所述的步骤。

在主机系统上,若要显示变量,请使用 视图>本地 菜单项来显示局部变量。

显示本地变量窗口的 WinDbg 的屏幕截图。

若要查找全局变量地址的位置,请输入 ? <variable name>

  • 单步跳出 (Shift+F11) – 该命令可让代码执行到当前例程(调用栈中的当前位置)并退出。 如果已查看足够多的例程,这将非常有用。

有关详细信息,请参阅调试参考文档中的 WinDbg 中的源代码调试(经典版)

第 8 节:查看变量和调用堆栈

第 8 节中,将显示有关变量和调用堆栈的信息。

此实验室假定使用前面所述的过程停止了 AddDevice 例程。 若要查看此处显示的输出,请根据需要重复前面所述的步骤。

<- 在主机系统上

显示变量

使用查看>局部菜单项显示局部变量。

显示本地变量窗口的 WinDbg 的屏幕截图。

全局变量

可以通过键入 来查找全局变量地址的位置?<变量名>

局部变量

可以通过键入 dv 命令来显示给定帧的所有局部变量的名称和值。 若要显示特定帧的所有局部变量的名称和值,请输入 dv 命令:

0: kd> dv
         Driver = 0x00001fff`7ff9c838
     DeviceInit = 0xffffd001`51978190
         status = 0n0

调用堆栈是导致程序计数器当前位置的函数调用链。 调用堆栈上的顶部函数是当前函数,下一个函数是调用当前函数的函数,依此等。

若要显示调用堆栈,请使用 k* 命令。

命令 描述
kb 显示堆栈和前三个参数。
kp 显示堆栈和参数的完整列表。
kn 允许您查看堆栈以及旁边的帧信息。
  1. 在主机系统上,如果要保留可用的调用堆栈,请选择查看>调用堆栈进行查看。 选择窗口顶部的列以切换其他信息的显示。

    WinDbg 屏幕截图,显示调用堆栈窗口。

  2. 使用 kn 命令在调试处于中断状态的示例适配器代码时显示调用堆栈。

    3: kd> kn
    # Child-SP          RetAddr           Call Site
    00 ffffd001`51978110 fffff801`0942f55b ECHO!EchoEvtDeviceAdd+0x66 [c:\Samples\kmdf echo sample\c++\driver\autosync\driver.c @ 138]
    01 (Inline Function) --------`-------- Wdf01000!FxDriverDeviceAdd::Invoke+0x30 [d:\wbrtm\minkernel\wdf\framework\shared\inc\private\common\fxdrivercallbacks.hpp @ 61]
    02 ffffd001`51978150 fffff801`eed8097d Wdf01000!FxDriver::AddDevice+0xab [d:\wbrtm\minkernel\wdf\framework\shared\core\km\fxdriverkm.cpp @ 72]
    03 ffffd001`51978570 fffff801`ef129423 nt!PpvUtilCallAddDevice+0x35 [d:\9142\minkernel\ntos\io\pnpmgr\verifier.c @ 104]
    04 ffffd001`519785b0 fffff801`ef0c4112 nt!PnpCallAddDevice+0x63 [d:\9142\minkernel\ntos\io\pnpmgr\enum.c @ 7397]
    05 ffffd001`51978630 fffff801`ef0c344f nt!PipCallDriverAddDevice+0x6e2 [d:\9142\minkernel\ntos\io\pnpmgr\enum.c @ 3390]
    ...
    

调用堆栈显示,内核 (nt) 调用了即插即用代码 (PnP),PnP 调用了驱动程序框架代码 (WDF),WDF 随后又调用了 echo 驱动程序 DeviceAdd 函数。

显示进程和线程

在本部分中,显示有关在内核模式下运行的进程和线程的信息。

过程

可以使用 !process 调试器扩展来显示或设置进程信息。 设置一个断点,以检查播放声音时使用的进程。

  1. 在主机系统上,输入 dv 命令以检查与 EchoEvtIo 例程关联的区域设置变量:

    0: kd> dv ECHO!EchoEvtIo*
    ECHO!EchoEvtIoQueueContextDestroy
    ECHO!EchoEvtIoWrite
    ECHO!EchoEvtIoRead         
    
  2. 使用 bc * 清除之前的断点:

    0: kd> bc *  
    
  3. 使用以下命令在 EchoEvtIo 例程上设置符号断点:

    0: kd> bm ECHO!EchoEvtIo*
      2: aade5490          @!”ECHO!EchoEvtIoQueueContextDestroy”
      3: aade55d0          @!”ECHO!EchoEvtIoWrite”
      4: aade54c0          @!”ECHO!EchoEvtIoRead”
    
  4. 列出断点,以便确认断点已正确设置:

    0: kd> bl
    1 e aabf0490 [c:\Samples\kmdf echo sample\c++\driver\autosync\queue.c @ 197]    0001 (0001) ECHO!EchoEvtIoQueueContextDestroy
    ...
    
  5. 输入 g 以重启代码执行:

    0: kd> g
    
  6. 在目标系统上运行 EchoApp.exe 驱动程序测试程序。

  7. 在主机系统上,测试应用运行时,将调用驱动程序中的 I/O 例程。 该调用会导致断点触发,而目标系统上的驱动程序代码将停止执行。

    Breakpoint 2 hit
    ECHO!EchoEvtIoWrite:
    fffff801`0bf95810 4c89442418      mov     qword ptr [rsp+18h],r8
    
  8. 使用 !process 命令显示运行 echoapp.exe所涉及的当前进程:

    0: kd> !process
    PROCESS ffffe0007e6a7780
        SessionId: 1  Cid: 03c4    Peb: 7ff7cfec4000  ParentCid: 0f34
        DirBase: 1efd1b000  ObjectTable: ffffc001d77978c0  HandleCount:  34.
        Image: echoapp.exe
        VadRoot ffffe000802c79f0 Vads 30 Clone 0 Private 135. Modified 5. Locked 0.
        DeviceMap ffffc001d83c6e80
        Token                             ffffc001cf270050
        ElapsedTime                       00:00:00.052
        UserTime                          00:00:00.000
        KernelTime                        00:00:00.000
        QuotaPoolUsage[PagedPool]         33824
        QuotaPoolUsage[NonPagedPool]      4464
        Working Set Sizes (now,min,max)  (682, 50, 345) (2728KB, 200KB, 1380KB)
        PeakWorkingSetSize                652
        VirtualSize                       16 Mb
        PeakVirtualSize                   16 Mb
        PageFaultCount                    688
        MemoryPriority                    BACKGROUND
        BasePriority                      8
        CommitCharge                      138
    
            THREAD ffffe00080e32080  Cid 03c4.0ec0  Teb: 00007ff7cfece000 Win32Thread: 0000000000000000 RUNNING on processor 1
    

    输出显示该进程与 echoapp.exe 线程相关联,当驱动程序写入事件断点被触发时,该线程正在运行。 有关详细信息,请参阅 !process

  9. 使用 !process 0 0 显示所有进程的摘要信息。 在输出中,使用 Ctrl+F 查找与 echoapp.exe 图像关联的进程的相同进程地址。 在此示例中,进程地址 ffffe0007e6a7780

    ...
    
    PROCESS ffffe0007e6a7780
        SessionId: 1  Cid: 0f68    Peb: 7ff7cfe7a000  ParentCid: 0f34
        DirBase: 1f7fb9000  ObjectTable: ffffc001cec82780  HandleCount:  34.
        Image: echoapp.exe
    
    ...
    
  10. 记录与 echoapp.exe 关联的进程 ID,以便稍后在本实验室中使用。 还可以使用 Ctrl+C 将地址复制到复制缓冲区供以后使用。

    _____________________________________________________(echoapp.exe 进程地址)

  11. 在调试器中根据需要输入 g,以向前运行代码,直到 echoapp.exe 完成运行。 它在读写事件中多次触发断点。 在 echoapp.exe完成后,通过按 Ctrl+ScrLk (Ctrl+Break) 切换到调试器。

  12. 使用 !process 命令以确认您正在运行不同的进程。 在此处显示的输出中,映像值为 System 的流程与 Echo 的映像值不同。

    1: kd> !process
    PROCESS ffffe0007b65d900
        SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
        DirBase: 001ab000  ObjectTable: ffffc001c9a03000  HandleCount: 786.
        Image: System
        VadRoot ffffe0007ce45930 Vads 14 Clone 0 Private 22. Modified 131605. Locked 64.
        DeviceMap ffffc001c9a0c220
        Token                             ffffc001c9a05530
        ElapsedTime                       21:31:02.516
    ...
    

    输出显示停止 OS 时,系统进程 ffffe0007b65d900 正在运行。

  13. 使用 !process 命令尝试查看与之前记录的 echoapp.exe 相关联的进程 ID。 提供前面记录的 echoapp.exe 进程地址,而不是此示例中显示的示例进程地址。

    0: kd> !process ffffe0007e6a7780
    TYPE mismatch for process object at 82a9acc0
    

    进程对象不再可用,因为 echoapp.exe 进程不再运行。

线程

用于查看和设置线程的命令类似于进程的命令。 使用 !thread 命令查看线程。 使用 .thread 设置当前线程。

  1. 在主机系统上,在调试器中输入 g 以在目标系统上重启代码执行。

  2. 在目标系统上,运行 EchoApp.exe 驱动程序测试程序。

  3. 在主机系统上,中断点命中,代码执行停止。

    Breakpoint 4 hit
    ECHO!EchoEvtIoRead:
    aade54c0 55              push    ebp
    
  4. 若要查看正在运行的线程,请输入 !thread。 应显示类似于以下示例的信息:

    0: kd>  !thread
    THREAD ffffe000809a0880  Cid 0b28.1158  Teb: 00007ff7d00dd000 Win32Thread: 0000000000000000 RUNNING on processor 0
    IRP List:
        ffffe0007bc5be10: (0006,01f0) Flags: 00060a30  Mdl: 00000000
    Not impersonating
    DeviceMap                 ffffc001d83c6e80
    Owning Process            ffffe0008096c900       Image:         echoapp.exe
    ...
    

    记下 echoapp.exe的图像名称。 这表示你正在查看与测试应用关联的线程。

  5. 使用 !process 命令确定此线程是否是唯一在与 echoapp.exe关联的进程中运行的线程。 进程中正在运行的线程的线程数与 !thread 命令显示的线程数相同。

    0: kd> !process
    PROCESS ffffe0008096c900
        SessionId: 1  Cid: 0b28    Peb: 7ff7d00df000  ParentCid: 0f34
        DirBase: 1fb746000  ObjectTable: ffffc001db6b52c0  HandleCount:  34.
        Image: echoapp.exe
        VadRoot ffffe000800cf920 Vads 30 Clone 0 Private 135. Modified 8. Locked 0.
        DeviceMap ffffc001d83c6e80
        Token                             ffffc001cf5dc050
        ElapsedTime                       00:00:00.048
        UserTime                          00:00:00.000
        KernelTime                        00:00:00.000
        QuotaPoolUsage[PagedPool]         33824
        QuotaPoolUsage[NonPagedPool]      4464
        Working Set Sizes (now,min,max)  (681, 50, 345) (2724KB, 200KB, 1380KB)
        PeakWorkingSetSize                651
        VirtualSize                       16 Mb
        PeakVirtualSize                   16 Mb
        PageFaultCount                    686
        MemoryPriority                    BACKGROUND
        BasePriority                      8
        CommitCharge                      138
    
            THREAD ffffe000809a0880  Cid 0b28.1158  Teb: 00007ff7d00dd000 Win32Thread: 0000000000000000 RUNNING on processor 0
    
  6. 使用 !process 0 0 命令找到两个相关进程的进程地址,并在此处记录这些进程地址。

    Cmd.exe: ____________________________________________________________

    EchoApp.exe: _______________________________________________________

    0: kd> !process 0 0 
    
    …
    
    PROCESS ffffe0007bbde900
        SessionId: 1  Cid: 0f34    Peb: 7ff72dfa7000  ParentCid: 0c64
        DirBase: 19c5fa000  ObjectTable: ffffc001d8c2f300  HandleCount:  31.
        Image: cmd.exe
    …
    PROCESS ffffe0008096c900
        SessionId: 1  Cid: 0b28    Peb: 7ff7d00df000  ParentCid: 0f34
        DirBase: 1fb746000  ObjectTable: ffffc001db6b52c0  HandleCount:  34.
        Image: echoapp.exe
    …
    

    或者,可以使用 !process 0 17 显示有关每个进程的详细信息。 此命令的输出可能很长。 可以使用 Ctrl+F 搜索输出。

  7. 使用 !process 命令列出运行计算机的两个进程的进程信息。 提供来自 !process 0 0 输出的进程地址,而不是此示例中显示的地址。

    此示例输出适用于前面记录的 cmd.exe 进程 ID。 此进程 ID 的图像名称是 cmd.exe

    0: kd>  !process ffffe0007bbde900
    PROCESS ffffe0007bbde900
        SessionId: 1  Cid: 0f34    Peb: 7ff72dfa7000  ParentCid: 0c64
        DirBase: 19c5fa000  ObjectTable: ffffc001d8c2f300  HandleCount:  31.
        Image: cmd.exe
        VadRoot ffffe0007bb8e7b0 Vads 25 Clone 0 Private 117. Modified 20. Locked 0.
        DeviceMap ffffc001d83c6e80
        Token                             ffffc001d8c48050
        ElapsedTime                       21:33:05.840
        UserTime                          00:00:00.000
        KernelTime                        00:00:00.000
        QuotaPoolUsage[PagedPool]         24656
        QuotaPoolUsage[NonPagedPool]      3184
        Working Set Sizes (now,min,max)  (261, 50, 345) (1044KB, 200KB, 1380KB)
        PeakWorkingSetSize                616
        VirtualSize                       2097164 Mb
        PeakVirtualSize                   2097165 Mb
        PageFaultCount                    823
        MemoryPriority                    FOREGROUND
        BasePriority                      8
        CommitCharge                      381
    
            THREAD ffffe0007cf34880  Cid 0f34.0f1c  Teb: 00007ff72dfae000 Win32Thread: 0000000000000000 WAIT: (UserRequest) UserMode Non-Alertable
                ffffe0008096c900  ProcessObject
            Not impersonating
    ...
    

    此示例输出适用于前面记录的 echoapp.exe 进程 ID。

    0: kd>  !process ffffe0008096c900
    PROCESS ffffe0008096c900
        SessionId: 1  Cid: 0b28    Peb: 7ff7d00df000  ParentCid: 0f34
        DirBase: 1fb746000  ObjectTable: ffffc001db6b52c0  HandleCount:  34.
        Image: echoapp.exe
        VadRoot ffffe000800cf920 Vads 30 Clone 0 Private 135. Modified 8. Locked 0.
        DeviceMap ffffc001d83c6e80
        Token                             ffffc001cf5dc050
        ElapsedTime                       00:00:00.048
        UserTime                          00:00:00.000
        KernelTime                        00:00:00.000
        QuotaPoolUsage[PagedPool]         33824
        QuotaPoolUsage[NonPagedPool]      4464
        Working Set Sizes (now,min,max)  (681, 50, 345) (2724KB, 200KB, 1380KB)
        PeakWorkingSetSize                651
        VirtualSize                       16 Mb
        PeakVirtualSize                   16 Mb
        PageFaultCount                    686
        MemoryPriority                    BACKGROUND
        BasePriority                      8
        CommitCharge                      138
    
            THREAD ffffe000809a0880  Cid 0b28.1158  Teb: 00007ff7d00dd000 Win32Thread: 0000000000000000 RUNNING on processor 0
            IRP List:
                ffffe0007bc5be10: (0006,01f0) Flags: 00060a30  Mdl: 00000000
            Not impersonating
    ...
    
  8. 在此处记录与两个进程关联的第一个线程地址。

    Cmd.exe: ____________________________________________________

    EchoApp.exe:_________________________________________________

  9. 使用 !Thread 命令显示有关当前线程的信息。

    0: kd>  !Thread
    THREAD ffffe000809a0880  Cid 0b28.1158  Teb: 00007ff7d00dd000 Win32Thread: 0000000000000000 RUNNING on processor 0
    IRP List:
        ffffe0007bc5be10: (0006,01f0) Flags: 00060a30  Mdl: 00000000
    Not impersonating
    DeviceMap                 ffffc001d83c6e80
    Owning Process            ffffe0008096c900       Image:         echoapp.exe
    Attached Process          N/A            Image:         N/A
    ...
    

    如预期的那样,当前线程是与 echoapp.exe 关联的线程,并且处于运行状态。

  10. 使用 !Thread 命令显示与 cmd.exe 进程关联的线程的相关信息。 提供前面记录的线程地址。

    0: kd> !Thread ffffe0007cf34880
    THREAD ffffe0007cf34880  Cid 0f34.0f1c  Teb: 00007ff72dfae000 Win32Thread: 0000000000000000 WAIT: (UserRequest) UserMode Non-Alertable
        ffffe0008096c900  ProcessObject
    Not impersonating
    DeviceMap                 ffffc001d83c6e80
    Owning Process            ffffe0007bbde900       Image:         cmd.exe
    Attached Process          N/A            Image:         N/A
    Wait Start TickCount      4134621        Ticks: 0
    Context Switch Count      4056           IdealProcessor: 0             
    UserTime                  00:00:00.000
    KernelTime                00:00:01.421
    Win32 Start Address 0x00007ff72e9d6e20
    Stack Init ffffd0015551dc90 Current ffffd0015551d760
    Base ffffd0015551e000 Limit ffffd00155518000 Call 0
    Priority 14 BasePriority 8 UnusualBoost 3 ForegroundBoost 2 IoPriority 2 PagePriority 5
    Child-SP          RetAddr           : Args to Child                                                           : Call Site
    ffffd001`5551d7a0 fffff801`eed184fe : fffff801`eef81180 ffffe000`7cf34880 00000000`fffffffe 00000000`fffffffe : nt!KiSwapContext+0x76 [d:\9142\minkernel\ntos\ke\amd64\ctxswap.asm @ 109]
    ffffd001`5551d8e0 fffff801`eed17f79 : ffff03a5`ca56a3c8 000000de`b6a6e990 000000de`b6a6e990 00007ff7`d00df000 : nt!KiSwapThread+0x14e [d:\9142\minkernel\ntos\ke\thredsup.c @ 6347]
    ffffd001`5551d980 fffff801`eecea340 : ffffd001`5551da18 00000000`00000000 00000000`00000000 00000000`00000388 : nt!KiCommitThreadWait+0x129 [d:\9142\minkernel\ntos\ke\waitsup.c @ 619]
    ...
    

    此线程与 cmd.exe 关联,处于等待状态。

  11. 提供等待 CMD.exe 线程的线程地址,以将上下文更改为该等待线程。

    0: kd> .Thread ffffe0007cf34880
    Implicit thread is now ffffe000`7cf34880
    
  12. 使用 k 命令查看与等待线程关联的调用堆栈。

    0: kd> k
      *** Stack trace for last set context - .thread/.cxr resets it
    # Child-SP          RetAddr           Call Site
    00 ffffd001`5551d7a0 fffff801`eed184fe nt!KiSwapContext+0x76 [d:\9142\minkernel\ntos\ke\amd64\ctxswap.asm @ 109]
    01 ffffd001`5551d8e0 fffff801`eed17f79 nt!KiSwapThread+0x14e [d:\9142\minkernel\ntos\ke\thredsup.c @ 6347]
    02 ffffd001`5551d980 fffff801`eecea340 nt!KiCommitThreadWait+0x129 [d:\9142\minkernel\ntos\ke\waitsup.c @ 619]
    03 ffffd001`5551da00 fffff801`ef02e642 nt!KeWaitForSingleObject+0x2c0 [d:\9142\minkernel\ntos\ke\wait.c @ 683]
    ...
    

    调用堆栈元素(如 KiCommitThreadWait)表示此线程未按预期运行。

有关线程和进程的详细信息,请参阅以下参考:

IRQL、注册和结束 WinDbg 会话

在本部分中,显示中断请求级别(IRQL)和寄存器的内容。

查看保存的 IRQL

IRQL 用于管理中断服务的优先级。 每个处理器都有一个 IRQL 设置,线程可以提高或降低该设置。 在处理器 IRQL 设置或下方发生的中断将被屏蔽,不会干扰当前操作。 高于处理器 IRQL 设置的中断优先于当前操作。

在主机系统上,!irql 扩展会在调试器中断发生之前,显示目标计算机当前处理器的IRQL。 当目标计算机进入调试器时,IRQL 会更改,但调试器中断前有效的 IRQL 被保存,并由 !irql显示。

0: kd> !irql
Debugger saved IRQL for processor 0x0 -- 2 (DISPATCH_LEVEL)

查看寄存器

在主机系统上,使用 r (Registers) 命令显示当前处理器上当前线程的寄存器的内容。

0: kd> r
rax=000000000000c301 rbx=ffffe00173eed880 rcx=0000000000000001
rdx=000000d800000000 rsi=ffffe00173eed8e0 rdi=ffffe00173eed8f0
rip=fffff803bb757020 rsp=ffffd001f01f8988 rbp=ffffe00173f0b620
 r8=000000000000003e  r9=ffffe00167a4a000 r10=000000000000001e
r11=ffffd001f01f88f8 r12=0000000000000000 r13=ffffd001f01efdc0
r14=0000000000000001 r15=0000000000000000
iopl=0         nv up ei pl nz na pe nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
nt!DbgBreakPointWithStatus:
fffff803`bb757020 cc              int     3

另外,也可以通过选择查看>寄存器来显示寄存器的内容。 有关详细信息,请参阅 r(寄存器)

在单步执行程序集语言代码和其他情况下,查看寄存器的内容很有帮助。 有关汇编语言的详细信息,请参阅 批注 x86 反汇编批注 x64 反汇编

有关寄存器内容的信息,请参阅 x86 体系结构x64 体系结构

结束 WinDbg 会话

如果想保留调试器,但又想连接到目标计算机,请使用 bc * 清除所有断点,这样目标计算机就不会尝试连接到主机调试器。 然后使用 g 命令让目标计算机再次运行。

若要结束调试会话,请在主机系统上闯入调试器并输入 (退出和分离)命令,或从菜单中选择“停止调试”

0: kd> qd

有关详细信息,请参见 在 WinDbg 中结束调试会话

Windows 调试资源

更多信息可以在 Windows 调试中获取。 其中一些书籍在其示例中使用早期版本的 Windows(如 Windows Vista),但所讨论的概念适用于大多数版本的 Windows。

  • 书籍

    • 《高级 Windows 调试》,作者:Mario Hewardt 和 Daniel Pravat
    • 《在 Windows 调试中:Windows® 调试和跟踪策略实用指南》,作者 Tarik Soulami
    • 《Windows 内部书籍》,作者 Pavel Yosifovich、Alex Ionescu、Mark Russinovich 和 David Solomon
  • 视频

  • 培训供应商

另请参阅