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

本实验室介绍 WinDbg 内核调试器。 使用 WinDbg 调试 echo 内核模式示例驱动程序代码。

实验室目标

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

在本实验室中,您将使用实时内核调试连接来浏览以下操作:

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

用户和内核模式调试

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

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

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

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

实验室设置

完成实验需要以下硬件:

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

完成实验需要以下软件:

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

该实验室包含以下部分:

连接到内核模式 WinDbg 会话

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

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

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

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

通过网络集线器或路由器连接两台电脑的示意图。

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

通过以太网配置内核模式调试

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

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

    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 更改启动信息之前,可能需要在测试计算机上暂时挂起 BitLocker 和安全启动等 Windows 安全功能。 在测试完成后重新启用这些安全功能。 在禁用安全功能时,适当管理测试计算机。 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 个项目),然后选择配置管理器。 确保三个项目的配置和平台设置都相同。 默认情况下,所有项目的配置都设置为“Win10 调试”,而平台设置为“Win64”。 如果对一个项目进行任何配置或平台更改,请对其余三个项目进行同样的更改。

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

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

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

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

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

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

    生成窗口应显示一条信息,指明所有三个项目均已成功生成。

提示

如果遇到生成错误信息,请使用生成错误编号来确定修复方法。 例如,MSBuild error MSB8040 描述了如何使用 Spectre 已缓解的库。

  1. 在文件资源管理器中,转到包含示例提取文件的文件夹。 例如,如果这是之前指定的文件夹,则转到 C:\DriverSamples\general\echo\kmdf。 在该文件夹中,编译的驱动程序文件的位置会因在“配置管理器”中选择的配置和平台设置而异。 如果不更改默认设置,那么编译后的驱动程序文件将保存到名为 \x64\Debug 的文件夹中,用于 64 位调试版本。

    转到包含自动同步驱动程序生成的文件的文件夹: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. 找到 U 盘或设置网络共享,将生成的驱动程序文件和测试 EchoApp 从主机复制到目标系统。

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

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

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

安装驱动程序的计算机被称为目标计算机测试计算机。 通常情况下,这台计算机与开发和生成驱动程序软件包的计算机是分开的。 开发和生成驱动程序的计算机被称为主机计算机

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

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

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

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

  1. 打开“Windows 设置”。

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

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

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

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

  6. 重新启动目标计算机。

在主机系统上,转到 WDK 安装中的“工具”文件夹,然后找到 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。 对于 echo 示例,硬件 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 Echo 驱动程序”。

设备管理器树的屏幕截图,其中突出显示了示例 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(滚动锁定)来中断目标系统上运行的代码。

  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. 在调试器的命令区输入以下命令,以便显示有关 echo 驱动程序的信息:

    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 命令显示与 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> 命令可显示与 echo 设备驱动程序关联的即插即用信息。 使用 !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 中,使用图标或输入 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 UI 中修改断点。

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

ba <access> <size> <address> {options}
选项 说明
e execute:当 CPU 从地址中提取指令时
r read/write:当 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 的屏幕截图。

全局变量

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

局部变量

输入 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
    ...
    

    输出显示当停止操作系统时,系统进程 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 会被保存并通过 !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 命令让目标计算机再次运行。

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

0: kd> qd

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

Windows 调试资源

有关 Windows 调试的更多信息。 其中一些书籍在示例中使用 Windows Vista 等较早版本的 Windows,但所讨论的概念适用于大多数版本的 Windows。

另请参阅