调试通用驱动程序 - 分步(Echo 内核模式)
2017-05-26 11:28
597 查看
本文档已存档,并且将不进行维护。
在本实验中,使用实时内核调试连接,以了解以下内容:
使用 Windows 调试器命令
使用标准命令(调用堆栈、变量、线程、IRQL)
使用高级驱动程序调试命令 (!commands)
使用符号
在实时调试中设置断点
查看调用堆栈
显示即插即用设备树
处理线程和进程上下文
注意 使用 Windows 调试器时,可执行两种类型的调试 - 用户或内核模式调试。
用户模式 - 应用程序和子系统在用户模式下的计算机上运行。用户模式下运行的进程将在其虚拟地址空间内执行此操作。限制这些进程获得对系统许多部分的直接访问权限,包括系统硬件、未被分配使用的内存和系统中可能有损系统整体性的其他部分。因为在用户模式下运行的进程将有效地与系统和其他用户模式进程隔离开来,所以它们不会干扰这些资源。
内核模式 - 内核模式是一种操作系统和特权程序在其中运行的处理器访问模式。内核模式代码有权访问系统的任何部分,且不像用户模式代码那样受到限制。它能够获得对运行在用户模式或内核模式中的任何其他进程的任何部分的访问权限。 许多核心操作系统功能和硬件设备驱动器均可在内核模式下运行。
本实验将重点介绍内核模式调试,因为它是用于调试多种设备驱动程序的方法。
此次练习将介绍用户模式和内核模式调试期间常用的调试命令。该练习还将介绍内核模式调试所使用的调试扩展(有时称为“!commands”)。
一台运行 Windows 10 的台式机(主机)
一台运行 Windows 10 的台式机(目标计算机)
一根网络交叉电缆或一个网络集线器以及多根网络电缆,用于连接这两台电脑
对 Internet 的访问权限,以下载符号文件
你将需要以下软件来完成此实验。
Windows 10 SDK
Windows 10 WDK
Visual Studio 2015
Windows 10 的 Echo 驱动程序示例
本实验包括以下十一个部分。
第 1 部分:连接到内核模式 WinDbg 会话
第 2 部分:内核模式调试命令和技巧
第 3 部分:下载和生成 KMDF 通用 Echo 驱动程序
第 4 部分:在目标计算机系统上安装 KMDF Echo 驱动程序示例
第 5 部分:使用 WinDbg 显示驱动程序相关信息
第 6 部分:显示即插即用设备树的相关信息
第 7 部分:使用断点和源代码
第 8 部分:查看变量和调用堆栈
第 9 部分:显示进程和线程
第 10 部分:IRQL、寄存器和结束 WinDbg 会话
第 11 部分:Windows 调试资源
在本实验中,电脑需配置为使用以太网网络连接,以供进行内核调试。
本实验使用两台电脑。Windows 调试器在“主机”系统上运行,而 KMDF Echo 驱动程序在“目标计算机”系统上运行。
图示左侧的“主机”通过交叉以太网电缆连接到右侧的“目标计算机”。
本实验中的步骤假设你使用的是网络交叉电缆,不过如果你直接将主机和目标计算机插入网络集线器中,也可进行该实验。
为了与内核模式应用程序配合使用以及使用 Windbg,建议使用 KDNET over Ethernet 传输。有关如何使用以太网传输协议的信息,请参阅
WinDbg 入门(内核模式)。有关设置目标计算机的详细信息,请参阅准备用于手动部署驱动程序的计算机和通过网络电缆手动设置内核模式调试。
<- 在主机系统上
1. 在主机系统上打开命令提示符,并键入 ipconfig 以确定其 IP 地址。
复制
2. 记录主机系统的 IP 地址:______________________________________
-> 在目标计算机系统上
3. 在目标计算机系统上打开命令提示符,并使用 ping 命令确定这两个系统之间的网络连接性。用你之前记录的主机计算机系统的 IP 地址来替代示例输出中所示的地址。
复制
通过完成以下步骤,在目标计算机系统上启用内核模式调试。
1. 在目标计算机上,以管理员身份打开“命令提示符”窗口。输入此命令以启动调试。
复制
2. 键入此命令以启用测试签名。
复制
3. 键入以下命令以设置主机系统的 IP 地址。使用你之前记录的主机系统的 IP 地址,而非示例中所示的地址。
复制
4. 键入此命令以确认 dbgsettings 均已正确设置。
复制
注意
防火墙和调试器
如果你收到一条来自防火墙的弹出式消息,并且如果你希望使用调试器,请解除所需网络类型的锁定。
<- 在主机系统上
1. 在主计算机上,以管理员身份打开“命令提示符”窗口。转到 WinDbg.exe 目录。 使用 x64 版本的 WinDbg.exe,它来自于作为 Windows 工具包安装的一部分进行安装的 Windows WDK。
复制
2. 使用以下命令启动具有远程用户调试功能的 WinDbg。密钥值和端口值须与我们之前使用 BCDEdit 在目标计算机系统上设置的值匹配。
复制
-> 在目标计算机系统上
重新启动目标计算机系统。
<- 在主机系统上
在一到两分钟内,调试输出应该会显示在主机系统上。
“调试器命令”窗口是 WinDbg 中的主要调试信息窗口。您可在此窗口中输入调试器命令并查看命令输出。
“调试器命令”窗口分为两个窗格。在窗口底部的较小窗格(命令输入窗格)中键入命令,在窗口顶部的较大窗格中查看命令输出。
在命令输入窗格中,使用向上键和向下键以滚动显示历史命令。命令显示后,你可以编辑它或按 ENTER 运行它。
-> 在主机系统上
支持带有 .prefer_dml 的调试器标记语言 (DML)
一些调试命令将显示采用调试器标记语言的文本,以便你可以通过在其上单击快速收集更多信息。
1. 在 WinDBg 中使用 Ctrl+Break (Scroll Lock) 以进入到正在目标计算机系统运行的代码。目标计算机系统可能需要一些时间来进行响应。2. 键入以下命令,以在调试器命令窗口中启用 DML。
复制
使用 .hh 以获取帮助
你可以使用 .hh 命令访问参考命令帮助。
3. 键入以下命令,以查看 .prefer_dml 的参考命令帮助。
复制
调试器帮助文件将显示有关 .prefer_dml 命令的帮助。
设置调试掩码
4. 键入以下命令,以更改默认调试位掩码,以便来自目标计算机系统的所有调试消息都显示在调试器中。
复制
在目标计算机系统上显示 Windows 的版本
5. 通过在 WinDbg 窗口中键入
vertarget (Show Target Computer Version) 命令,从而在目标计算机系统上显示详细的版本信息。
复制
列出加载的模块
6. 通过在 WinDbg 窗口中键入
lm (List Loaded Modules) 命令来显示加载的模块,你可以验证是否正在使用正确的内核模式进程。
复制
注意 在本实验中,已省略的输出用“…”指示。
7. 要请求特定模块的详细信息,请使用所示的 v(详细)选项。
复制
8. 由于我们尚未设置符号路径和加载的符号,因此调试器中提供了有限的信息。
在使用 WinDbg 时,你通常会用到自己的驱动程序代码。要熟悉 WinDbg 操作,请使用 KMDF 模板“Echo”示例驱动程序。若提供源代码,则更易于用户理解显示在 WinDbg 中的信息。此外,该示例还将用于演示如何逐句通过本机内核模式代码。这项技术对调试复杂的内核模式代码问题十分有价值。
若要下载和生成 Echo 示例音频驱动程序,请完成以下步骤。
从 GitHub 下载并解压缩 KMDF Echo 示例
你可以使用浏览器,在此处通过 GitHub 查看 Echo 示例:
https://github.com/Microsoft/Windows-driver-samples/tree/97cf5197cf5b882b2c689d8dc2b555f2edf8f418/general/echo/kmdf
你可以在此处读取该示例:
https://github.com/Microsoft/Windows-driver-samples/blob/97cf5197cf5b882b2c689d8dc2b555f2edf8f418/general/echo/kmdf/ReadMe.md
你可以在此处浏览所有的通用驱动程序示例:
https://github.com/Microsoft/Windows-driver-samples
KMDF Echo 示例位于 general 文件夹中。
a.对于本实验,我们将以压缩文件的形式下载通用驱动程序示例。
https://github.com/Microsoft/Windows-driver-samples/archive/master.zip
b. 将 master.zip 文件下载到本地硬盘驱动器中。
c. 右键单击 Windows-driver-samples-master.zip,然后选择“全部解压缩”。指定新的文件夹,或浏览到一个现有文件夹以存储已解压缩的文件。例如,你可以将 C:\DriverSamples\ 指定为要在其中解压缩文件的新文件夹。
d.解压缩文件后,请导航到以下子文件夹。
C:\DriverSamples\general\echo\kmdf
在 Visual Studio 中打开驱动程序解决方案
在 Microsoft Visual Studio 中,依次单击“文件”>“打开”>“项目/解决方案...”,然后导航到包含已解压缩文件的文件夹(例如,C:\DriverSamples\general\echo\kmdf)。双击
kmdfecho 解决方案文件以打开它。
在 Visual Studio 中,找到“解决方案资源管理器”。(如果尚未打开,请从“视图”菜单中选择“解决方案资源管理器”。)在“解决方案资源管理器”中,你可以看到一个具有三个项目的解决方案。
设置示例的配置和平台
在“解决方案资源管理器”中,右键单击“解决方案‘kmdfecho’(3 个项目)”,然后选择“配置管理器”。请确保配置和平台设置与四个项目相同。默认情况下,在所有这些项目中都将配置设置为 "Win10 Debug",而将平台设置为 "Win64"。如果你对其中一个项目做了任何配置和/或平台更改,则必须对剩余三个项目做相同的更改。
设置运行时库
设置运行时库 - 将运行时库的版本从 DLL 版本更改为非 DLL 版本。如果没有此设置,则必须将 MSVC 运行时单独安装到目标计算机。
检查驱动程序签名
打开 Echo 驱动程序的属性页,并确保“驱动程序签名”>“签名模式”已设置为“测试签名”。之所以必须这样做,是因为 Windows 要求对驱动程序进行签名。
使用 Visual Studio 生成示例
在 Visual Studio 中,依次单击“生成”>“生成解决方案”。
如果一切正常,生成窗口将显示一条消息,指示所有这三个项目均已成功生成。
查找生成的驱动程序文件
在文件资源管理器中,导航到包含该示例的已解压缩文件的文件夹。例如,如果 C:\DriverSamples\general\echo\kmdf 是你前面指定的文件夹所在的路径,则导航至该路径。在该文件夹内,已编译的驱动程序文件的位置会有所不同,具体取决于“配置管理器”中选定的配置和平台设置。例如,如果让默认设置保持不变,则编译的驱动程序文件将针对 64 位调试版保存到名为 \x64\Debug 的文件夹中。
导航到包含 Autosync 驱动程序的生成文件的文件夹:
C:\DriverSamples\general\echo\kmdf\driver\AutoSync\x64\Debug。该文件夹应包含以下文件:
此外,还将生成 echoapp.exe 文件,可在此处找到它:C:\DriverSamples\general\echo\kmdf\exe\x64\Debug
找到 USB 便携驱动器或设置网络共享,以将生成的驱动程序文件和测试 EchoApp 从主机复制到目标计算机系统。
在下一部分中,将代码复制到目标计算机系统中,然后安装和测试驱动程序。
-> 在目标计算机系统上
安装驱动程序的计算机称为“目标计算机”或“测试计算机”。通常,此类计算机独立于开发和生成驱动程序包的计算机。开发和生成驱动程序的计算机称为“主计算机”。
将驱动程序包移至目标计算机并安装该驱动程序的过程称为“部署”驱动程序。可自动或手动部署示例 Echo 驱动程序。
手动部署驱动程序之前,必须通过打开测试签名准备目标计算机。还需要在 WDK 安装中找到 DevCon 工具。完成该操作后,你便可以随时运行生成的驱动程序示例了。
通过执行以下步骤在目标计算机系统上安装驱动程序。
准备目标计算机
以管理员身份打开命令提示符窗口。然后,输入以下命令:
bcdedit /set TESTSIGNING ON
重新启动目标计算机。
<- 在主机系统上
导航至 WDK 安装中的 Tools 文件夹,并找到 DevCon 工具。例如,查找以下文件夹:
C:\Program Files (x86)\Windows Kits\10.0\Tools\x64\devcon.exe在目标计算机上为生成的驱动程序包创建文件夹(例如,C:\EchoDriver)。从主计算机上前面描述的生成驱动程序包中复制所有文件,并将这些文件都保存到在目标计算机上创建的文件夹中。
在主计算机系统上找到 .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此时将显示一个对话框,指示测试驱动程序为未签名的驱动程序。请单击“Install this driver anyway”以继续操作。
有关更详细的说明,请参阅配置计算机以进行驱动程序部署、测试和调试。
成功安装示例驱动程序后,可随时对其进行测试。
在设备管理器中查看驱动程序
1. 在目标计算机的“命令提示符”窗口中,输入 devmgmt 以打开“设备管理器”。在“设备管理器”的“视图”菜单上,选择“依类型排序设备”。在设备树的“示例设备”节点中,找到示例 WDF Echo 驱动程序。
测试驱动程序
2. 通过键入 echoapp 启动测试 Echo 应用,以确认该驱动程序可正常运行。
复制
通过执行以下步骤查看驱动程序相关信息。
<- 在主机系统上
如果已关闭调试器,可在管理员命令提示符窗口中,使用以下命令重新打开它。
复制
使用 Ctrl+Break (Scroll Lock) 以进入正在目标计算机系统上运行的代码。
设置符号路径
要在 WinDbg 环境中将符号路径添加到 Microsoft 符号服务器,请使用 .symfix 命令。
复制
若要添加本地符号位置以便使用本地符号,请依次使用 .sympath+ 和 .reload /f 来添加路径。
复制
注意 带有 /f 强制选项的 .reload 命令可用于删除指定模块的所有符号信息,以及重新加载这些符号。在某些情况下,此命令还可用于自行重新加载或卸载模块。
注意 你必须加载正确的符号才能使用 WinDbg 提供的高级功能。如果未正确配置符号,则在尝试使用依赖于符号的功能时,将收到提示符号不可用的消息。
复制
注意
符号服务器
有多种方法可用于符号的使用。在许多情况下,在需要使用符号时,均可将电脑配置为从 Microsoft 提供的符号服务器访问它们。本演练假设使用此方法。如果你所在环境中的符号位于其他位置,请修改相关步骤以使用该位置。有关其他信息,请参阅符号存储区和符号服务器。
注意
理解源代码符号要求
要执行源调试,必须生成二进制文件的已检查(调试)版本。编译器将创建符号文件(.pdb 文件)。这些符号文件会向调试器显示二进制指令响应源行的方式。这些实际源文件本身必须也能够访问调试器。
符号文件不包含源代码文本。对于调试而言,链接器最好不优化您的代码。如果代码已经优化,则更难以进行源调试和访问本地变量,有时几乎不可能进行。如果你无法查看本地变量或源行,请设置以下生成选项:
复制
在调试器的命令区域中键入下列内容,以显示有关 Echo 驱动程序的信息:
复制
有关信息,请参阅
lm。
因为我们之前设置了 prefer_dml =1,所以某些输出的元素是可单击的热链接。 单击调试输出中的浏览所有全局符号链接,以显示有关以字母“a”开头的项目符号的信息。
复制
事实上,Echo 示例不包含以“a”开头的任何符号,因此,若要显示以 Echo 开头的 Echo 驱动程序相关联的所有符号相关信息,请键入
x ECHO!Echo*。
复制
有关信息,请参阅
x (Examine Symbols)。
!lmi 扩展显示有关某个模块的详细信息。键入 !lmi echo。输出应与如下所示的文本类似。
复制
5. 使用 !dh 扩展来显示标头信息,如下所示。
复制
即插即用设备树中设备驱动程序的相关信息可用于疑难解答。 例如,如果设备驱动程序未常驻在设备树中,则该设备驱动程序的安装可能存在问题。
有关设备节点调试扩展的详细信息,请参阅
!devnode。
<- 在主机系统上
若要查看即插即用设备树中的所有设备节点,请输入 !devnode 0 1 命令。
复制
通过使用 Ctrl+F 在生成的输出中进行搜索,查找名为 echo 的设备驱动程序。
应加载 Echo 设备驱动程序。使用 !devnode 0 1 echo 命令,以显示与 Echo 设备驱动程序相关联的即插即用信息,如下所示。
复制
上一命令中显示的输出包含与驱动程序正在运行的实例相关联的 PDO,在本例中为 0xffffe0007b71a960。输入
!devobj<PDO address> 命令,以在电脑上显示与 Echo 设备驱动程序相关联的即插即用信息。使用
!devnode 显示在电脑上的 PDO 地址,而不是使用此处所示的地址。
复制
显示在 !devnode 0 1 命令中的输出包含与驱动程序正在运行的实例相关联的 PDO 地址,在本例中为 0xffffe0007b71a960。输入
!devstack<PDO address> 命令,以显示与设备驱动程序相关联的即插即用信息。使用
!devnode 显示在电脑上的 PDO 地址,而不是使用如下所示的地址。
复制
输出显示,我们有一个非常简单的设备驱动程序堆栈。Echo 驱动程序是 PnPManager 节点的子节点。PnPManager 是根节点。
复制
此图展示了一个更复杂的设备节点树。
注意 有关更复杂的驱动程序堆栈的详细信息,请参阅 MSDN 上的驱动程序堆栈以及设备节点和设备堆栈。
注意
使用命令设置断点
为了能逐句通过代码并实时检查变量值,需启用断点并设置源代码的路径。
断点用于在某一特定的代码行处停止代码执行。然后,你可以从该断点处继续向前执行代码,以调试该特定的代码段。
若要使用调试命令设置断点,请使用以下 b 命令之一:
有关详细信息,请参阅调试参考文档中的在 WinDbg 中进行源代码调试。
<- 在主机系统上
使用 WinDbg UI 以确认在当前 WinDbg 会话中已启用“调试”>“源模式”。
通过键入以下命令,将本地代码位置添加到源路径。
复制
通过键入以下命令,将本地符号位置添加到符号路径。
复制
我们将使用 x 命令来检查与 Echo 驱动程序相关联的符号,以确定要用于断点的函数名称。 我们可以使用通配符或 Ctrl+F 来查找
DeviceAdd 的函数名称。
复制
上面的输出显示,Echo 驱动程序的 DeviceAdd 方法即为 ECHO!EchoEvtDeviceAdd。
此外,我们可以通过查看源代码,来查找断点所需的函数名称。
使用 bm 命令设置断点,该命令使用驱动程序名称,后跟要在其中设置断点的函数名称(例如 AddDevice),用感叹号隔开。我们将使用
AddDevice 监控驱动程序的加载进度。
复制
注意
你可以将不同的语法与设置变量结合使用,例如,<module>!<symbol>、<class>::<method>、‘<file.cpp>:<line number>’,或多次跳过 <condition> <#>。 有关详细信息,请参阅
WinDbg 和其他 Windows 调试器中的条件断点。
列出当前断点以确认断点已通过键入 bl 命令进行设置。
复制
“e”指示断点数字为 1 时将触发。
通过键入
g (Go) 命令,在目标计算机系统上重新启动代码执行。
-> 在目标计算机系统上
在 Windows 中,使用“设备管理器”图标或输入 mmc devmgmt.msc,即可打开设备管理器。在“设备管理器”中,展开“示例”节点。
右键单击 KMDF Echo 驱动程序条目,然后从菜单中选择“禁用”。
再次右键单击 KMDF Echo 驱动程序条目,然后从菜单中选择“启用”。
<- 在主机系统上
启用驱动程序时,应触发 AddDevice 调试断点,并且应暂停目标计算机系统上驱动程序代码的执行。命中断点时,该执行应在
AddDevice 例程开始处停止。调试命令输出将显示“Breakpoint 1 hit”。
通过键入 p 命令或按 F10 逐行通过代码,直至到达以下 AddDevice 例程的末尾处。括号字符“}”将突出显示,如下所示。
在下一部分中,我们将在执行 DeviceAdd 代码后,检查变量状态。
注意
修改断点状态
可使用以下命令修改现有断点:
或者,你可以通过在 WinDbg 中依次单击“编辑”>“断点”来修改断点。请注意,断点对话框仅适用于现有断点。必须从命令行设置新断点。
注意
设置内存访问断点
还可以设置在访问内存时触发的断点。使用 ba(在访问时中断)命令及以下语法。
复制
请注意,在任意给定时间内你只能设置四个数据断点,由你来确保正确对齐数据,否则将不会触发断点(即,字必须以能被 2 整除的地址结尾,双字必须以能被 4 整除的地址结尾,四字必须以能被 0 或 8 整除的地址结尾)
例如,要在特定内存地址上设置读/写断点,可以使用如下命令。
复制
注意
从“调试器命令”窗口逐句通过代码
可以使用以下命令逐句通过代码。括号内显示关联的键盘快捷键:
中断 (Ctrl+Break) - 只要系统正在运行且与 WinDbg 通信,该命令便会中断系统(内核调试器中的快捷键顺序是 Ctrl+C)。
运行到光标处(F7 或 Ctrl+F10)– 将光标置于源窗口或反汇编窗口中要中断执行的位置,然后按 F7,代码执行将运行到此处。请注意,如果代码执行流未到达光标所指示位置(例如,未执行 IF 语句),WinDbg 将不会中断,因为代码执行未到达所指示位置。
运行 (F5) – 运行直到遇到断点或发生如错误检查之类的事件。
单步执行 (F10) – 此命令以逐条通过语句或逐个通过指令的方式来执行代码。如果遇到调用,代码执行不进入被调用例程即可忽略调用。(如果编程语言是 C 或 C++ 且 WinDbg 在源模式下,则可使用“调试”>“源模式”开启或关闭源模式。)
跳入 (F11) – 除调用执行不进入已调用例程外,该命令类似 step-over。
跳出 (Shift+F11) – 该命令使执行从当前例程(调用堆栈中的当前位置)运行并退出。如果例程太多,此命令将很有用。
有关详细信息,请参阅调试参考文档中的在 WinDbg 中进行源代码调试。
本实验假定你已使用前面所述的流程停止在 AddDevice 例程处。若要查看此处的输出显示,请重复前面所述的步骤(如果需要)。
<- 在主机系统上
显示变量
使用“查看”>“本地”菜单项,以显示本地变量。
全局变量
可通过键入 <variable name> 找到全局变量地址的位置。
本地变量
通过键入 dv 命令,可针对给定框架显示所有本地变量的名称和值。
复制
Callstacks
注意
调用堆栈是已引导到程序计数器的当前位置的函数调用链。调用堆栈上的顶部函数是当前函数,下一个函数是调用了当前函数的函数,依此类推。
要显示调用堆栈,请使用 k* 命令:
<- 在主机系统上
1. 如果希望调用堆栈一直可用,可通过依次单击“查看”>“调用堆栈”查看它。单击窗口顶部的列以切换附加信息的显示。
2. 使用 kn 命令,以显示在中断状态下调试示例适配器代码时的调用堆栈。
复制
该调用堆栈显示,内核 (nt) 已调入即插即用代码 (PnP) 中,并且已调用驱动程序框架代码 (Wdf),该框架代码随后调用了 Echo 驱动程序的
DeviceAdd 函数。
注意
可使用
!process 调试器扩展,显示或设置进程信息。我们将通过设置断点来检查播放声音时所使用的进程。
<- 在主机系统上
键入 dv 命令,以检查与 EchoEvtIo 例程相关联的区域设置变量。
复制
使用 bc * 清除之前的断点。
复制
3. 使用以下命令,在 EchoEvtIo 例程上设置符号断点。
复制
列出断点以确认断点已正确进行设置。
复制
键入 g 命令以重新启动代码执行。
复制
-> 在目标计算机系统上
在目标计算机系统上运行 EchoApp.exe 驱动程序测试程序。
<- 在主机系统上
当测试应用运行时,将调用驱动程序中的 I/O 例程。此操作将触发断点,并且将暂停目标计算机系统上驱动程序代码的执行。
复制
使用 !process 命令,以显示运行 echoapp.exe 时所涉及到的当前进程。
复制
输出显示了当命中驱动程序写入事件上的断点时,该进程与正在运行的 echoapp.exe 相关联。有关详细信息,请参阅
!process。
使用 !process 0 0 显示所有进程的汇总信息。在输出中,使用 Ctrl+F 查找与 echoapp.exe 图像相关联的进程相同的进程地址。在下面显示的示例中,进程地址是 ffffe0007e6a7780。
复制
记录与 echoapp.exe 相关联的进程 ID,以供稍后在本实验中使用。 也可以使用 CTRL+C,将该地址复制到复制缓冲区以供以后使用。
_____________________________________________________(echoapp.exe 进程地址)
根据需要,在调试器中输入 g 以运行代码,直至 echoapp.exe 完成运行。它将多次命中读取和写入事件中的断点。在 echoapp.exe 完成运行后,按 Ctrl+ScrLk (Ctrl+Break) 即可进入调试器。
使用 !process 命令以确认你现在运行的是其他进程。在下面显示的输出中,进程中 System 的映像值不同于
Echo 的映像值。
复制
上面的输出显示了当我们暂停操作系统的运行时,系统进程 ffffe0007b65d900 仍在运行。
现在,可使用 !process 命令尝试观察你之前记录的与 echoapp.exe 相关联的进程 ID。提供你之前记录的 echoapp.exe 进程地址,而非下面示例中所示的进程地址。
复制
该进程对象不再可用,因为 echoapp.exe 进程已不再运行。
查看和设置线程的命令与查看和设置进程的命令非常类似。 使用
!thread 命令查看线程。使用
.thread 设置当前线程。
<- 在主机系统上
在调试器中输入 g,以在目标计算机系统上重新启动代码执行。
-> 在目标计算机系统上
在目标计算机系统上运行 EchoApp.exe 驱动程序测试程序。
<- 在主机系统上
将命中断点,且代码执行将暂停。
复制
要查看正在运行的线程,请键入
!thread。应显示类似以下内容的信息:
复制
请注意,映像名称为 echoapp.exe,其指示我们正在查看与测试应用相关联的线程。
4. 使用 !process 命令,以确定这是否是与 echoapp.exe 相关联的进程中正在运行的唯一线程。 请注意,该进程中正在运行的线程的线程数与使用 !thread 命令显示的正在运行的线程数相同。
复制
使用 !process 0 0 command 找到两个相关的进程的进程地址,并在此处记录这两个进程地址。
Cmd.exe:____________________________________________________________
EchoApp.exe:_______________________________________________________
复制
注意 也可以通过使用 !process 0 17 来显示有关每个进程的详细信息。此命令的输出可能会很耗时。此输出可使用 Ctrl+F 进行搜索。
使用 !process 命令,以列出电脑上正在运行的这两个进程的进程信息。提供 !process 0 0 输出中的进程地址,而非下面所示的地址。
此示例输出中的进程 ID 是你之前记录的 cmd.exe 进程 ID。请注意,此进程 ID 的映像名称是 cmd.exe。
复制
此示例输出中的进程 ID 是你之前记录的 echoapp.exe 进程 ID。
复制
在此处记录与这两个线程相关联的第一个线程地址。
Cmd.exe:____________________________________________________
EchoApp.exe:_________________________________________________
使用 !Thread 命令来显示有关当前线程的信息。
复制
正如预期的那样,当前线程为与 echoapp.exe 相关联的线程且正处于运行状态。
使用 !Thread 命令,以显示有关与 cmd.exe 进程相关联的线程的信息。提供你之前记录的线程地址。
复制
该线程与 cmd.exe 相关联且正处于等待状态。
提供等待线程 CMD.exe 的线程地址,以更改该等待线程的上下文。
复制
使用 k 命令,查看与等待线程相关联的调用堆栈。
复制
调用堆栈元素(如 KiCommitThreadWait)指示该线程未按预期方式运行。
注意
有关线程和进程的详细信息,请参阅 MSDN 上的以下参考部分:
线程和进程
更改上下文
<- 在主机系统上
中断请求级别 (IRQL) 用于管理中断服务的优先级。每个处理器都有一个 IRQL 设置,可用于提高或降低线程的优先级。在处理器的 IRQL 设置优先级或更低的优先级上发生的中断将被屏蔽,且不会干扰当前操作。在高于处理器的 IRQL 设置的优先级上发生的中断优先于当前操作。在调试器中断发生之前,!irql
扩展将在目标计算机的当前处理器上显示中断请求级别 (IRQL)。当目标计算机进入调试器时,IRQL 将更改,不过调试器中断发生前的有效 IRQL 将被保存且由
!irql 显示。
复制
使用
r (Registers) 命令,以针对当前处理器上的当前线程显示寄存器的内容。
复制
或者,你可以通过依次单击“查看”>“寄存器”来显示寄存器的内容。 有关详细信息,请参阅
r (Registers)。
当逐句通过汇编语言代码执行以及处于其他应用场景中时,查看寄存器的内容将会非常有用。有关汇编语言反汇编的详细信息,请参阅带有批注的 x86 反汇编和带有批注的
x64 反汇编。
有关寄存器的内容的信息,请参阅
x86 体系结构和
x64 体系结构。
要结束用户模式调试会话,请将调试器返回至休眠模式,并将目标应用程序设置为再次运行,输入 qd(退出和分离)命令。
请务必使用 g 命令以让目标计算机运行代码,以便可对其进行使用。使用 bc * 清除任何断点也是一个好办法,这样目标计算机便不会中断,并尝试连接到主计算机调试器。
复制
有关详细信息,请参阅调试参考文档中的在 WinDbg 中结束调试会话。
书籍
Advanced Windows Debugging,作者:Mario Hewardt 和 Daniel Pravat
Inside Windows Debugging: A Practical Guide to Debugging and Tracing Strategies in Windows®,作者:Tarik Soulami
Windows Internals,作者:Mark E. Russinovich、David A. Solomon 和 Alex Ionescu
视频
Defrag Tools 电视节目 WinDbg 第 13-29 集 http://channel9.msdn.com/Shows/Defrag-Tools
培训供应商:
OSRhttps://www.osr.com/
显示: 继承
保护
调试通用驱动程序 - 分步(Echo 内核模式)
本实验中引入了 WinDbg 内核调试器。 WinDbg 用于调试 Echo 内核模式的示例驱动程序代码。实验目标
本实验包含以下练习:引入调试工具、介绍常见调试命令、阐述断点的用法,以及演示调试扩展的用法。在本实验中,使用实时内核调试连接,以了解以下内容:
使用 Windows 调试器命令
使用标准命令(调用堆栈、变量、线程、IRQL)
使用高级驱动程序调试命令 (!commands)
使用符号
在实时调试中设置断点
查看调用堆栈
显示即插即用设备树
处理线程和进程上下文
注意 使用 Windows 调试器时,可执行两种类型的调试 - 用户或内核模式调试。
用户模式 - 应用程序和子系统在用户模式下的计算机上运行。用户模式下运行的进程将在其虚拟地址空间内执行此操作。限制这些进程获得对系统许多部分的直接访问权限,包括系统硬件、未被分配使用的内存和系统中可能有损系统整体性的其他部分。因为在用户模式下运行的进程将有效地与系统和其他用户模式进程隔离开来,所以它们不会干扰这些资源。
内核模式 - 内核模式是一种操作系统和特权程序在其中运行的处理器访问模式。内核模式代码有权访问系统的任何部分,且不像用户模式代码那样受到限制。它能够获得对运行在用户模式或内核模式中的任何其他进程的任何部分的访问权限。 许多核心操作系统功能和硬件设备驱动器均可在内核模式下运行。
本实验将重点介绍内核模式调试,因为它是用于调试多种设备驱动程序的方法。
此次练习将介绍用户模式和内核模式调试期间常用的调试命令。该练习还将介绍内核模式调试所使用的调试扩展(有时称为“!commands”)。
实验设置
你将需要以下硬件来完成该实验。一台运行 Windows 10 的台式机(主机)
一台运行 Windows 10 的台式机(目标计算机)
一根网络交叉电缆或一个网络集线器以及多根网络电缆,用于连接这两台电脑
对 Internet 的访问权限,以下载符号文件
你将需要以下软件来完成此实验。
Windows 10 SDK
Windows 10 WDK
Visual Studio 2015
Windows 10 的 Echo 驱动程序示例
本实验包括以下十一个部分。
第 1 部分:连接到内核模式 WinDbg 会话
第 2 部分:内核模式调试命令和技巧
第 3 部分:下载和生成 KMDF 通用 Echo 驱动程序
第 4 部分:在目标计算机系统上安装 KMDF Echo 驱动程序示例
第 5 部分:使用 WinDbg 显示驱动程序相关信息
第 6 部分:显示即插即用设备树的相关信息
第 7 部分:使用断点和源代码
第 8 部分:查看变量和调用堆栈
第 9 部分:显示进程和线程
第 10 部分:IRQL、寄存器和结束 WinDbg 会话
第 11 部分:Windows 调试资源
第 1 部分:连接到内核模式 WinDbg 会话
在第 1 部分中,你将在主机和目标计算机系统上配置网络调试。在本实验中,电脑需配置为使用以太网网络连接,以供进行内核调试。
本实验使用两台电脑。Windows 调试器在“主机”系统上运行,而 KMDF Echo 驱动程序在“目标计算机”系统上运行。
图示左侧的“主机”通过交叉以太网电缆连接到右侧的“目标计算机”。
本实验中的步骤假设你使用的是网络交叉电缆,不过如果你直接将主机和目标计算机插入网络集线器中,也可进行该实验。
为了与内核模式应用程序配合使用以及使用 Windbg,建议使用 KDNET over Ethernet 传输。有关如何使用以太网传输协议的信息,请参阅
WinDbg 入门(内核模式)。有关设置目标计算机的详细信息,请参阅准备用于手动部署驱动程序的计算机和通过网络电缆手动设置内核模式调试。
使用交叉以太网电缆配置内核模式调试
按照后续步骤,在目标计算机系统上启用内核模式调试。<- 在主机系统上
1. 在主机系统上打开命令提示符,并键入 ipconfig 以确定其 IP 地址。
复制
C:\>ipconfig 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 命令确定这两个系统之间的网络连接性。用你之前记录的主机计算机系统的 IP 地址来替代示例输出中所示的地址。
复制
C:\> ping 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
通过完成以下步骤,在目标计算机系统上启用内核模式调试。
1. 在目标计算机上,以管理员身份打开“命令提示符”窗口。输入此命令以启动调试。
复制
C:\> bcdedit /set {default} DEBUG YES
2. 键入此命令以启用测试签名。
复制
C:\> bcdedit /set TESTSIGNING ON
3. 键入以下命令以设置主机系统的 IP 地址。使用你之前记录的主机系统的 IP 地址,而非示例中所示的地址。
复制
C:\> bcdedit /dbgsettings net hostip:192.168.1.1 port:50000 key:1.2.3.4
4. 键入此命令以确认 dbgsettings 均已正确设置。
复制
C:\> bcdedit /dbgsettings key 1.2.3.4 debugtype NET hostip 169.168.1.1 port 50000 dhcp Yes The operation completed successfully.
注意
防火墙和调试器
如果你收到一条来自防火墙的弹出式消息,并且如果你希望使用调试器,请解除所需网络类型的锁定。
<- 在主机系统上
1. 在主计算机上,以管理员身份打开“命令提示符”窗口。转到 WinDbg.exe 目录。 使用 x64 版本的 WinDbg.exe,它来自于作为 Windows 工具包安装的一部分进行安装的 Windows WDK。
复制
C:\> Cd C:\Program Files(x86)\Windows Kits\10.0\Debuggers\x64
2. 使用以下命令启动具有远程用户调试功能的 WinDbg。密钥值和端口值须与我们之前使用 BCDEdit 在目标计算机系统上设置的值匹配。
复制
WinDbg –k net:port=50000,key=1.2.3.4
-> 在目标计算机系统上
重新启动目标计算机系统。
<- 在主机系统上
在一到两分钟内,调试输出应该会显示在主机系统上。
“调试器命令”窗口是 WinDbg 中的主要调试信息窗口。您可在此窗口中输入调试器命令并查看命令输出。
“调试器命令”窗口分为两个窗格。在窗口底部的较小窗格(命令输入窗格)中键入命令,在窗口顶部的较大窗格中查看命令输出。
在命令输入窗格中,使用向上键和向下键以滚动显示历史命令。命令显示后,你可以编辑它或按 ENTER 运行它。
第 2 部分:内核模式调试命令和技巧
在第 2 部分中,将使用调试命令来显示有关目标计算机系统的信息。-> 在主机系统上
支持带有 .prefer_dml 的调试器标记语言 (DML)
一些调试命令将显示采用调试器标记语言的文本,以便你可以通过在其上单击快速收集更多信息。
1. 在 WinDBg 中使用 Ctrl+Break (Scroll Lock) 以进入到正在目标计算机系统运行的代码。目标计算机系统可能需要一些时间来进行响应。2. 键入以下命令,以在调试器命令窗口中启用 DML。
复制
0: kd> .prefer_dml 1 DML versions of commands on by default
使用 .hh 以获取帮助
你可以使用 .hh 命令访问参考命令帮助。
3. 键入以下命令,以查看 .prefer_dml 的参考命令帮助。
复制
0: kd> .hh .prefer_dml
调试器帮助文件将显示有关 .prefer_dml 命令的帮助。
设置调试掩码
4. 键入以下命令,以更改默认调试位掩码,以便来自目标计算机系统的所有调试消息都显示在调试器中。
复制
0: kd> !ed nt!Kd_DEFAULT_MASK 0xFFFFFFFF
在目标计算机系统上显示 Windows 的版本
5. 通过在 WinDbg 窗口中键入
vertarget (Show Target Computer Version) 命令,从而在目标计算机系统上显示详细的版本信息。
复制
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
列出加载的模块
6. 通过在 WinDbg 窗口中键入
lm (List Loaded Modules) 命令来显示加载的模块,你可以验证是否正在使用正确的内核模式进程。
复制
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 ...
注意 在本实验中,已省略的输出用“…”指示。
7. 要请求特定模块的详细信息,请使用所示的 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
8. 由于我们尚未设置符号路径和加载的符号,因此调试器中提供了有限的信息。
第 3 部分:下载和生成 KMDF 通用 Echo 驱动程序
在第 3 部分中,将下载和生成 KMDF 通用 Echo 驱动程序。在使用 WinDbg 时,你通常会用到自己的驱动程序代码。要熟悉 WinDbg 操作,请使用 KMDF 模板“Echo”示例驱动程序。若提供源代码,则更易于用户理解显示在 WinDbg 中的信息。此外,该示例还将用于演示如何逐句通过本机内核模式代码。这项技术对调试复杂的内核模式代码问题十分有价值。
若要下载和生成 Echo 示例音频驱动程序,请完成以下步骤。
从 GitHub 下载并解压缩 KMDF Echo 示例
你可以使用浏览器,在此处通过 GitHub 查看 Echo 示例:
https://github.com/Microsoft/Windows-driver-samples/tree/97cf5197cf5b882b2c689d8dc2b555f2edf8f418/general/echo/kmdf
你可以在此处读取该示例:
https://github.com/Microsoft/Windows-driver-samples/blob/97cf5197cf5b882b2c689d8dc2b555f2edf8f418/general/echo/kmdf/ReadMe.md
你可以在此处浏览所有的通用驱动程序示例:
https://github.com/Microsoft/Windows-driver-samples
KMDF Echo 示例位于 general 文件夹中。
a.对于本实验,我们将以压缩文件的形式下载通用驱动程序示例。
https://github.com/Microsoft/Windows-driver-samples/archive/master.zip
b. 将 master.zip 文件下载到本地硬盘驱动器中。
c. 右键单击 Windows-driver-samples-master.zip,然后选择“全部解压缩”。指定新的文件夹,或浏览到一个现有文件夹以存储已解压缩的文件。例如,你可以将 C:\DriverSamples\ 指定为要在其中解压缩文件的新文件夹。
d.解压缩文件后,请导航到以下子文件夹。
C:\DriverSamples\general\echo\kmdf
在 Visual Studio 中打开驱动程序解决方案
在 Microsoft Visual Studio 中,依次单击“文件”>“打开”>“项目/解决方案...”,然后导航到包含已解压缩文件的文件夹(例如,C:\DriverSamples\general\echo\kmdf)。双击
kmdfecho 解决方案文件以打开它。
在 Visual Studio 中,找到“解决方案资源管理器”。(如果尚未打开,请从“视图”菜单中选择“解决方案资源管理器”。)在“解决方案资源管理器”中,你可以看到一个具有三个项目的解决方案。
设置示例的配置和平台
在“解决方案资源管理器”中,右键单击“解决方案‘kmdfecho’(3 个项目)”,然后选择“配置管理器”。请确保配置和平台设置与四个项目相同。默认情况下,在所有这些项目中都将配置设置为 "Win10 Debug",而将平台设置为 "Win64"。如果你对其中一个项目做了任何配置和/或平台更改,则必须对剩余三个项目做相同的更改。
设置运行时库
设置运行时库 - 将运行时库的版本从 DLL 版本更改为非 DLL 版本。如果没有此设置,则必须将 MSVC 运行时单独安装到目标计算机。
检查驱动程序签名
打开 Echo 驱动程序的属性页,并确保“驱动程序签名”>“签名模式”已设置为“测试签名”。之所以必须这样做,是因为 Windows 要求对驱动程序进行签名。
使用 Visual Studio 生成示例
在 Visual Studio 中,依次单击“生成”>“生成解决方案”。
如果一切正常,生成窗口将显示一条消息,指示所有这三个项目均已成功生成。
查找生成的驱动程序文件
在文件资源管理器中,导航到包含该示例的已解压缩文件的文件夹。例如,如果 C:\DriverSamples\general\echo\kmdf 是你前面指定的文件夹所在的路径,则导航至该路径。在该文件夹内,已编译的驱动程序文件的位置会有所不同,具体取决于“配置管理器”中选定的配置和平台设置。例如,如果让默认设置保持不变,则编译的驱动程序文件将针对 64 位调试版保存到名为 \x64\Debug 的文件夹中。
导航到包含 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 驱动程序通信。 |
找到 USB 便携驱动器或设置网络共享,以将生成的驱动程序文件和测试 EchoApp 从主机复制到目标计算机系统。
在下一部分中,将代码复制到目标计算机系统中,然后安装和测试驱动程序。
第 4 部分:在目标计算机系统上安装 KMDF Echo 驱动程序示例
在第 4 部分中,你将使用 Devcon 来安装 Echo 示例驱动程序。-> 在目标计算机系统上
安装驱动程序的计算机称为“目标计算机”或“测试计算机”。通常,此类计算机独立于开发和生成驱动程序包的计算机。开发和生成驱动程序的计算机称为“主计算机”。
将驱动程序包移至目标计算机并安装该驱动程序的过程称为“部署”驱动程序。可自动或手动部署示例 Echo 驱动程序。
手动部署驱动程序之前,必须通过打开测试签名准备目标计算机。还需要在 WDK 安装中找到 DevCon 工具。完成该操作后,你便可以随时运行生成的驱动程序示例了。
通过执行以下步骤在目标计算机系统上安装驱动程序。
准备目标计算机
以管理员身份打开命令提示符窗口。然后,输入以下命令:
bcdedit /set TESTSIGNING ON
重新启动目标计算机。
<- 在主机系统上
导航至 WDK 安装中的 Tools 文件夹,并找到 DevCon 工具。例如,查找以下文件夹:
C:\Program Files (x86)\Windows Kits\10.0\Tools\x64\devcon.exe在目标计算机上为生成的驱动程序包创建文件夹(例如,C:\EchoDriver)。从主计算机上前面描述的生成驱动程序包中复制所有文件,并将这些文件都保存到在目标计算机上创建的文件夹中。
在主计算机系统上找到 .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此时将显示一个对话框,指示测试驱动程序为未签名的驱动程序。请单击“Install this driver anyway”以继续操作。
有关更详细的说明,请参阅配置计算机以进行驱动程序部署、测试和调试。
成功安装示例驱动程序后,可随时对其进行测试。
在设备管理器中查看驱动程序
1. 在目标计算机的“命令提示符”窗口中,输入 devmgmt 以打开“设备管理器”。在“设备管理器”的“视图”菜单上,选择“依类型排序设备”。在设备树的“示例设备”节点中,找到示例 WDF Echo 驱动程序。
测试驱动程序
2. 通过键入 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
第 5 部分:使用 WinDbg 显示有关驱动程序的信息
在第 5 部分中,将设置符号路径,并使用内核调试器命令来显示有关 KMDF Echo 示例驱动程序的信息。通过执行以下步骤查看驱动程序相关信息。
<- 在主机系统上
如果已关闭调试器,可在管理员命令提示符窗口中,使用以下命令重新打开它。
复制
WinDbg -k net:port=50000,key=1.2.3.4
使用 Ctrl+Break (Scroll Lock) 以进入正在目标计算机系统上运行的代码。
设置符号路径
要在 WinDbg 环境中将符号路径添加到 Microsoft 符号服务器,请使用 .symfix 命令。
复制
0: kd> .symfix
若要添加本地符号位置以便使用本地符号,请依次使用 .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 提供的符号服务器访问它们。本演练假设使用此方法。如果你所在环境中的符号位于其他位置,请修改相关步骤以使用该位置。有关其他信息,请参阅符号存储区和符号服务器。
注意
理解源代码符号要求
要执行源调试,必须生成二进制文件的已检查(调试)版本。编译器将创建符号文件(.pdb 文件)。这些符号文件会向调试器显示二进制指令响应源行的方式。这些实际源文件本身必须也能够访问调试器。
符号文件不包含源代码文本。对于调试而言,链接器最好不优化您的代码。如果代码已经优化,则更难以进行源调试和访问本地变量,有时几乎不可能进行。如果你无法查看本地变量或源行,请设置以下生成选项:
复制
set COMPILE_DEBUG=1 set ENABLE_OPTIMIZER=0
在调试器的命令区域中键入下列内容,以显示有关 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。
因为我们之前设置了 prefer_dml =1,所以某些输出的元素是可单击的热链接。 单击调试输出中的浏览所有全局符号链接,以显示有关以字母“a”开头的项目符号的信息。
复制
0: kd> x /D Echo!a*
事实上,Echo 示例不包含以“a”开头的任何符号,因此,若要显示以 Echo 开头的 Echo 驱动程序相关联的所有符号相关信息,请键入
x 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 (Examine Symbols)。
!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 部分:显示即插即用设备树的相关信息
在第 6 部分中,将显示有关 Echo 示例设备驱动程序的信息,以及它位于即插即用设备树中的哪个位置。即插即用设备树中设备驱动程序的相关信息可用于疑难解答。 例如,如果设备驱动程序未常驻在设备树中,则该设备驱动程序的安装可能存在问题。
有关设备节点调试扩展的详细信息,请参阅
!devnode。
<- 在主机系统上
若要查看即插即用设备树中的所有设备节点,请输入 !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… …
通过使用 Ctrl+F 在生成的输出中进行搜索,查找名为 echo 的设备驱动程序。
应加载 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) …
上一命令中显示的输出包含与驱动程序正在运行的实例相关联的 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.
显示在 !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
此图展示了一个更复杂的设备节点树。
注意 有关更复杂的驱动程序堆栈的详细信息,请参阅 MSDN 上的驱动程序堆栈以及设备节点和设备堆栈。
第 7 部分:使用断点和源代码
在第 7 部分中,将设置断点并逐句通过内核模式源代码。注意
使用命令设置断点
为了能逐句通过代码并实时检查变量值,需启用断点并设置源代码的路径。
断点用于在某一特定的代码行处停止代码执行。然后,你可以从该断点处继续向前执行代码,以调试该特定的代码段。
若要使用调试命令设置断点,请使用以下 b 命令之一:
命令 | 说明 |
---|---|
bp | 设置断点,卸载其所在模块时将激活该断点。 |
bu | 设置断点,卸载模块时无法解析该断点,重新加载模块时重新启用该断点。 |
bm | 为符号设置断点。此命令将适当使用 bu 或 bp,并允许使用通配符 * 在所有相匹配(例如,一个类中的所有方法)的每一个符号上设置断点。 |
有关详细信息,请参阅调试参考文档中的在 WinDbg 中进行源代码调试。
<- 在主机系统上
使用 WinDbg UI 以确认在当前 WinDbg 会话中已启用“调试”>“源模式”。
通过键入以下命令,将本地代码位置添加到源路径。
复制
.srcpath+ C:\DriverSamples\KMDF_Echo_Sample\driver\AutoSync
通过键入以下命令,将本地符号位置添加到符号路径。
复制
.sympath+ C:\DriverSamples\KMDF_Echo_Sample\driver\AutoSync
我们将使用 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。
此外,我们可以通过查看源代码,来查找断点所需的函数名称。
使用 bm 命令设置断点,该命令使用驱动程序名称,后跟要在其中设置断点的函数名称(例如 AddDevice),用感叹号隔开。我们将使用
AddDevice 监控驱动程序的加载进度。
复制
0: kd> bm ECHO!EchoEvtDeviceAdd 1: fffff801`0bf9b1c0 @!"ECHO!EchoEvtDeviceAdd"
注意
你可以将不同的语法与设置变量结合使用,例如,<module>!<symbol>、<class>::<method>、‘<file.cpp>:<line number>’,或多次跳过 <condition> <#>。 有关详细信息,请参阅
WinDbg 和其他 Windows 调试器中的条件断点。
列出当前断点以确认断点已通过键入 bl 命令进行设置。
复制
0: kd> bl 1 e fffff801`0bf9b1c0 0001 (0001) ECHO!EchoEvtDeviceAdd
“e”指示断点数字为 1 时将触发。
通过键入
g (Go) 命令,在目标计算机系统上重新启动代码执行。
-> 在目标计算机系统上
在 Windows 中,使用“设备管理器”图标或输入 mmc devmgmt.msc,即可打开设备管理器。在“设备管理器”中,展开“示例”节点。
右键单击 KMDF Echo 驱动程序条目,然后从菜单中选择“禁用”。
再次右键单击 KMDF Echo 驱动程序条目,然后从菜单中选择“启用”。
<- 在主机系统上
启用驱动程序时,应触发 AddDevice 调试断点,并且应暂停目标计算机系统上驱动程序代码的执行。命中断点时,该执行应在
AddDevice 例程开始处停止。调试命令输出将显示“Breakpoint 1 hit”。
通过键入 p 命令或按 F10 逐行通过代码,直至到达以下 AddDevice 例程的末尾处。括号字符“}”将突出显示,如下所示。
在下一部分中,我们将在执行 DeviceAdd 代码后,检查变量状态。
注意
修改断点状态
可使用以下命令修改现有断点:
命令 | 说明 |
---|---|
bl | 列出断点。 |
bc | 从列表中清除断点。使用 bc * 可清除所有断点。 |
bd | 禁用断点。使用 bd * 可禁用所有断点。 |
be | 启用断点。使用 be * 可启用所有断点。 |
或者,你可以通过在 WinDbg 中依次单击“编辑”>“断点”来修改断点。请注意,断点对话框仅适用于现有断点。必须从命令行设置新断点。
注意
设置内存访问断点
还可以设置在访问内存时触发的断点。使用 ba(在访问时中断)命令及以下语法。
复制
ba <access> <size> <address> {options}
选项 | 说明 |
---|---|
e | 执行(当 CPU 从地址获得一条指令时) |
r | 读/写(当 CPU 读取或写入地址时) |
w | 写(当 CPU 写入地址时) |
请注意,在任意给定时间内你只能设置四个数据断点,由你来确保正确对齐数据,否则将不会触发断点(即,字必须以能被 2 整除的地址结尾,双字必须以能被 4 整除的地址结尾,四字必须以能被 0 或 8 整除的地址结尾)
例如,要在特定内存地址上设置读/写断点,可以使用如下命令。
复制
ba r 4 0x0003f7bf0
注意
从“调试器命令”窗口逐句通过代码
可以使用以下命令逐句通过代码。括号内显示关联的键盘快捷键:
中断 (Ctrl+Break) - 只要系统正在运行且与 WinDbg 通信,该命令便会中断系统(内核调试器中的快捷键顺序是 Ctrl+C)。
运行到光标处(F7 或 Ctrl+F10)– 将光标置于源窗口或反汇编窗口中要中断执行的位置,然后按 F7,代码执行将运行到此处。请注意,如果代码执行流未到达光标所指示位置(例如,未执行 IF 语句),WinDbg 将不会中断,因为代码执行未到达所指示位置。
运行 (F5) – 运行直到遇到断点或发生如错误检查之类的事件。
单步执行 (F10) – 此命令以逐条通过语句或逐个通过指令的方式来执行代码。如果遇到调用,代码执行不进入被调用例程即可忽略调用。(如果编程语言是 C 或 C++ 且 WinDbg 在源模式下,则可使用“调试”>“源模式”开启或关闭源模式。)
跳入 (F11) – 除调用执行不进入已调用例程外,该命令类似 step-over。
跳出 (Shift+F11) – 该命令使执行从当前例程(调用堆栈中的当前位置)运行并退出。如果例程太多,此命令将很有用。
有关详细信息,请参阅调试参考文档中的在 WinDbg 中进行源代码调试。
第 8 部分:查看变量和调用堆栈
在第 8 部分中,将显示有关变量和调用堆栈的信息。本实验假定你已使用前面所述的流程停止在 AddDevice 例程处。若要查看此处的输出显示,请重复前面所述的步骤(如果需要)。
<- 在主机系统上
显示变量
使用“查看”>“本地”菜单项,以显示本地变量。
全局变量
可通过键入 <variable name> 找到全局变量地址的位置。
本地变量
通过键入 dv 命令,可针对给定框架显示所有本地变量的名称和值。
复制
0: kd> dv Driver = 0x00001fff`7ff9c838 DeviceInit = 0xffffd001`51978190 status = 0n0
Callstacks
注意
调用堆栈是已引导到程序计数器的当前位置的函数调用链。调用堆栈上的顶部函数是当前函数,下一个函数是调用了当前函数的函数,依此类推。
要显示调用堆栈,请使用 k* 命令:
命令 | 说明 |
---|---|
kb | 显示堆栈和前三个参数。 |
kp | 显示堆栈和参数的完整列表。 |
kn | 允许你查看堆栈及其旁边的框架信息。 |
<- 在主机系统上
1. 如果希望调用堆栈一直可用,可通过依次单击“查看”>“调用堆栈”查看它。单击窗口顶部的列以切换附加信息的显示。
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) 中,并且已调用驱动程序框架代码 (Wdf),该框架代码随后调用了 Echo 驱动程序的
DeviceAdd 函数。
第 9 部分:显示进程和线程
进程
在第 9 部分中,将显示有关内核模式下运行的进程和线程的信息。注意
可使用
!process 调试器扩展,显示或设置进程信息。我们将通过设置断点来检查播放声音时所使用的进程。
<- 在主机系统上
键入 dv 命令,以检查与 EchoEvtIo 例程相关联的区域设置变量。
复制
0: kd> dv ECHO!EchoEvtIo* ECHO!EchoEvtIoQueueContextDestroy ECHO!EchoEvtIoWrite ECHO!EchoEvtIoRead
使用 bc * 清除之前的断点。
复制
0: kd> bc *
3. 使用以下命令,在 EchoEvtIo 例程上设置符号断点。
复制
0: kd> bm ECHO!EchoEvtIo* 2: aade5490 @!”ECHO!EchoEvtIoQueueContextDestroy” 3: aade55d0 @!”ECHO!EchoEvtIoWrite” 4: aade54c0 @!”ECHO!EchoEvtIoRead”
列出断点以确认断点已正确进行设置。
复制
0: kd> bl 1 e aabf0490 [c:\Samples\kmdf echo sample\c++\driver\autosync\queue.c @ 197] 0001 (0001) ECHO!EchoEvtIoQueueContextDestroy ...
键入 g 命令以重新启动代码执行。
复制
0: kd> g
-> 在目标计算机系统上
在目标计算机系统上运行 EchoApp.exe 驱动程序测试程序。
<- 在主机系统上
当测试应用运行时,将调用驱动程序中的 I/O 例程。此操作将触发断点,并且将暂停目标计算机系统上驱动程序代码的执行。
复制
Breakpoint 2 hit ECHO!EchoEvtIoWrite: fffff801`0bf95810 4c89442418 mov qword ptr [rsp+18h],r8
使用 !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。
使用 !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 ...
记录与 echoapp.exe 相关联的进程 ID,以供稍后在本实验中使用。 也可以使用 CTRL+C,将该地址复制到复制缓冲区以供以后使用。
_____________________________________________________(echoapp.exe 进程地址)
根据需要,在调试器中输入 g 以运行代码,直至 echoapp.exe 完成运行。它将多次命中读取和写入事件中的断点。在 echoapp.exe 完成运行后,按 Ctrl+ScrLk (Ctrl+Break) 即可进入调试器。
使用 !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 仍在运行。
现在,可使用 !process 命令尝试观察你之前记录的与 echoapp.exe 相关联的进程 ID。提供你之前记录的 echoapp.exe 进程地址,而非下面示例中所示的进程地址。
复制
0: kd> !process ffffe0007e6a7780 TYPE mismatch for process object at 82a9acc0
该进程对象不再可用,因为 echoapp.exe 进程已不再运行。
线程
注意查看和设置线程的命令与查看和设置进程的命令非常类似。 使用
!thread 命令查看线程。使用
.thread 设置当前线程。
<- 在主机系统上
在调试器中输入 g,以在目标计算机系统上重新启动代码执行。
-> 在目标计算机系统上
在目标计算机系统上运行 EchoApp.exe 驱动程序测试程序。
<- 在主机系统上
将命中断点,且代码执行将暂停。
复制
Breakpoint 4 hit ECHO!EchoEvtIoRead: aade54c0 55 push ebp
要查看正在运行的线程,请键入
!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,其指示我们正在查看与测试应用相关联的线程。
4. 使用 !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
使用 !process 0 0 command 找到两个相关的进程的进程地址,并在此处记录这两个进程地址。
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 进行搜索。
使用 !process 命令,以列出电脑上正在运行的这两个进程的进程信息。提供 !process 0 0 输出中的进程地址,而非下面所示的地址。
此示例输出中的进程 ID 是你之前记录的 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 ...
此示例输出中的进程 ID 是你之前记录的 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 ...
在此处记录与这两个线程相关联的第一个线程地址。
Cmd.exe:____________________________________________________
EchoApp.exe:_________________________________________________
使用 !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 相关联的线程且正处于运行状态。
使用 !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 相关联且正处于等待状态。
提供等待线程 CMD.exe 的线程地址,以更改该等待线程的上下文。
复制
0: kd> .Thread ffffe0007cf34880 Implicit thread is now ffffe000`7cf34880
使用 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)指示该线程未按预期方式运行。
注意
有关线程和进程的详细信息,请参阅 MSDN 上的以下参考部分:
线程和进程
更改上下文
第 10 部分:IRQL、寄存器和结束 WinDbg 会话
查看已保存的 IRQL
在第 10 部分中,将显示 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 (Registers)。
当逐句通过汇编语言代码执行以及处于其他应用场景中时,查看寄存器的内容将会非常有用。有关汇编语言反汇编的详细信息,请参阅带有批注的 x86 反汇编和带有批注的
x64 反汇编。
有关寄存器的内容的信息,请参阅
x86 体系结构和
x64 体系结构。
结束 WinDbg 会话
<- 在主机系统上要结束用户模式调试会话,请将调试器返回至休眠模式,并将目标应用程序设置为再次运行,输入 qd(退出和分离)命令。
请务必使用 g 命令以让目标计算机运行代码,以便可对其进行使用。使用 bc * 清除任何断点也是一个好办法,这样目标计算机便不会中断,并尝试连接到主计算机调试器。
复制
0: kd> qd
有关详细信息,请参阅调试参考文档中的在 WinDbg 中结束调试会话。
第 11 部分:Windows 调试资源
Windows 调试提供附加信息。 请注意,某些手册将使用旧版 Windows(例如其示例中使用的是 Windows Vista),不过所讨论的概念适用于大多数版本的 Windows。书籍
Advanced Windows Debugging,作者:Mario Hewardt 和 Daniel Pravat
Inside Windows Debugging: A Practical Guide to Debugging and Tracing Strategies in Windows®,作者:Tarik Soulami
Windows Internals,作者:Mark E. Russinovich、David A. Solomon 和 Alex Ionescu
视频
Defrag Tools 电视节目 WinDbg 第 13-29 集 http://channel9.msdn.com/Shows/Defrag-Tools
培训供应商:
OSRhttps://www.osr.com/
相关主题
标准调试技巧专业调试技巧Windows 调试入门显示: 继承
保护
相关文章推荐
- Kmd教程1-内核模式驱动程序基础
- 在内核模式驱动程序中调用 DLL
- 使用 C++ 编写内核模式驱动程序的优点与缺点
- windbg 如何再内核模式调试用户空间的程序
- 内核术语--内核模式,用户模式,内核对象,内核调试,安全,注册表,Unicode,驱动
- Kmd教程1-内核模式驱动程序基础
- 内核模式驱动程序的网络结构 .
- windbg 内核模式调试用户进程
- VS2012 + WDK8.0 + Win8调试内核驱动程序(一)
- WIndows内核模式驱动程序运行环境
- 如何从内核模式设备驱动程序中打开一个文件以及如何读取或写入文件
- 内核模式驱动程序框架 (KMDF)处理I/O请求的情况
- 【转帖】使用 C++ 编写内核模式驱动程序的优点与缺点
- windbg 如何再内核模式调试用户空间的程序
- 使用 C++ 编写内核模式驱动程序的优点与缺点
- Kmd教程1-内核模式驱动程序基础
- 【转帖】如何从内核模式设备驱动程序中打开一个文件以及如何读取或写入文件
- 用windbg内核模式调试用户态程序
- windbg 如何再内核模式调试用户空间的程序
- 使用WinDbg内核调试 分类: windows驱动程序WDM 2013-09-25 14:43 493人阅读 评论(0) 收藏