调试驱动程序 - 分步实验室(Sysvad 内核模式)
此实验室提供动手练习,演示如何调试 Sysvad 音频内核模式设备驱动程序。
Microsoft Windows 调试器 (WinDbg) 是一款功能强大的基于 Windows 的调试工具,可用于执行用户模式和内核模式调试。 WinDbg 可对 Windows 内核、内核模式驱动程序和系统服务以及用户模式应用程序和驱动程序进行源级调试。
WinDbg 可以逐步分析源代码、设置断点、查看变量(包括 C++ 对象)、堆栈跟踪和内存。 其调试器命令窗口允许用户发出各种命令。
实验室设置
要完成实验室设置,需要以下硬件:
- 运行 Windows 10 的笔记本电脑或台式计算机(主机)
- 运行 Windows 10 的笔记本电脑或台式计算机(目标)
- 用于连接两台计算机的网络集线器/路由器以及网络电缆
- 访问 Internet 以下载符号文件
需要以下软件才能完成实验室设置。
- Microsoft Visual Studio 2017
- 适用于 Windows 10 的 Windows 软件开发工具包 (SDK)
- 适用于 Windows 10 的 Windows 驱动程序工具包 (WDK)
- 适用于 Windows 10 的示例 Sysvad 音频驱动程序
有关下载和安装 WDK 的信息,请参阅下载 Windows 驱动程序工具包 (WDK)。
Sysvad 调试演练
本实验室将指导你完成内核模式驱动程序的调试过程。 练习使用 Syvad 虚拟音频驱动程序示例。 由于 Syvad 音频驱动程序不与实际音频硬件交互,因此可用于大多数设备上。 实验室包括以下任务:
- 第 1 部分:连接到内核模式 WinDbg 会话
- 第 2 部分:内核模式调试命令和技术
- 第 3 部分:下载并生成 Sysvad 音频驱动程序
- 第 4 部分:在目标系统上安装 Sysvad 音频驱动程序
- 第 5 部分:使用 WinDbg 显示驱动程序的相关信息
- 第 6 部分:显示即插即用设备树信息
- 第 7 部分:使用断点和源代码
- 第 8 部分:查看变量
- 第 9 部分:查看调用堆栈
- 第 10 部分:显示进程和线程
- 第 11 部分:IRQL、寄存器和反汇编
- 第 12 部分:使用内存
- 第 13 部分:结束 WinDbg 会话
- 第 14 部分:Windows 调试资源
Echo 驱动程序实验室
Echo 驱动程序比 Sysvad 音频驱动程序更为简单。 如果是 WinDbg 的新手,可以考虑先完成调试通用驱动程序 - 分步实验室(Echo 内核模式)。 该实验室重复使用实验室中的设置说明,因此如果已经完成了该实验室,则可以跳过此处的第 1 和第 2 部分。
第 1 部分:连接到内核模式 WinDbg 会话
在第 1 部分中,将在主机和目标系统上配置网络调试。
此实验室中的计算机需要配置为使用以太网网络连接进行内核调试。
此实验室使用两台计算机。 WinDbg 在主机系统上运行,而 Sysvad 驱动程序在目标系统上运行。
使用网络集线器/路由器和网线连接两台计算机。
要使用内核模式应用程序和 WinDbg,建议使用通过以太网传输的 KDNET。 有关如何使用以太网传输协议的信息,请参阅 WinDbg(内核模式)入门指南。 有关设置目标计算机的详细信息,请参阅为手动部署驱动程序准备计算机以及自动设置 KDNET 网络内核调试。
使用以太网配置内核模式调试
要在目标系统上启用内核模式调试,请执行以下步骤。
<- 在主机系统上
- 在主机系统上打开命令提示符,键入 ipconfig /all 以确定其 IP 地址。
C:\>ipconfig /all
Windows IP Configuration
Host Name . . . . . . . . . . . . : TARGETPC
...
Ethernet adapter Ethernet:
Connection-specific DNS Suffix . :
Link-local IPv6 Address . . . . . : fe80::c8b6:db13:d1e8:b13b3
Autoconfiguration IPv4 Address. . : 169.182.1.1
Subnet Mask . . . . . . . . . . . : 255.255.0.0
Default Gateway . . . . . . . . . :
记录下主机系统的 IP 地址:______________________________________
记录下主机系统的主机名:______________________________________
-> 在目标系统上
- 在目标系统上打开命令提示符,使用 ping 命令来确认两个系统之间的网络连接。 使用记录下的主机系统的实际 IP 地址,而不是示例输出中显示的 169.182.1.1。
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
要使用 KDNET 实用工具在目标系统上启用内核模式调试,请执行以下步骤。
在主机系统中,找到 WDK KDNET 目录。 默认情况下,它位于这里。
C:\Program Files (x86)\Windows Kits\10\Debuggers\x64
此实验室假设目标计算机和主机都运行的 64 位版本的 Windows。 如果情况并非如此,最好的办法是在主电脑上运行与目标电脑相同“位数”的工具。 例如,如果目标计算机运行的是 32 位 Windows,请在主机上运行 32 版本的调试器。 有关详细信息,请参阅选择 32 位或 64 位调试工具。
找到这两个文件,并将它们复制到网络共享或优盘中,以便在目标计算机上使用。
kdnet.exe
VerifiedNICList.xml
在目标计算机上,以管理员身份打开“命令提示符”窗口。 输入此命令以验证目标计算机上的 NIC 是否支持。
C:\KDNET>kdnet
Network debugging is supported on the following NICs:
busparams=0.25.0, Intel(R) 82579LM Gigabit Network Connection, KDNET is running on this NIC.kdnet.exe
- 键入此命令可以设置主机系统的 IP 地址。 使用记录下的主机系统的实际 IP 地址,而不是示例输出中显示的 169.182.1.1。 为每个目标/主机配对选择一个唯一的端口地址,如 50010。
C:\>kdnet 169.182.1.1 50010
Enabling network debugging on Intel(R) 82577LM Gigabit Network Connection.
Key=2steg4fzbj2sz.23418vzkd4ko3.1g34ou07z4pev.1sp3yo9yz874p
重要
在使用 BCDEdit 更改启动信息之前,可能需要在测试电脑上暂时挂起 Windows 安全功能,例如 BitLocker 和安全启动。 测试完成后重新启用这些安全功能,并在禁用安全功能时适当地管理测试电脑。 UEFI 中通常已禁用安全启动。 要访问 UEFI 设置,请使用系统、恢复、高级启动。 重新启动后,选择“故障排除”、“高级选项”、“UEFI 固件设置”。 请谨慎操作,因为错误设置 UEFI 选项或禁用 BitLocker 可能会导致系统无法运行。
- 键入此命令以确认已正确设置 dbgsettings。
C:\> bcdedit /dbgsettings
busparams 0.25.0
key 2steg4fzbj2sz.23418vzkd4ko3.1g34ou07z4pev.1sp3yo9yz874p
debugtype NET
hostip 169.182.1.1
port 50010
dhcp Yes
The operation completed successfully.
将自动生成的唯一密钥复制到文本文件中,以避免在主机上输入。 将包含密钥的文本文件复制到主机系统中。
注意防火墙和调试器
如果收到来自防火墙的弹出消息并希望使用调试器,请选中所有三个复选框。
<- 在主机系统上
- 在主计算机上,以管理员身份打开命令提示符窗口。 转到 WinDbg.exe 目录。 我们将使用安装 Windows 工具包过程中安装的 Windows 驱动程序工具包 (WDK) 中的 x64 版本 WinDbg.exe。
C:\> Cd C:\Program Files (x86)\Windows Kits\10\Debuggers\x64
- 使用以下命令通过远程用户调试来启动 WinDbg。 密钥和端口的值与之前在目标计算机上使用 BCDEdit 设置的值一致。
C:\> WinDbg –k net:port=50010,key=2steg4fzbj2sz.23418vzkd4ko3.1g34ou07z4pev.1sp3yo9yz874p
-> 在目标系统上
重启目标系统。
<- 在主机系统上
一两分钟后,主机系统上就会显示调试输出。
“调试器命令”窗口是 WinDbg 的主要调试信息窗口。 可以在此窗口中输入调试器命令并查看命令输出。
调试器命令窗口被分为两个窗格。 可以在窗口底部较小的窗格(命令输入窗格)中输入命令,并在窗口顶部较大的窗格中查看命令输出。
在命令输入窗格中,使用上箭头和下箭头键可以滚动浏览命令历史记录。 命令出现后,可对其进行编辑,或者按 ENTER 运行命令。
第 2 部分:内核模式调试命令和技术
在第 2 部分中,将使用调试命令来显示目标系统的相关信息。
<- 在主机系统上
使用 .prefer_dml 启用调试器标记语言 (DML)
某些调试命令使用调试器标记语言显示文本,可以选择这些文本来快速收集更多信息。
- 在 WinDBg 中使用 Ctrl+Break(滚动锁定)来中断目标系统上运行的代码。 目标系统可能需要一些时间才会响应。
- 在调试器命令窗口中键入以下命令以启用 DML。
0: kd> .prefer_dml 1
DML versions of commands on by default
使用 .hh 获得帮助
可使用 .hh 命令来访问参考命令帮助。
- 键入以下命令可查看 .prefer_dml 的命令参考帮助。
0: kd> .hh .prefer_dml
调试器帮助文件将显示 .prefer_dml 命令的帮助。
显示目标系统的 Windows 版本
- 在 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
列出已加载的模块
- 在 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
...
注意此实验室中省略的输出用“...” 表示。
由于尚未设置符号路径和加载符号,所以调试器中提供的信息非常有限。
第 3 部分:下载并生成 Sysvad 音频驱动程序
在第 3 部分中,将下载并生成 Sysvad 音频驱动程序。
通常情况下,在使用 WinDbg 时需要用自己的驱动程序代码。 为了熟悉音频驱动程序的调试,使用了 Sysvad 虚拟音频采样驱动程序。 此示例用于说明如何通过本地内核模式代码进行单步操作。 此技术对于调试复杂的内核模式代码问题非常有用。
要下载并生成 Sysvad 示例音频驱动程序,请执行以下步骤。
从 GitHub 下载并提取 Sysvad 音频示例
可以使用浏览器在这里查看 Sysvad 示例和 Readme.md 文件:
https://github.com/Microsoft/Windows-driver-samples/tree/main/audio/sysvad
此实验室将展示如何以 zip 文件格式下载通用驱动程序示例。
a. 将 master.zip 文件下载到本地硬盘。
https://github.com/Microsoft/Windows-driver-samples/archive/master.zip
b. 选择并按住(或右键单击)Windows-driver-samples-master.zip,然后选择全部提取。 指定一个新文件夹,或浏览到将存储提取文件的现有文件夹。 例如,可以指定 C:\WDK_Samples\ 作为要将文件提取到的新文件夹。
c. 提取文件后,导航到以下子文件夹。
C:\WDK_Samples\Sysvad
在 Visual Studio 中打开驱动程序解决方案
在 Visual Studio 中,选择文件>打开>项目/解决方案...,然后导航到包含提取文件的文件夹(例如,C:\WDK_Samples\Sysvad)。 双击 Syvad 解决方案文件。
在 Visual Studio 中,找到“解决方案资源管理器”。 (如果尚未打开,请从视图菜单中选择解决方案资源管理器。)在解决方案资源器中,可以看到一个包含几个项目的解决方案。
设置示例的配置和平台
在“解决方案资源管理器”中,选择并按住(或右键单击)解决方案“sysvad”(项目 7/7),然后选择配置管理器。 确保四个项目的配置和平台设置都相同。 默认情况下,所有项目的配置都设置为“Win10 调试”,而平台设置为“Win64”。 如果对一个项目进行任何配置和/或平台更改,则必须对其余三个项目进行同样的更改。
注意此实验室假设使用的是 64 位 Windows。 如果使用的是 32 位 Windows,请生成 32 位的驱动程序。
检查驱动程序签名
找到 TabletAudioSample。 打开 Sysvad 驱动程序的属性页面,确保将驱动程序签名>签名模式设置为“测试签名”。
需要修改驱动程序示例,以便使用不与现有驱动程序重叠的值。 请参阅从示例代码到生产驱动程序 - 示例中要更改的内容,了解如何创建与 Windows 中安装的现有真实驱动程序共存的独特驱动程序示例。
使用 Visual Studio 生成示例
在 Visual Studio 中,选择生成>生成解决方案。
生成窗口应显示一条信息,指明所有六个项目均已成功生成。
提示
如果遇到生成错误信息,请使用生成错误编号来确定修复方法。 例如,MSBuild error MSB8040 描述了如何使用 Spectre 已缓解的库。
找到生成的驱动程序文件
在文件资源管理器中,导航至包含示例提取文件的文件夹。 例如,如果先前指定的文件夹是 C:\WDK_Samples\Sysvad,则导航到该文件夹。 在该文件夹中,编译的驱动程序文件的位置会因在“配置管理器”中选择的配置和平台设置而异。 例如,如果不更改默认设置,那么编译后的驱动程序文件将保存到名为 \x64\Debug 的文件夹中,用于 64 位调试版本。
导航至包含 TabletAudioSample 驱动程序内置文件的文件夹:
C:\WDK_Samples\Sysvad\TabletAudioSample\x64\Debug. 该文件夹将包含 TabletAudioSample .SYS 驱动程序、符号 pdp 文件和 inf 文件。 还需要找到 DelayAPO、KWSApo 和 KeywordDetectorContosoAdapter 的 dll 和符号文件。
安装驱动程序需要以下文件。
文件名 说明 TabletAudioSample.sys 驱动程序文件。 TabletAudioSample.pdb 驱动程序符号文件。 tabletaudiosample.inf 一个信息 (INF) 文件,其中包含安装驱动程序所需的信息。 KeywordDetectorContosoAdapter.dll 示例关键字检测器。 KeywordDetectorContosoAdapter.pdb 示例关键字检测器符号文件。 DelayAPO.dll 示例延迟 APO。 DelayAPO.pdb 延迟 APO 符号文件。 KWSApo.dll 示例关键字检测工具 APO。 KWSApo.pdb 关键字检测工具符号文件。 TabletAudioSample.cer TabletAudioSample 证书文件。 找到 U 盘或设置网络共享,将内置驱动程序文件从主机复制到目标系统。
在下一部分中,将代码复制到目标系统,然后安装和测试驱动程序。
第 4 部分:在目标系统上安装 Sysvad 音频驱动程序示例
在第 4 部分中,将使用 devcon 来安装 Sysvad 音频驱动程序。
-> 在目标系统上
安装驱动程序的计算机被称为目标计算机或测试计算机。 通常情况下,这是一台与开发和生成驱动程序软件包的计算机不同的计算机。 开发和生成驱动程序的计算机被称为主机计算机。
将驱动程序软件包移动到目标计算机并安装驱动程序的过程被称为部署驱动程序。
部署驱动程序前,必须打开测试签名,让目标计算机做好准备。 之后,就可以在目标系统上运行已生成的驱动程序示例了。
要在目标系统上安装驱动程序,请执行以下步骤。
启用测试签名的驱动程序
启用运行测试签名驱动程序的功能:
打开“Windows 设置”。
在“更新和安全”中,选择“恢复”。
在“高级启动”下,选择“立即重新启动”。
计算机重启后,选择“故障排除”。
然后选择“高级选项”、“启动设置”,然后选择“重新启动”。
按 F7 键,选择“禁用强制驱动程序签名”。
计算机将以新的值启动。
-> 在目标系统上
安装驱动程序
以下说明将介绍如何安装和测试示例驱动程序。
安装此驱动程序需要的 INF 文件是 TabletAudioSample.inf。 在目标计算机上,以管理员身份打开“命令提示符”窗口。 导航至驱动程序包文件夹,右键单击 TabletAudioSample.inf 文件,然后选择“安装”。
此时将显示一个对话框,指示测试驱动程序是未签名驱动程序。 选择“仍然安装此驱动程序”以继续。
提示
如果在安装过程中遇到任何问题,请查看以下文件了解更多信息。
%windir%\inf\setupapi.dev.log
有关更详细的说明,请参阅为驱动程序部署、测试和调试配置计算机。
INF 文件包含用于安装 tabletaudiosample.sys 的硬件 ID。 对于 Syvad 示例,硬件 ID 为:
root\sysvad_TabletAudioSample
在“设备管理器”中检查驱动程序
在目标计算机的“命令提示符”窗口中,输入 devmgmt 以打开“设备管理器”。 在设备管理器的“查看”菜单中,选择“按类型列出设备”。
在设备树中,找到“音频设备”节点中的虚拟音频设备 (WDM) - 平板电脑示例。 它通常位于“声音、视频和游戏控制器”节点下。 确认它已安装并处于活动状态。
在“设备管理器”中突出显示计算机上实际硬件的驱动程序。 然后选择并按住(或右键单击)该驱动程序,选择“禁用”将该驱动程序禁用。
在“设备管理器”中确认音频硬件驱动程序是否显示了一个表示已禁用的下箭头。
成功安装示例驱动程序后,就可以对其进行测试了。
测试 Sysvad 音频驱动程序
在目标计算机的“命令提示符”窗口中,输入 devmgmt 以打开“设备管理器”。 在“设备管理器”的“查看”菜单中,选择“按类型列出设备”。 在设备树中,找到”虚拟音频设备 (WDM) - 平板电脑示例“。
打开控制面板并导航至硬件和声音>管理音频设备。 在“声音”对话框中,选择标记为“虚拟音频设备 (WDM) - 平板电脑示例”的扬声器图标,然后选择“设置默认值”,但不要选择“确定”。 这将使“声音”对话框保持打开状态。
在目标计算机上找到 MP3 或其他音频文件,然后双击播放它。 然后,在“声音”对话框中,验证音量级别指示器中是否存在与“虚拟音频设备 (WDM) - 平板电脑示例”驱动程序关联的活动。
第 5 部分:使用 WinDbg 显示驱动程序的相关信息
在第 5 部分中,将设置符号路径,并使用内核调试器命令来显示 Sysvad 示例驱动程序的相关信息。
符号允许 WinDbg 显示变量名等其他信息,这些信息在调试时非常有用。 WinDbg 使用 Microsoft Visual Studio 调试符号格式进行源级调试。 它可以访问具有 PDB 符号文件的模块中的任何符号或变量。
要加载调试器,请执行以下步骤。
<- 在主机系统上
如果关闭了调试器,请在管理员命令提示符窗口中使用以下命令再次将其打开。 将密钥和端口替换为之前配置的密钥和端口。
C:\> WinDbg –k net:port=50010,key=2steg4fzbj2sz.23418vzkd4ko3.1g34ou07z4pev.1sp3yo9yz874p
使用 Ctrl+Break(滚动锁定)来中断目标系统上运行的代码。
设置符号路径
要在 WinDbg 环境中设置 Microsoft 符号服务器的符号路径,请使用 .symfix 命令。
0: kd> .symfix
要添加本地符号位置以使用本地符号,请使用 .sympath+ 和 .reload /f 来添加路径。
0: kd> .sympath+ C:\WDK_Samples\Sysvad 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 文件)。 这些符号文件将向调试器显示二进制指令与源代码行的对应关系。 实际源文件本身也必须要能被调试器访问。
符号文件不包含源代码文本。 为了便于调试,链接器最好不要优化代码。 如果代码经过了优化,源代码调试和访问局部变量就会变得更加困难,有时甚至几乎不可能。 如果在查看局部变量或源代码行时遇到问题,请设置以下生成选项。
设置 COMPILE_DEBUG=1
设置 ENABLE_OPTIMIZER=0
在调试器的命令区键入以下内容,以便显示有关 Sysvad 驱动程序的信息。
0: kd> lm m tabletaudiosample v Browse full module list start end module name fffff801`14b40000 fffff801`14b86000 tabletaudiosample (private pdb symbols) C:\Debuggers\sym\TabletAudioSample.pdb\E992C4803EBE48C7B23DC1596495CE181\TabletAudioSample.pdb Loaded symbol image file: tabletaudiosample.sys Image path: \SystemRoot\system32\drivers\tabletaudiosample.sys Image name: tabletaudiosample.sys Browse all global symbols functions data Timestamp: Thu Dec 10 12:20:26 2015 (5669DE8A) CheckSum: 0004891E ...
有关详细信息,请参阅 lm。
在调试输出中选择“浏览所有全局符号”链接,以便显示以字母 a 开头的项目符号信息。
由于启用了 DML,因此输出中的某些元素是可以选择的热链接。 在调试输出中选择数据链接,以便显示以字母 a 开头的项目符号信息。
0: kd> x /D /f tabletaudiosample!a* A B C D E F G H I J K L M N O P Q R S T U V W X Y Z fffff806`9adb1000 tabletaudiosample!AddDevice (struct _DRIVER_OBJECT *, struct _DEVICE_OBJECT *)
有关信息,请参阅 x(检查符号)。
!lmi 扩展显示有关模块的详细信息。 键入 !lmi tabletaudiosample。 输出结果应与下图文本类似。
0: kd> !lmi tabletaudiosample Loaded Module Info: [tabletaudiosample] Module: tabletaudiosample Base Address: fffff8069ad90000 Image Name: tabletaudiosample.sys Machine Type: 34404 (X64) Time Stamp: 58ebe848 Mon Apr 10 13:17:12 2017 Size: 48000 CheckSum: 42df7 Characteristics: 22 Debug Data Dirs: Type Size VA Pointer CODEVIEW a7, e5f4, d1f4 RSDS - GUID: {5395F0C5-AE50-4C56-AD31-DD5473BD318F} Age: 1, Pdb: C:\Windows-driver-samples-master\audio\sysvad\TabletAudioSample\x64\Debug\TabletAudioSample.pdb ?? 250, e69c, d29c [Data not mapped] Image Type: MEMORY - Image read successfully from loaded memory. Symbol Type: PDB - Symbols loaded successfully from image header. C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\sym\TabletAudioSample.pdb\5395F0C5AE504C56AD31DD5473BD318F1\TabletAudioSample.pdb Compiler: Resource - front end [0.0 bld 0] - back end [14.0 bld 24210] Load Report: private symbols & lines, not source indexed C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\sym\TabletAudioSample.pdb\5395F0C5AE504C56AD31DD5473BD318F1\TabletAudioSample.pdb
使用 !dh 扩展来显示标头信息,如下所示。
0: kd> !dh tabletaudiosample File Type: EXECUTABLE IMAGE FILE HEADER VALUES 8664 machine (X64) 9 number of sections 5669DE8A time date stamp Thu Dec 10 12:20:26 2015 0 file pointer to symbol table 0 number of symbols F0 size of optional header 22 characteristics Executable App can handle >2gb addresses ...
第 6 部分:显示即插即用设备树信息
在第 6 部分中,将介绍有关 Sysvad 示例设备驱动程序的信息,以及它在即插即用设备树中的位置。
即插即用设备树中有关设备驱动程序的信息对故障排除很有用。 例如,如果设备驱动程序没有驻留在设备树中,则表明设备驱动程序的安装可能存在问题。
有关设备节点调试扩展的详细信息,请参阅 !devnode。
<- 在主机系统上
要查看即插即用设备树中的所有设备节点,请输入 !devnode 0 1 命令。 运行该命令可能需要一两分钟。 在此期间,WinDbg 的状态区域中将显示“*忙”。
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 在生成的输出中查找设备驱动程序的名称 sysvad。
在 Syvad 的 !devnode 输出中将出现一个名为
sysvad_TabletAudioSample
的设备节点条目。DevNode 0xffffe00086e68190 for PDO 0xffffe00089c575a0 InstancePath is "ROOT\sysvad_TabletAudioSample\0000" ServiceName is "sysvad_tabletaudiosample" State = DeviceNodeStarted (0x308) ...
请注意,显示的是 PDO 地址和 DevNode 地址。
使用
!devnode 0 1 sysvad_TabletAudioSample
命令可显示与 Sysvad 设备驱动程序关联的即插即用信息。0: kd> !devnode 0 1 sysvad_TabletAudioSample Dumping IopRootDeviceNode (= 0xffffe00082df8d30) DevNode 0xffffe00086e68190 for PDO 0xffffe00089c575a0 InstancePath is "ROOT\sysvad_TabletAudioSample\0000" ServiceName is "sysvad_tabletaudiosample" State = DeviceNodeStarted (0x308) Previous State = DeviceNodeEnumerateCompletion (0x30d) DevNode 0xffffe000897fb650 for PDO 0xffffe00089927e30 InstancePath is "SWD\MMDEVAPI\{0.0.0.00000000}.{64097438-cdc0-4007-a19e-62e789062e20}" State = DeviceNodeStarted (0x308) Previous State = DeviceNodeStartPostWork (0x307) DevNode 0xffffe00086d2f5f0 for PDO 0xffffe00089939ae0 InstancePath is "SWD\MMDEVAPI\{0.0.0.00000000}.{78880f4e-9571-44a4-a9df-960bde446487}" State = DeviceNodeStarted (0x308) Previous State = DeviceNodeStartPostWork (0x307) DevNode 0xffffe00089759bb0 for PDO 0xffffe000875aa060 InstancePath is "SWD\MMDEVAPI\{0.0.0.00000000}.{7cad07f2-d0a0-4b9b-8100-8dc735e9c447}" State = DeviceNodeStarted (0x308) Previous State = DeviceNodeStartPostWork (0x307) DevNode 0xffffe00087735010 for PDO 0xffffe000872068c0 InstancePath is "SWD\MMDEVAPI\{0.0.0.00000000}.{fc38551b-e69f-4b86-9661-ae6da78bc3c6}" State = DeviceNodeStarted (0x308) Previous State = DeviceNodeStartPostWork (0x307) DevNode 0xffffe00088457670 for PDO 0xffffe0008562b830 InstancePath is "SWD\MMDEVAPI\{0.0.1.00000000}.{0894b831-c9fe-4c56-86a6-092380fc5628}" State = DeviceNodeStarted (0x308) Previous State = DeviceNodeStartPostWork (0x307) DevNode 0xffffe000893dbb70 for PDO 0xffffe00089d68060 InstancePath is "SWD\MMDEVAPI\{0.0.1.00000000}.{15eb6b5c-aa54-47b8-959a-0cff2c1500db}" State = DeviceNodeStarted (0x308) Previous State = DeviceNodeStartPostWork (0x307) DevNode 0xffffe00088e6f250 for PDO 0xffffe00089f6e990 InstancePath is "SWD\MMDEVAPI\{0.0.1.00000000}.{778c07f0-af9f-43f2-8b8d-490024f87239}" State = DeviceNodeStarted (0x308) Previous State = DeviceNodeStartPostWork (0x307) DevNode 0xffffe000862eb4b0 for PDO 0xffffe000884443a0 InstancePath is "SWD\MMDEVAPI\{0.0.1.00000000}.{e4b72c7c-be50-45df-94f5-0f2922b85983}" State = DeviceNodeStarted (0x308) Previous State = DeviceNodeStartPostWork (0x307)
上一条命令显示的输出包括与运行中的驱动程序实例相关联的 PDO,在本例中为 0xffffe00089c575a0。 输入 !devobj<PDO address> 命令,以便显示与 Sysvad 设备驱动程序关联的即插即用信息。 使用 !devnode 在计算机上显示的 PDO 地址,而不是此处显示的地址。
0: kd> !devobj 0xffffe00089c575a0 Device object (ffffe00089c575a0) is for: 0000004e \Driver\PnpManager DriverObject ffffe00082d47e60 Current Irp 00000000 RefCount 65 Type 0000001d Flags 00001040 SecurityDescriptor ffffc102b0f6d171 DevExt 00000000 DevObjExt ffffe00089c576f0 DevNode ffffe00086e68190 ExtensionFlags (0000000000) Characteristics (0x00000180) FILE_AUTOGENERATED_DEVICE_NAME, FILE_DEVICE_SECURE_OPEN AttachedDevice (Upper) ffffe00088386a50 \Driver\sysvad_tabletaudiosample Device queue is not busy.
!devobj 命令中显示的输出包括所附设备的名称:\Driver\sysvad_tabletaudiosample。 使用位掩码为 2 的 !drvobj 命令,以便显示与所连接设备关联的信息。
0: kd> !drvobj \Driver\sysvad_tabletaudiosample 2 Driver object (ffffe0008834f670) is for: \Driver\sysvad_tabletaudiosample DriverEntry: fffff80114b45310 tabletaudiosample!FxDriverEntry DriverStartIo: 00000000 DriverUnload: fffff80114b5fea0 tabletaudiosample!DriverUnload AddDevice: fffff80114b5f000 tabletaudiosample!AddDevice Dispatch routines: [00] IRP_MJ_CREATE fffff80117b49a20 portcls!DispatchCreate [01] IRP_MJ_CREATE_NAMED_PIPE fffff8015a949a00 nt!IopInvalidDeviceRequest [02] IRP_MJ_CLOSE fffff80115e26f90 ks!DispatchCleanup [03] IRP_MJ_READ fffff80115e32710 ks!DispatchRead [04] IRP_MJ_WRITE fffff80115e327e0 ks!DispatchWrite [05] IRP_MJ_QUERY_INFORMATION fffff8015a949a00 nt!IopInvalidDeviceRequest [06] IRP_MJ_SET_INFORMATION fffff8015a949a00 nt!IopInvalidDeviceRequest [07] IRP_MJ_QUERY_EA fffff8015a949a00 nt!IopInvalidDeviceRequest [08] IRP_MJ_SET_EA fffff8015a949a00 nt!IopInvalidDeviceRequest [09] IRP_MJ_FLUSH_BUFFERS fffff80115e32640 ks!DispatchFlush [0a] IRP_MJ_QUERY_VOLUME_INFORMATION fffff8015a949a00 nt!IopInvalidDeviceRequest [0b] IRP_MJ_SET_VOLUME_INFORMATION fffff8015a949a00 nt!IopInvalidDeviceRequest [0c] IRP_MJ_DIRECTORY_CONTROL fffff8015a949a00 nt!IopInvalidDeviceRequest [0d] IRP_MJ_FILE_SYSTEM_CONTROL fffff8015a949a00 nt!IopInvalidDeviceRequest [0e] IRP_MJ_DEVICE_CONTROL fffff80115e27480 ks!DispatchDeviceIoControl [0f] IRP_MJ_INTERNAL_DEVICE_CONTROL fffff8015a949a00 nt!IopInvalidDeviceRequest [10] IRP_MJ_SHUTDOWN fffff8015a949a00 nt!IopInvalidDeviceRequest [11] IRP_MJ_LOCK_CONTROL fffff8015a949a00 nt!IopInvalidDeviceRequest [12] IRP_MJ_CLEANUP fffff8015a949a00 nt!IopInvalidDeviceRequest [13] IRP_MJ_CREATE_MAILSLOT fffff8015a949a00 nt!IopInvalidDeviceRequest [14] IRP_MJ_QUERY_SECURITY fffff80115e326a0 ks!DispatchQuerySecurity [15] IRP_MJ_SET_SECURITY fffff80115e32770 ks!DispatchSetSecurity [16] IRP_MJ_POWER fffff80117b3dce0 portcls!DispatchPower [17] IRP_MJ_SYSTEM_CONTROL fffff80117b13d30 portcls!PcWmiSystemControl [18] IRP_MJ_DEVICE_CHANGE fffff8015a949a00 nt!IopInvalidDeviceRequest [19] IRP_MJ_QUERY_QUOTA fffff8015a949a00 nt!IopInvalidDeviceRequest [1a] IRP_MJ_SET_QUOTA fffff8015a949a00 nt!IopInvalidDeviceRequest [1b] IRP_MJ_PNP fffff80114b5f7d0 tabletaudiosample!PnpHandler
输入 !devstack<PDO address> 命令,以便显示与设备驱动程序关联的即插即用信息。 !devnode 0 1 命令中显示的输出包括与正在运行的驱动程序实例相关联的 PDO 地址。 本例中,该地址为 0xffffe00089c575a0。 使用 !devnode 在计算机上显示的 PDO 地址,而不是下图所示的地址。
0: kd> !devstack 0xffffe00089c575a0 !DevObj !DrvObj !DevExt ObjectName ffffe00088d212e0 \Driver\ksthunk ffffe00088d21430 0000007b ffffe00088386a50 \Driver\sysvad_tabletaudiosampleffffe00088386ba0 0000007a > ffffe00089c575a0 \Driver\PnpManager 00000000 0000004e !DevNode ffffe00086e68190 : DeviceInst is "ROOT\sysvad_TabletAudioSample\0000" ServiceName is "sysvad_tabletaudiosample"
输出显示有一个非常简单的设备驱动程序堆栈。 sysvad_TabletAudioSample 驱动程序是 PnPManager 节点的子节点。 PnPManager 是一个根节点。
此图显示了一个更为复杂的设备节点树。
注意 有关更复杂的驱动程序堆栈的详细信息,请参阅驱动程序堆栈和设备节点和设备堆栈。
第 7 部分:使用断点
在第 7 部分中,将使用断点在特定点停止代码执行。
使用命令来设置断点
断点用于在特定代码行停止代码的执行。 然后,就可以从这一点开始向前执行代码,对特定的代码部分进行调试。
要使用调试命令来设置断点,请使用以下 b 命令之一。
bp |
设置一个断点,该断点将一直处于活动状态,直到其所在模块被卸载。 |
bu |
设置一个断点,该断点在卸载模块时未解析,并在重新加载模块时重新启用。 |
bm |
为符号设置一个断点。 此命令将适当使用 bu 或 bp,并允许使用通配符 * 在每个匹配的符号(如类中的所有方法)上设置断点。 |
使用 WinDbg UI 确认调试>源模式是否已在当前 WinDbg 会话中启用。
输入以下命令,将本地代码位置添加到源路径中。
.sympath+ C:\WDK_Samples\Sysvad
键入以下命令,将本地符号位置添加到符号路径中。
.sympath+ C:\WDK_Samples\Sysvad
设置调试掩码
在使用驱动程序时,查看它可能显示的所有信息会非常方便。 键入以下内容以更改默认调试位掩码,从而在调试器中显示来自目标系统的所有调试信息。
0: kd> ed nt!Kd_DEFAULT_MASK 0xFFFFFFFF
使用驱动程序名称后跟要在其中设置断点的函数名称 (AddDevice)(以感叹号分隔),通过 bm 命令来设置断点。
0: kd> bm tabletaudiosample!AddDevice breakpoint 1 redefined 1: fffff801`14b5f000 @!"tabletaudiosample!AddDevice"
可以将不同的语法结合设置变量来使用,如 <module>!<symbol>、<class>::<method>、“<file.cpp>:<line number>”,或者跳过若干次 <condition><#>。 有关详细信息,请参阅 Using Breakpoints。
列出当前断点,以确认通过键入 bl 命令设置了断点。
0: kd> bl 1 e fffff801`14b5f000 0001 (0001) tabletaudiosample!AddDevice
输入 go 命令 g 在目标系统上重新启动代码执行。
-> 在目标系统上
在 Windows 中,使用图标或输入 mmc devmgmt.msc 来打开“设备管理器”。 在“设备管理器”中展开“声音、视频和游戏控制器”节点。 选择并按住(或右键单击)虚拟音频驱动程序条目,然后从菜单中选择“禁用”。
再次选择并按住(或右键单击)虚拟音频驱动程序条目,然后从菜单中选择“启用”。
<- 在主机系统上
这会导致 Windows 重新加载调用 AddDevice 的驱动程序。 这样将导致 AddDevice 调试断点触发,而目标系统上的驱动程序代码应停止执行。
Breakpoint 1 hit tabletaudiosample!AddDevice: fffff801`14baf000 4889542410 mov qword ptr [rsp+10h],rdx
如果源路径设置正确,则应停止 adapter.cpp 中的 AddDevice 例程位置
{ PAGED_CODE(); NTSTATUS ntStatus; ULONG maxObjects; DPF(D_TERSE, ("[AddDevice]")); maxObjects = g_MaxMiniports; #ifdef SYSVAD_BTH_BYPASS // // Allow three (3) Bluetooth hands-free profile devices. // maxObjects += g_MaxBthHfpMiniports * 3; #endif // SYSVAD_BTH_BYPASS // Tell the class driver to add the device. // ntStatus = PcAddAdapterDevice ( DriverObject, PhysicalDeviceObject, PCPFNSTARTDEVICE(StartDevice), maxObjects, 0 ); return ntStatus; } // AddDevice
键入 p 命令或按 F10 键,逐行查看代码。 可以从 sysvad AddDevice 代码跳转到 PpvUtilCall、PnpCallAddDevice,然后跳转到 PipCallDriverAddDevice Windows 代码。 可以为 p 命令指定一个数字,以便向前执行多行,例如 p 5。
完成代码单步执行后,使用 go 命令 g 在目标系统上重新启动执行。
设置内存访问断点
还可以设置在访问内存位置时触发的断点。 使用 ba(访问时中断)命令,语法如下。
ba <access> <size> <address> {options}
选项 | 说明 |
---|---|
e |
execute(当 CPU 从地址中提取指令时) |
r |
read/write(当 CPU 读取或写入地址时) |
w |
write(当 CPU 写入地址时) |
请注意,任何时候都只能设置四个数据断点,并且必须确保正确对齐数据,否则将无法触发断点(word 必须以可被 2 整除的地址结尾,dwords 必须可被 4 整除,quadword 必须可被 0 或 8 整除)
例如,要在特定内存地址上设置读/写断点,可使用如下命令。
ba r 4 fffff800`7bc9eff0
修改断点状态
可以使用以下命令修改现有的断点。
bl |
列出断点。 |
bc |
清除列表中的断点。 使用 bc * 可以清除所有断点。 |
bd |
禁用断点。 使用 bd * 禁用所有断点。 |
be |
启用断点。 使用 be * 启用所有断点。 |
或者,也可以通过选择编辑>断点来修改断点。 请注意,断点对话框只对现有断点有效。 必须通过命令行来设置新的断点。
在 MixerVolume 上设置断点
设备驱动程序加载后,音频驱动程序代码的不同部分会被调用以响应各种事件。 在下一部分中将设置一个断点,当用户调整虚拟音频驱动程序的音量控制时,就会触发该断点。
要在 MixerVolume 上设置断点,请执行以下步骤。
<- 在主机系统上
要找到更改卷的方法,请使用 x 命令列出 CAdapterCommon 中包含卷字符串的符号。
kd> x tabletaudiosample!CAdapterCommon::* ... fffff800`7bce26a0 tabletaudiosample!CAdapterCommon::MixerVolumeWrite (unsigned long, unsigned long, long) …
使用 CTRL+F 在输出中向上搜索卷,找到 MixerVolumeWrite 方法。
使用 bc * 清除之前的断点。
使用以下命令在 CAdapterCommon::MixerVolumeWrite 例程上设置符号断点。
kd> bm tabletaudiosample!CAdapterCommon::MixerVolumeWrite 1: fffff801`177b26a0 @!"tabletaudiosample!CAdapterCommon::MixerVolumeWrite"
列出断点,以便确认断点已正确设置。
kd> bl 1 e fffff801`177b26a0 [c:\WDK_Samples\audio\sysvad\common.cpp @ 1668] 0001 (0001) tabletaudiosample!CAdapterCommon::MixerVolumeWrite
输入 go 命令 g 在目标系统上重新启动代码执行。
在控制面板中选择硬件和声音>声音。 选择并按住(或右键单击)“接收器说明示例”,然后选择“属性”。 选择“音量”选项卡。调整滑块音量。
这将导致 SetMixerVolume 调试断点触发,并且目标系统上的驱动程序代码停止执行。
kd> g Breakpoint 1 hit tabletaudiosample!CAdapterCommon::MixerVolumeWrite: fffff801`177b26a0 44894c2420 mov dword ptr [rsp+20h],r9d
应该在 common.cpp 中的这一行停止运行
{ if (m_pHW) { m_pHW->SetMixerVolume(Index, Channel, Value); } } // MixerVolumeWrite
使用 dv 命令显示当前变量及其值。 有关变量的详细信息将在本实验室的下一部分中提供。
2: kd> dv this = 0x00000000`00000010 ulNode = 0x344 ulChannel = 0x210a45f8 lVolume = 0n24
按 F10 单步执行代码。
按 F5 完成 MixerVolumeWrite 代码的执行。
摘要 - 从“调试器命令”窗口单步执行代码
以下是可以用来单步执行代码的命令(括号中显示的是相关的键盘快捷键)。
中断 (Ctrl+Break) - 只要系统正在运行并与 WinDbg 通信(内核调试器中的顺序为 Ctrl+C),该命令就会中断系统。
单步跳过 (F10) - 该命令可让代码一次只执行一条语句或指令。 如果遇到调用,则代码执行将越过调用,而不会进入被调用例程。 (如果编程语言为 C 或 C++,且 WinDbg 处于源模式,则可以使用调试>源模式来打开或关闭源模式)。
单步执行 (F11) - 该命令与单步跳过类似,只是调用的执行会进入被调用例程。
单步跳出 (Shift+F11) – 该命令可让代码执行到当前例程(调用栈中的当前位置)并退出。 如果已查看足够多的例程,这将非常有用。
运行到光标(F7 或 Ctrl+F10)- 将光标放在源代码或反汇编窗口中需要中断执行的位置,然后按 F7;代码执行将运行到该点。 请注意,如果代码执行流没有到达游标指示的点(比如没有执行 IF 语句),则 WinDbg 不会中断,因为代码执行并未到达指示的点。
运行 (F5) – 运行,直到遇到断点或发生错误检查等事件。
高级选项
将指令设置到当前行 (Ctrl+Shift+I) - 在源窗口中,可以将光标放在某一行上,然后输入此快捷键,只要让它继续运行(例如使用 F5 或 F10),代码就会从该点开始执行。 如果要重试序列,这将非常有用,但需要注意一些事项。 例如,寄存器和变量不会被设置为代码执行到该行时的状态。
直接设置 eip 寄存器 - 可以在 eip 寄存器中输入一个值,一旦按下 F5(或 F10、F11 等),就会从该地址开始执行。 这与将指令设置到光标指定的当前行类似,只不过指定的是程序集指令的地址。
通过用户界面进行操作比从命令行进行操作更容易,因此推荐使用这种方法。 如有必要,可使用以下命令在命令行中单步执行源文件:
.lines - 启用源行信息。
bp main - 在模块开始处设置初始断点。
l+t - 将按源行进行单步执行。
选择调试>源模式以进入源模式;只使用
L+t
命令是不够的。l+s - 在提示符下显示源行。
g - 运行程序,直到输入了“main”。
p - 执行一个源行。
有关详细信息,请参阅调试参考文档中的 WinDbg 中的源代码调试(经典版)。
在代码中设置断点
可以通过添加 DebugBreak()
语句、重新生成项目和重新安装驱动程序来在代码中设置断点。 每次启用驱动程序时,该断点都会被触发,因此这将是一种在早期开发阶段而非生产代码中使用的技术。 这种技术不像使用断点命令动态设置断点那样灵活。
提示:可能需要保留一份未添加断点的 Sysvad 驱动程序副本,以用于进一步的实验室工作。
在示例代码中添加
DebugBreak()
语句,设置每次运行 AddDevice 方法时中断。... // Insert the DebugBreak() statment before the PcAddAdapterDevice is called. // DebugBreak() // Tell the class driver to add the device. // ntStatus = PcAddAdapterDevice ( DriverObject, PhysicalDeviceObject, PCPFNSTARTDEVICE(StartDevice), maxObjects, 0 ); return ntStatus; } // AddDevice
按照之前介绍的所有步骤,在 Microsoft Visual Studio 中重新生成驱动程序,并将其重新安装到目标计算机。 在安装更新的驱动程序之前,请务必卸载现有的驱动程序。
清除之前的任何断点,并确保调试器已连接到目标计算机。
当代码运行到
DebugBreak
语句时,将停止执行并显示一条信息。KERNELBASE!DebugBreak: 77b3b770 defe __debugbreak
第 8 部分:显示变量
在第 8 部分中,将使用调试器命令来显示变量。
在代码执行过程中检查变量以确认代码是否按预期运行,可能会很有用。 此实验室会检查音频驱动程序产生声音时的变量。
使用 dv 命令检查与 tabletaudiosample!CMiniportWaveRT::New* 关联的局部变量。
kd> dv tabletaudiosample!CMiniportWaveRT::New*
清除之前的断点
bc *
使用以下命令在 CMiniportWaveCyclicStreamMSVAD 例程上设置符号断点。
0: kd> bm tabletaudiosample!CMiniportWaveRT::NewStream 1: fffff801`177dffc0 @!"tabletaudiosample!CMiniportWaveRT::NewStream"
输入 go 命令 g 在目标系统上重新启动代码执行。
-> 在目标系统上
找到一个小型媒体文件(如扩展名为 .wav 文件的 Windows 通知声音文件),然后选择该文件进行播放。 例如,可以使用 Windows\Media 目录中的 Ring05.wav。
<- 在主机系统上
播放媒体文件时,断点应被触发,目标系统上的驱动程序代码应停止执行。
Breakpoint 1 hit tabletaudiosample!CMiniportWaveRT::NewStream: fffff801`177dffc0 44894c2420 mov dword ptr [rsp+20h],r9d
源代码窗口应突出显示 NewStream 函数入口处的括号。
/*++ Routine Description: The NewStream function creates a new instance of a logical stream associated with a specified physical channel. Callers of NewStream should run at IRQL PASSIVE_LEVEL. Arguments: OutStream - OuterUnknown - Pin - Capture - DataFormat - Return Value: NT status code. --*/ { ...
局部变量
输入 dv 命令可以显示指定帧的所有局部变量的名称和值。
0: kd> dv this = 0xffffe000`4436f8e0 OutStream = 0xffffe000`49d2f130 OuterUnknown = 0xffffe000`4436fa30 Pin = 0 Capture = 0x01 ' DataFormat = 0xffffe000`44227790 signalProcessingMode = {487E9220-E000-FFFF-30F1-D24900E0FFFF} ntStatus = 0n1055 stream = 0x00000000`00000200
使用 DML 显示变量
要使用 DML 来浏览变量,请选择带下划线的元素。 选择操作会生成一个 dx(显示 NatVis 表达式)命令,用于向下钻取嵌套数据结构。
0: kd> dx -r1 (*((tabletaudiosample!CMiniportWaveRT *)0xffffe001d10b8380)) (*((tabletaudiosample!CMiniportWaveRT *)0xffffe001d10b8380)) : [Type: CMiniportWaveRT] [+0x020] m_lRefCount : 0 [+0x028] m_pUnknownOuter : 0xffffe001d1477e50 : [Type: IUnknown *] [+0x030] m_ulLoopbackAllocated : 0x2050 [+0x034] m_ulSystemAllocated : 0x180 [+0x038] m_ulOffloadAllocated : 0x0 [+0x03c] m_dwCaptureAllocatedModes : 0x0 0: kd> dx -r1 (*((tabletaudiosample!_GUID *)0xffffd001c8acd348)) (*((tabletaudiosample!_GUID *)0xffffd001c8acd348)) : {487E9220-E000-FFFF-30F1-D24900E0FFFF} [Type: _GUID] [<Raw View>] 0: kd> dx -r1 -n (*((tabletaudiosample!_GUID *)0xffffd001c8acd348)) (*((tabletaudiosample!_GUID *)0xffffd001c8acd348)) : [Type: _GUID] [+0x000] Data1 : 0x487e9220 [+0x004] Data2 : 0xe000 [+0x006] Data3 : 0xffff [+0x008] Data4 : [Type: unsigned char [8]] 0: kd> dx -r1 -n (*((tabletaudiosample!unsigned char (*)[8])0xffffd001c8acd350)) (*((tabletaudiosample!unsigned char (*)[8])0xffffd001c8acd350)) : [Type: unsigned char [8]] [0] : 0x30 [1] : 0xf1 [2] : 0xd2 [3] : 0x49 [4] : 0x0 [5] : 0xe0 [6] : 0xff [7] : 0xff
全局变量
可以通过键入 ? <variable name> 来查找全局变量的内存位置。
0: kd> ? signalProcessingMode Evaluate expression: -52768896396472 = ffffd001`c8acd348
这样将返回变量的内存位置,本例中为 ffffd001`c8acd348。 可以使用前一条命令返回的内存位置,通过键入 dd 命令来转储该位置的值,从而查看内存位置的内容。
0: kd> dd ffffd001`c8acd348 ffffd001`c8acd348 487e9220 ffffe000 49d2f130 ffffe000 ffffd001`c8acd358 4837c468 ffffe000 18221570 ffffc000 ffffd001`c8acd368 4436f8e0 ffffe000 487e9220 ffffe000 ffffd001`c8acd378 18ab145b fffff801 4837c420 ffffe000 ffffd001`c8acd388 4436f8e0 ffffe000 49d2f130 ffffe000 ffffd001`c8acd398 4436fa30 ffffe000 00000000 00000000 ffffd001`c8acd3a8 00000001 00000000 44227790 ffffe000 ffffd001`c8acd3b8 18adc7f9 fffff801 495972a0 ffffe000
还可以将变量名称与 dd 命令配合使用。
0: kd> dd signalProcessingMode ffffd001`c8acd348 487e9220 ffffe000 49d2f130 ffffe000 ffffd001`c8acd358 4837c468 ffffe000 18221570 ffffc000 ffffd001`c8acd368 4436f8e0 ffffe000 487e9220 ffffe000 ffffd001`c8acd378 18ab145b fffff801 4837c420 ffffe000 ffffd001`c8acd388 4436f8e0 ffffe000 49d2f130 ffffe000 ffffd001`c8acd398 4436fa30 ffffe000 00000000 00000000 ffffd001`c8acd3a8 00000001 00000000 44227790 ffffe000 ffffd001`c8acd3b8 18adc7f9 fffff801 495972a0 ffffe000
显示变量
使用查看>局部菜单项可显示局部变量。 该界面还提供深入了解更复杂的数据结构的功能。
使用 p 或 F10 在代码中向前移动约 10 行,直到高亮显示 ntStatus = IsFormatSupported(Pin, Capture, DataFormat); 代码行。
PAGED_CODE(); ASSERT(OutStream); ASSERT(DataFormat); DPF_ENTER(("[CMiniportWaveRT::NewStream]")); NTSTATUS ntStatus = STATUS_SUCCESS; PCMiniportWaveRTStream stream = NULL; GUID signalProcessingMode = AUDIO_SIGNALPROCESSINGMODE_DEFAULT; *OutStream = NULL; // // If the data format attributes were specified, extract them. // if ( DataFormat->Flags & KSDATAFORMAT_ATTRIBUTES ) { // The attributes are aligned (QWORD alignment) after the data format PKSMULTIPLE_ITEM attributes = (PKSMULTIPLE_ITEM) (((PBYTE)DataFormat) + ((DataFormat->FormatSize + FILE_QUAD_ALIGNMENT) & ~FILE_QUAD_ALIGNMENT)); ntStatus = GetAttributesFromAttributeList(attributes, attributes->Size, &signalProcessingMode); } // Check if we have enough streams. // if (NT_SUCCESS(ntStatus)) { ntStatus = ValidateStreamCreate(Pin, Capture, signalProcessingMode); } // Determine if the format is valid. // if (NT_SUCCESS(ntStatus)) { ntStatus = IsFormatSupported(Pin, Capture, DataFormat); } ...
使用 dv 命令显示指定帧的所有局部变量的名称和值。 请注意,正如所预料的那样,这些值与上次运行该命令时的值有所不同,因为运行了额外的代码来更改局部变量,而且有些变量现在不在当前帧中,或者其值已发生变化。
2: kd> dv this = 0xffffe001`d1182000 OutStream = 0xffffe001`d4776d20 OuterUnknown = 0xffffe001`d4776bc8 Pin = 0 Capture = 0x00 ' DataFormat = 0xffffe001`cd7609b0 signalProcessingMode = {4780004E-7133-41D8-8C74-660DADD2C0EE} ntStatus = 0n0 stream = 0x00000000`00000000
第 9 部分:查看调用堆栈
在第 9 部分中,将查看调用堆栈,以检查调用者/并列代码。
调用堆栈是指向程序计数器当前位置的函数调用链。 调用堆栈上最上面的函数是当前函数,而下一个函数是调用当前函数的函数,依此类推。
要显示调用堆栈,请使用 k* 命令:
kb |
显示堆栈和前三个参数。 |
kp |
显示堆栈和完整的参数列表。 |
kn |
允许查看堆栈和旁边的帧信息。 |
如果想让调用堆栈可供使用,可以选择查看>调用堆栈进行查看。 选择窗口顶部的列可切换显示其他信息。
该输出显示了在中断状态下调试示例适配器代码时的调用堆栈。
0: kd> kb
# RetAddr : Args to Child : Call Site
00 fffff800`7a0fa607 : ffffe001`d1182000 ffffe001`d4776d20 ffffe001`d4776bc8 ffffe001`00000000 : tabletaudiosample!CMiniportWaveRT::NewStream+0x1dc [c:\data1\threshold\audio\endpointscommon\minwavert.cpp @ 597]
01 fffff800`7a0fb2c3 : 00000000`00000000 ffffe001`d122bb10 ffffe001`ceb81750 ffffe001`d173f058 : portcls!CPortPinWaveRT::Init+0x2e7
02 fffff800`7a0fc7f9 : ffffe001`d4776bc0 00000000`00000000 ffffe001`d10b8380 ffffe001`d122bb10 : portcls!CPortFilterWaveRT::NewIrpTarget+0x193
03 fffff800`7a180552 : 00000000`00000000 ffffe001`d10b8380 ffffe001`d122bb10 ffffe001`d4565600 : portcls!xDispatchCreate+0xd9
04 fffff800`7a109a9a : ffffe001`d10b84d0 ffffe001`d10b8380 00000000`00000000 ffffe001`00000000 : ks!KsDispatchIrp+0x272
05 fffff800`7bd314b1 : ffffe001`d122bb10 ffffd001`c3098590 ffffe001`d122bd90 ffffe001`ce80da70 : portcls!DispatchCreate+0x7a
06 fffff803`cda1bfa8 : 00000000`00000024 00000000`00000000 00000000`00000000 ffffe001`d122bb10 : ksthunk!CKernelFilterDevice::DispatchIrp+0xf9
07 fffff803`cda7b306 : 00000000`000001f0 ffffe001`d48ce690 ffffe001`d13d6400 ffffe001`d13d64c0 : nt!IopParseDevice+0x7c8
08 fffff803`cda12916 : 00000000`000001f0 ffffd001`c30988d0 ffffe001`d13d6490 fffff803`cda7b250 : nt!IopParseFile+0xb6
09 fffff803`cda1131c : ffffe001`d2ccb001 ffffd001`c30989e0 00ffffe0`00000040 ffffe001`cd127dc0 : nt!ObpLookupObjectName+0x776
0a fffff803`cd9fedb8 : ffffe001`00000001 ffffe001`d48ce690 00000000`00000000 00000000`00000000 : nt!ObOpenObjectByNameEx+0x1ec
0b fffff803`cd9fe919 : 000000ee`6d1fc8d8 000000ee`6d1fc788 000000ee`6d1fc7e0 000000ee`6d1fc7d0 : nt!IopCreateFile+0x3d8
0c fffff803`cd752fa3 : ffffc000`1f296870 fffff803`cd9d9fbd ffffd001`c3098be8 00000000`00000000 : nt!NtCreateFile+0x79
0d 00007fff`69805b74 : 00007fff`487484e6 0000029b`00000003 00000000`0000012e 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x13
0e 00007fff`487484e6 : 0000029b`00000003 00000000`0000012e 00000000`00000000 00000000`00000000 : 0x00007fff`69805b74
0f 0000029b`00000003 : 00000000`0000012e 00000000`00000000 00000000`00000000 00000000`00000000 : 0x00007fff`487484e6
10 00000000`0000012e : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000080 : 0x0000029b`00000003
11 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000080 00000000`00000000 : 0x12e
可以使用 DML 进一步浏览代码。 选择第一个 00 条目时,使用 .frame(设置本地上下文)命令来设置上下文,然后使用 dv(显示局部变量)命令来显示局部变量。
0: kd> .frame 0n0;dv /t /v
00 ffffd001`c30981d0 fffff800`7a0fa607 tabletaudiosample!CMiniportWaveRT::NewStream+0x1dc [c:\data1\threshold\audio\endpointscommon\minwavert.cpp @ 597]
ffffd001`c30982b0 class CMiniportWaveRT * this = 0xffffe001`d1182000
ffffd001`c30982b8 struct IMiniportWaveRTStream ** OutStream = 0xffffe001`d4776d20
ffffd001`c30982c0 struct IPortWaveRTStream * OuterUnknown = 0xffffe001`d4776bc8
ffffd001`c30982c8 unsigned long Pin = 0
ffffd001`c30982d0 unsigned char Capture = 0x00 '
ffffd001`c30982d8 union KSDATAFORMAT * DataFormat = 0xffffe001`cd7609b0
ffffd001`c3098270 struct _GUID signalProcessingMode = {4780004E-7133-41D8-8C74-660DADD2C0EE}
ffffd001`c3098210 long ntStatus = 0n0
ffffd001`c3098218 class CMiniportWaveRTStream * stream = 0x00000000`00000000
第 10 部分:显示进程和线程
在第 10 部分中,将使用调试器命令来显示进程和线程。
处理
要更改当前进程上下文,请使用 .process <process> 命令。 以下示例演示了如何识别一个进程并将上下文切换到该进程。
使用
!process
命令显示当前播放声音的进程。有关详细信息,请参阅 !process
输出显示该进程与 audiodg.exe 关联。 如果仍处于本主题上一节所述的断点处,则当前进程应与 audiodg.exe 映像相关联。
<- 在主机系统上
0: kd> !process
PROCESS ffffe001d147c840
SessionId: 0 Cid: 10f0 Peb: ee6cf8a000 ParentCid: 0434
DirBase: d2122000 ObjectTable: ffffc0001f191ac0 HandleCount: <Data Not Accessible>
Image: audiodg.exe
VadRoot ffffe001d4222f70 Vads 70 Clone 0 Private 504. Modified 16. Locked 0.
DeviceMap ffffc00019113080
Token ffffc0001f1d4060
ElapsedTime <Invalid>
UserTime 00:00:00.000
KernelTime 00:00:00.000
QuotaPoolUsage[PagedPool] 81632
QuotaPoolUsage[NonPagedPool] 9704
Working Set Sizes (now,min,max) (2154, 1814, 2109) (8616KB, 7256KB, 8436KB)
PeakWorkingSetSize 2101
VirtualSize 2097192 Mb
PeakVirtualSize 2097192 Mb
PageFaultCount 2336
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 1573
THREAD ffffe001d173e840 Cid 10f0.1dac Teb: 000000ee6cf8b000 Win32Thread: ffffe001d1118cf0 WAIT: (UserRequest) UserMode Non-Alertable
ffffe001d16c4dd0 NotificationEvent
ffffe001d08b0840 ProcessObject
THREAD ffffe001ceb77080 Cid 10f0.16dc Teb: 000000ee6cf8d000 Win32Thread: 0000000000000000 WAIT: (WrQueue) UserMode Alertable
ffffe001cf2d1840 QueueObject
THREAD ffffe001d112c840 Cid 10f0.0a4c Teb: 000000ee6cf8f000 Win32Thread: 0000000000000000 WAIT: (WrQueue) UserMode Alertable
ffffe001cf2d1840 QueueObject
THREAD ffffe001d16c7840 Cid 10f0.13c4 Teb: 000000ee6cf91000 Win32Thread: 0000000000000000 WAIT: (WrQueue) UserMode Alertable
ffffe001cf2d1840 QueueObject
THREAD ffffe001cec67840 Cid 10f0.0dbc Teb: 000000ee6cf93000 Win32Thread: 0000000000000000 WAIT: (WrQueue) UserMode Alertable
ffffe001d173e5c0 QueueObject
THREAD ffffe001d1117840 Cid 10f0.1d6c Teb: 000000ee6cf95000 Win32Thread: 0000000000000000 WAIT: (WrQueue) UserMode Alertable
ffffe001d173e5c0 QueueObject
THREAD ffffe001cdeae840 Cid 10f0.0298 Teb: 000000ee6cf97000 Win32Thread: 0000000000000000 RUNNING on processor 2
请注意,与该进程关联的一个线程处于 RUNNING 状态。 此线程支持在触发断点时播放媒体剪辑。
使用 !process 0 0 命令显示所有进程的摘要信息。 在命令输出中使用 CTRL+F 查找与 audiodg.exe 映像关联的进程 ID。 在下面的示例中,进程 ID 为 ffffe001d147c840。
记录下电脑上与 audiodg.exe 关联的进程 ID,以便以后在此实验室中使用。 ________________________
...
PROCESS ffffe001d147c840
SessionId: 0 Cid: 10f0 Peb: ee6cf8a000 ParentCid: 0434
DirBase: d2122000 ObjectTable: ffffc0001f191ac0 HandleCount: <Data Not Accessible>
Image: audiodg.exe
...
在调试器中输入 g 以便向前运行代码,直到媒体剪辑播放完毕。 然后按 Ctrl+ScrLk (Ctrl+Break) 键进入调试器。使用 !process 命令确认现在运行的是另一个进程。
!process
PROCESS ffffe001cd0ad040
SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 001aa000 ObjectTable: ffffc00017214000 HandleCount: <Data Not Accessible>
Image: System
VadRoot ffffe001d402b820 Vads 438 Clone 0 Private 13417. Modified 87866. Locked 64.
DeviceMap ffffc0001721a070
Token ffffc00017216a60
ElapsedTime 05:04:54.716
UserTime 00:00:00.000
KernelTime 00:00:20.531
QuotaPoolUsage[PagedPool] 0
QuotaPoolUsage[NonPagedPool] 0
Working Set Sizes (now,min,max) (1720, 50, 450) (6880KB, 200KB, 1800KB)
PeakWorkingSetSize 15853
VirtualSize 58 Mb
PeakVirtualSize 74 Mb
PageFaultCount 46128
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 66
THREAD ffffe001cd0295c0 Cid 0004.000c Teb: 0000000000000000 Win32Thread: 0000000000000000 WAIT: (Executive) KernelMode Non-Alertable
fffff803cd8e0120 SynchronizationEvent
THREAD ffffe001cd02a6c0 Cid 0004.0010 Teb: 0000000000000000 Win32Thread: 0000000000000000 WAIT: (Executive) KernelMode Non-Alertable
fffff803cd8e0ba0 Semaphore Limit 0x7fffffff
...
上面的输出显示,ffffe001cd0ad040 的不同系统进程正在运行。 图像名称显示的是系统,而不是 audiodg.exe。
现在使用 !process 命令切换到与 audiodg.exe 关联的进程。 在示例中,进程 ID 为 ffffe001d147c840。 将示例中的进程 ID 替换为之前记录下的进程 ID。
0: kd> !process ffffe001d147c840
PROCESS ffffe001d147c840
SessionId: 0 Cid: 10f0 Peb: ee6cf8a000 ParentCid: 0434
DirBase: d2122000 ObjectTable: ffffc0001f191ac0 HandleCount: <Data Not Accessible>
Image: audiodg.exe
VadRoot ffffe001d4222f70 Vads 60 Clone 0 Private 299. Modified 152. Locked 0.
DeviceMap ffffc00019113080
Token ffffc0001f1d4060
ElapsedTime 1 Day 01:53:14.490
UserTime 00:00:00.031
KernelTime 00:00:00.031
QuotaPoolUsage[PagedPool] 81552
QuotaPoolUsage[NonPagedPool] 8344
Working Set Sizes (now,min,max) (1915, 1814, 2109) (7660KB, 7256KB, 8436KB)
PeakWorkingSetSize 2116
VirtualSize 2097189 Mb
PeakVirtualSize 2097192 Mb
PageFaultCount 2464
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 1418
THREAD ffffe001d173e840 Cid 10f0.1dac Teb: 000000ee6cf8b000 Win32Thread: ffffe001d1118cf0 WAIT: (UserRequest) UserMode Non-Alertable
ffffe001d16c4dd0 NotificationEvent
ffffe001d08b0840 ProcessObject
Not impersonating
DeviceMap ffffc00019113080
Owning Process ffffe001d147c840 Image: audiodg.exe
Attached Process N/A Image: N/A
Wait Start TickCount 338852 Ticks: 197682 (0:00:51:28.781)
Context Switch Count 36 IdealProcessor: 0
UserTime 00:00:00.015
KernelTime 00:00:00.000
Win32 Start Address 0x00007ff7fb928de0
Stack Init ffffd001c2ec6dd0 Current ffffd001c2ec60c0
Base ffffd001c2ec7000 Limit ffffd001c2ec1000 Call 0
Priority 8 BasePriority 8 UnusualBoost 0 ForegroundBoost 0 IoPriority 2 PagePriority 5
Kernel stack not resident.
THREAD ffffe001d115c080 Cid 10f0.15b4 Teb: 000000ee6cf9b000 Win32Thread: 0000000000000000 WAIT: (WrQueue) UserMode Alertable
ffffe001d0bf0640 QueueObject
Not impersonating
DeviceMap ffffc00019113080
Owning Process ffffe001d147c840 Image: audiodg.exe
Attached Process N/A Image: N/A
Wait Start TickCount 338852 Ticks: 197682 (0:00:51:28.781)
Context Switch Count 1 IdealProcessor: 0
UserTime 00:00:00.000
KernelTime 00:00:00.000
Win32 Start Address 0x00007fff6978b350
Stack Init ffffd001c3143dd0 Current ffffd001c3143520
Base ffffd001c3144000 Limit ffffd001c313e000 Call 0
Priority 8 BasePriority 8 UnusualBoost 0 ForegroundBoost 0 IoPriority 2 PagePriority 5
Kernel stack not resident.
THREAD ffffe001d3a27040 Cid 10f0.17f4 Teb: 000000ee6cf9d000 Win32Thread: 0000000000000000 WAIT: (WrQueue) UserMode Alertable
ffffe001d173e5c0 QueueObject
Not impersonating
DeviceMap ffffc00019113080
Owning Process ffffe001d147c840 Image: audiodg.exe
Attached Process N/A Image: N/A
Wait Start TickCount 518918 Ticks: 17616 (0:00:04:35.250)
Context Switch Count 9 IdealProcessor: 1
UserTime 00:00:00.000
KernelTime 00:00:00.000
Win32 Start Address 0x00007fff6978b350
Stack Init ffffd001c70c6dd0 Current ffffd001c70c6520
Base ffffd001c70c7000 Limit ffffd001c70c1000 Call 0
Priority 9 BasePriority 8 UnusualBoost 0 ForegroundBoost 0 IoPriority 2 PagePriority 5
Kernel stack not resident.
由于此代码未处于活动状态,因此所有线程都处于 WAIT 状态,正如预期的那样。
线程
用于查看和设置线程的命令与进程的命令非常相似。 使用 !thread 命令来查看线程。 使用 .thread 来设置当前线程。
要浏览与媒体播放器关联的线程,请再次播放媒体剪辑。 如果上一节所述的断点仍然存在,则将在 audiodg.exe 的上下文中停止。
使用 !thread -1 0 显示当前线程的简要信息。 这样会显示线程地址、线程和进程 ID、线程环境块 (TEB) 地址、创建线程运行的 Win32 函数地址(如有),以及线程的计划状态。
0: kd> !thread -1 0
THREAD ffffe001d3a27040 Cid 10f0.17f4 Teb: 000000ee6cf9d000 Win32Thread: 0000000000000000 RUNNING on processor 0
要查看有关正在运行的线程的详细信息,请键入 !thread。 应显示类似如下的信息。
0: kd> !thread
THREAD ffffe001d3a27040 Cid 10f0.17f4 Teb: 000000ee6cf9d000 Win32Thread: 0000000000000000 RUNNING on processor 0
IRP List:
ffffe001d429e580: (0006,02c8) Flags: 000008b4 Mdl: 00000000
Not impersonating
DeviceMap ffffc00019113080
Owning Process ffffe001d147c840 Image: audiodg.exe
Attached Process N/A Image: N/A
Wait Start TickCount 537630 Ticks: 0
Context Switch Count 63 IdealProcessor: 1
UserTime 00:00:00.000
KernelTime 00:00:00.015
Win32 Start Address 0x00007fff6978b350
Stack Init ffffd001c70c6dd0 Current ffffd001c70c6520
Base ffffd001c70c7000 Limit ffffd001c70c1000 Call 0
Priority 8 BasePriority 8 UnusualBoost 0 ForegroundBoost 0 IoPriority 2 PagePriority 5
Child-SP RetAddr : Args to Child : Call Site
ffffd001`c70c62a8 fffff800`7a0fa607 : ffffe001`d4aec5c0 ffffe001`cdefd3d8 ffffe001`d4aec5c0 ffffe001`cdefd390 : tabletaudiosample!CMiniportWaveRT::NewStream [c:\data1\threshold\audio\endpointscommon\minwavert.cpp @ 562]
ffffd001`c70c62b0 fffff800`7a0fb2c3 : 00000000`00000000 ffffe001`d429e580 ffffe001`d4ea47b0 ffffe001`cdefd3d8 : portcls!CPortPinWaveRT::Init+0x2e7
ffffd001`c70c6340 fffff800`7a0fc7f9 : ffffe001`d4aec430 00000000`00000000 ffffe001`d10b8380 ffffe001`d429e580 : portcls!CPortFilterWaveRT::NewIrpTarget+0x193
ffffd001`c70c63c0 fffff800`7a180552 : 00000000`00000000 ffffe001`d10b8380 ffffe001`d429e580 ffffe001`d4565600 : portcls!xDispatchCreate+0xd9
ffffd001`c70c6450 fffff800`7a109a9a : ffffe001`d10b84d0 ffffe001`d10b8380 00000000`00000000 ffffe001`00000000 : ks!KsDispatchIrp+0x272
ffffd001`c70c6510 fffff800`7bd314b1 : ffffe001`d429e580 ffffd001`c70c6590 ffffe001`d429e800 ffffe001`ce80da70 : portcls!DispatchCreate+0x7a
ffffd001`c70c6540 fffff803`cda1bfa8 : 00000000`00000025 00000000`00000000 00000000`00000000 ffffe001`d429e580 : ksthunk!CKernelFilterDevice::DispatchIrp+0xf9
ffffd001`c70c65a0 fffff803`cda7b306 : 00000000`000002fc ffffe001`d5e0d510 00000000`00000000 ffffe001`d3341bd0 : nt!IopParseDevice+0x7c8
ffffd001`c70c6770 fffff803`cda12916 : 00000000`000002fc ffffd001`c70c68d0 ffffe001`d3341ba0 fffff803`cda7b250 : nt!IopParseFile+0xb6
ffffd001`c70c67d0 fffff803`cda1131c : ffffe001`ceb6c601 ffffd001`c70c69e0 00000000`00000040 ffffe001`cd127dc0 : nt!ObpLookupObjectName+0x776
ffffd001`c70c6970 fffff803`cd9fedb8 : ffff8ab8`00000001 ffffe001`d5e0d510 00000000`00000000 00000000`00000000 : nt!ObOpenObjectByNameEx+0x1ec
ffffd001`c70c6a90 fffff803`cd9fe919 : 000000ee`6d37c6e8 00000004`6d37c500 000000ee`6d37c5f0 000000ee`6d37c5e0 : nt!IopCreateFile+0x3d8
ffffd001`c70c6b40 fffff803`cd752fa3 : fffff6fb`7da05360 fffff6fb`40a6c0a8 fffff681`4d815760 ffff8ab8`92895e23 : nt!NtCreateFile+0x79
ffffd001`c70c6bd0 00007fff`69805b74 : 00007fff`487484e6 0000029b`00000003 00000000`0000012e 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x13 (TrapFrame @ ffffd001`c70c6c40)
000000ee`6d37c568 00007fff`487484e6 : 0000029b`00000003 00000000`0000012e 00000000`00000000 00000000`00000000 : 0x00007fff`69805b74
000000ee`6d37c570 0000029b`00000003 : 00000000`0000012e 00000000`00000000 00000000`00000000 00000000`00000000 : 0x00007fff`487484e6
000000ee`6d37c578 00000000`0000012e : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000080 : 0x0000029b`00000003
000000ee`6d37c580 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000080 00000000`00000000 : 0x12e
使用 k 命令查看与线程关联的调用堆栈。
0: kd> k
# Child-SP RetAddr Call Site
00 ffffd001`c70c62a8 fffff800`7a0fa607 tabletaudiosample!CMiniportWaveRT::NewStream [c:\data1\threshold\audio\endpointscommon\minwavert.cpp @ 562]
01 ffffd001`c70c62b0 fffff800`7a0fb2c3 portcls!CPortPinWaveRT::Init+0x2e7
02 ffffd001`c70c6340 fffff800`7a0fc7f9 portcls!CPortFilterWaveRT::NewIrpTarget+0x193
03 ffffd001`c70c63c0 fffff800`7a180552 portcls!xDispatchCreate+0xd9
04 ffffd001`c70c6450 fffff800`7a109a9a ks!KsDispatchIrp+0x272
05 ffffd001`c70c6510 fffff800`7bd314b1 portcls!DispatchCreate+0x7a
06 ffffd001`c70c6540 fffff803`cda1bfa8 ksthunk!CKernelFilterDevice::DispatchIrp+0xf9
07 ffffd001`c70c65a0 fffff803`cda7b306 nt!IopParseDevice+0x7c8
08 ffffd001`c70c6770 fffff803`cda12916 nt!IopParseFile+0xb6
09 ffffd001`c70c67d0 fffff803`cda1131c nt!ObpLookupObjectName+0x776
0a ffffd001`c70c6970 fffff803`cd9fedb8 nt!ObOpenObjectByNameEx+0x1ec
0b ffffd001`c70c6a90 fffff803`cd9fe919 nt!IopCreateFile+0x3d8
0c ffffd001`c70c6b40 fffff803`cd752fa3 nt!NtCreateFile+0x79
0d ffffd001`c70c6bd0 00007fff`69805b74 nt!KiSystemServiceCopyEnd+0x13
0e 000000ee`6d37c568 00007fff`487484e6 0x00007fff`69805b74
0f 000000ee`6d37c570 0000029b`00000003 0x00007fff`487484e6
10 000000ee`6d37c578 00000000`0000012e 0x0000029b`00000003
11 000000ee`6d37c580 00000000`00000000 0x12e
在调试器中输入 g 以便向前运行代码,直到媒体剪辑播放完毕。 然后按 Ctrl - ScrLk (Ctrl-Break) 键终端调试器。使用 !thread 命令确认现在运行的是不同的线程。
0: kd> !thread
THREAD ffffe001ce80b840 Cid 17e4.01ec Teb: 00000071fa9b9000 Win32Thread: ffffe001d41690d0 RUNNING on processor 0
Not impersonating
DeviceMap ffffc0001974e2c0
Owning Process ffffe001d1760840 Image: rundll32.exe
Attached Process N/A Image: N/A
Wait Start TickCount 538040 Ticks: 0
Context Switch Count 3181840 IdealProcessor: 0
UserTime 00:00:08.250
KernelTime 00:00:10.796
Win32 Start Address 0x00007ff6d2f24270
Stack Init ffffd001cd16afd0 Current ffffd001cd16a730
Base ffffd001cd16b000 Limit ffffd001cd165000 Call 0
Priority 8 BasePriority 8 UnusualBoost 0 ForegroundBoost 0 IoPriority 2 PagePriority 5
Child-SP RetAddr : Args to Child : Call Site
fffff803`cf373d18 fffff800`7a202852 : fffff803`cf373e60 00000000`00000001 ffffe001`cf4ed330 00000000`0000ffff : nt!DbgBreakPointWithStatus
fffff803`cf373d20 fffff803`cd6742c6 : ffffe001`cf4ed2f0 fffff803`cf373e60 00000000`00000001 00000000`0004e4b8 : kdnic!TXSendCompleteDpc+0x142
fffff803`cf373d60 fffff803`cd74d495 : 00000000`00000000 fffff803`cd923180 fffff803`cde1f4b0 fffff901`40669010 : nt!KiRetireDpcList+0x5f6
fffff803`cf373fb0 fffff803`cd74d2a0 : 00000000`00000090 0000000e`0000006a 00000000`00000092 00000000`00000000 : nt!KxRetireDpcList+0x5 (TrapFrame @ fffff803`cf373e70)
ffffd001`cd16a6c0 fffff803`cd74bd75 : 00000000`00000000 fffff803`cd74a031 00000000`00000000 00000000`00000000 : nt!KiDispatchInterruptContinue
ffffd001`cd16a6f0 fffff803`cd74a031 : 00000000`00000000 00000000`00000000 ffffe001`cff4d2a0 fffff803`cd67738e : nt!KiDpcInterruptBypass+0x25
ffffd001`cd16a700 fffff960`50cdb5a4 : fffff901`400006d0 00000000`00000001 fffff901`40000d60 ffffd001`cd16a9f0 : nt!KiInterruptDispatchNoLockNoEtw+0xb1 (TrapFrame @ ffffd001`cd16a700)
ffffd001`cd16a890 fffff960`50c66b2f : 00000000`00000000 fffff901`40669010 fffff901`42358580 fffff901`40000d60 : win32kfull!Win32FreePoolImpl+0x34
ffffd001`cd16a8c0 fffff960`50c68cd6 : 00000000`00000000 ffffd001`cd16a9f0 fffff901`400006d0 fffff901`400c0460 : win32kfull!EXLATEOBJ::vAltUnlock+0x1f
ffffd001`cd16a8f0 fffff803`cd752fa3 : 00000000`00000000 00000000`00000000 ffffe001`ce80b840 00000000`00000000 : win32kfull!NtGdiAlphaBlend+0x1d16
ffffd001`cd16add0 00007fff`674c1494 : 00007fff`674b1e97 0000a7c6`daee0559 00000000`00000001 0000020b`741f3c50 : nt!KiSystemServiceCopyEnd+0x13 (TrapFrame @ ffffd001`cd16ae40)
00000071`fa74c9a8 00007fff`674b1e97 : 0000a7c6`daee0559 00000000`00000001 0000020b`741f3c50 00000000`00ffffff : 0x00007fff`674c1494
00000071`fa74c9b0 0000a7c6`daee0559 : 00000000`00000001 0000020b`741f3c50 00000000`00ffffff 00000000`00000030 : 0x00007fff`674b1e97
00000071`fa74c9b8 00000000`00000001 : 0000020b`741f3c50 00000000`00ffffff 00000000`00000030 00000000`01010bff : 0x0000a7c6`daee0559
00000071`fa74c9c0 0000020b`741f3c50 : 00000000`00ffffff 00000000`00000030 00000000`01010bff 00000000`00000000 : 0x1
00000071`fa74c9c8 00000000`00ffffff : 00000000`00000030 00000000`01010bff 00000000`00000000 00000000`000000c0 : 0x0000020b`741f3c50
00000071`fa74c9d0 00000000`00000030 : 00000000`01010bff 00000000`00000000 00000000`000000c0 00000000`00000030 : 0xffffff
00000071`fa74c9d8 00000000`01010bff : 00000000`00000000 00000000`000000c0 00000000`00000030 00000071`00000030 : 0x30
00000071`fa74c9e0 00000000`00000000 : 00000000`000000c0 00000000`00000030 00000071`00000030 00000071`01ff8000 : 0x1010bff
图像名称是 rundll32.exe,这确实不是与播放媒体剪辑关联的图像名称。
注意 要设置当前线程,请键入 .thread <thread number>。
有关线程和进程的详细信息,请参阅以下参考:
第 11 部分:IRQL、寄存器和反汇编
查看保存的 IRQL
在第 11 部分中,将显示 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 反汇编。
第 12 部分:使用内存
在第 12 部分中,将使用调试器命令来显示内存内容。
查看内存
可能需要检查内存以识别问题,或者检查变量、指针等。 输入以下 d* <address> 命令之一即可显示内存。
db |
以字节值和 ASCII 字符显示数据。 |
dd |
以双宽单词(4 个字节)显示数据。 |
du |
以 Unicode 字符显示数据。 |
dw |
以单词值(2 个字节)和 ASCII 字符显示数据。 |
注意 如果尝试显示无效的地址,则其内容显示为问号 (?)。
或者,也可以通过选择查看>内存来查看内存。 使用“显示格式”下拉菜单更改内存的显示方式。
要查看与音量控制关联的数据,请使用 bm 命令在 PropertyHandlerAudioEngineVolumeLevel 例程上设置断点。 在设置新的断点之前,我们将使用 bc * 来清除之前的所有断点。
kd> bc *
使用 bm 命令在 PropertyHandlerAudioEngineVolumeLevel 例程上设置一个断点。
kd> bm tabletaudiosample!CMiniportWaveRT::SetDeviceChannelVolume 1: fffff80f`02c3a4b0 @!"tabletaudiosample!CMiniportWaveRT::SetDeviceChannelVolume"
列出断点,以便确认断点已正确设置。
kd> bl 1: fffff80f`02c3a4b0 @!"tabletaudiosample!CMiniportWaveRT::SetDeviceChannelVolume"
使用 g 命令重新启动代码执行。
在目标系统的系统托盘中调整音量。 这将导致断点触发。
Breakpoint 1 hit tabletaudiosample!CMiniportWaveRT::SetDeviceChannelVolume: fffff80f`02c3a4b0 44894c2420 mov dword ptr [rsp+20h],r9d
使用查看>局部菜单项显示局部变量。 记录下 IVolume 变量的当前值。
键入 dt 命令和变量名,即可显示示例代码中 IVolume 变量的数据类型和当前值。
kd> dt lVolume Local var @ 0xa011ea50 Type long 0n-6291456
输入 SetDeviceChannelVolume 时断点被触发。
STDMETHODIMP_(NTSTATUS) CMiniportWaveRT::SetDeviceChannelVolume(_In_ ULONG _ulNodeId, _In_ UINT32 _uiChannel, _In_ LONG _Volume) { NTSTATUS ntStatus = STATUS_INVALID_DEVICE_REQUEST; PAGED_CODE (); DPF_ENTER(("[CMiniportWaveRT::SetEndpointChannelVolume]")); IF_TRUE_ACTION_JUMP(_ulNodeId != KSNODE_WAVE_AUDIO_ENGINE, ntStatus = STATUS_INVALID_DEVICE_REQUEST, Exit); // Snap the volume level to our range of steppings. LONG lVolume = VOLUME_NORMALIZE_IN_RANGE(_Volume); ntStatus = SetChannelVolume(_uiChannel, lVolume); Exit: return ntStatus; }
尝试使用 dt(显示类型)命令显示 IVolume 内存位置的值。
kd> dt dt lVolume Local var @ 0xffffb780b7eee664 Type long 0n0
因为变量尚未定义,所以不包含信息。
按 F10 向前运行到 SetDeviceChannelVolume 中的最后一行代码。
return ntStatus;
使用 dt(显示类型)命令显示 IVolume 内存位置的值。
kd> dt lVolume Local var @ 0xffffb780b7eee664 Type long 0n-6291456
变量处于活动状态后,本例中显示的值为 6291456。
还可以使用 ?(对表达式进行评估))命令来显示内存位置。
kd> ? lVolume Evaluate expression: -79711507126684 = ffffb780`b7eee664
所示地址 ffffb780`b7eee664 是 lVolume 变量的地址。 使用 dd 命令显示该位置的内存内容。
kd> dd ffffb780`b7eee664 ffffb780`b7eee664 ffa00000 00000018 00000000 c52d7008 ffffb780`b7eee674 ffffc98e e0495756 fffff80e c52d7008 ffffb780`b7eee684 ffffc98e 00000000 fffff80e 00000000 ffffb780`b7eee694 ffffc98e ffa00000 ffffb780 b7eee710 ffffb780`b7eee6a4 ffffb780 00000000 00000000 c7477260 ffffb780`b7eee6b4 ffffc98e b7eee7a0 ffffb780 b7eee6f0 ffffb780`b7eee6c4 ffffb780 e04959ca fffff80e 00000000 ffffb780`b7eee6d4 00000000 00000028 00000000 00000002
通过指定范围参数 L4,可以显示地址的前四个字节。
kd> dd ffffb780`b7eee664 l4 ffffb780`b7eee664 ffa00000 00000018 00000000 c52d7008
要查看显示的不同类型内存输出,请键入 du、da 和 db 命令。
kd> du ffffb780`b7eee664 ffffb780`b7eee664 "" kd> a ffffb780`b7eee664 ffffb780`b7eee664 "" kd> db 0xffffae015ff97664 ffffae01`5ff97664 00 80 bc ff 18 00 00 00-00 00 00 00 08 50 e0 51 .............P.Q ffffae01`5ff97674 00 c0 ff ff 56 57 da 56-0e f8 ff ff 08 50 e0 51 ....VW.V.....P.Q ffffae01`5ff97684 00 c0 ff ff 00 00 00 00-0e f8 ff ff 00 00 00 00 ................ ffffae01`5ff97694 00 c0 ff ff aa 80 bc ff-01 ae ff ff 10 77 f9 5f .............w._ ffffae01`5ff976a4 01 ae ff ff 40 00 00 00-00 e6 ff ff 10 dc 30 55 ....@.........0U ffffae01`5ff976b4 00 c0 ff ff a0 77 f9 5f-01 ae ff ff f0 76 f9 5f .....w._.....v._ ffffae01`5ff976c4 01 ae ff ff ca 59 da 56-0e f8 ff ff 00 00 00 00 .....Y.V........ ffffae01`5ff976d4 00 00 00 00 28 00 00 00-00 00 00 00 02 00 00 00 ....(...........
使用 df float 选项可将数据显示为单精度浮点数(4 个字节)。
df ffffb780`b7eee664 ffffb780`b7eee664 -1.#QNAN 3.3631163e-044 0 -2775.002 ffffb780`b7eee674 -1.#QNAN -5.8032637e+019 -1.#QNAN -2775.002 ffffb780`b7eee684 -1.#QNAN 0 -1.#QNAN 0 ffffb780`b7eee694 -1.#QNAN -1.#QNAN -1.#QNAN -2.8479408e-005
写入内存
与用于读取内存的命令类似,也可以使用 e* 命令更改内存内容。
命令 | 说明 |
---|---|
ea |
ASCII 字符串(非 NULL 结尾) |
eu |
Unicode 字符串(非 NULL 结尾 |
ew |
Word 值(2 个字节) |
eza |
以 NULL 结尾的 ASCII 字符串 |
ezu |
以 Null 值结束的 Unicode 字符串 |
eb |
字节值 |
ed |
双单词值(4 个字节) |
下面的示例显示了如何覆盖内存。
首先,找到示例代码中使用的 lVolume 的地址。
kd> ? lVolume Evaluate expression: -79711507126684 = ffffb780`b7eee664
使用 eb 命令用新字符覆盖该内存地址。
kd> eb 0xffffb780`b7eee664 11 11 11 11 11
输入 db 命令,显示内存位置,确认字符已被覆盖。
kd> db 0xffffb780`b7eee664 ffffb780`b7eee664 11 11 11 11 11 00 00 00-00 00 00 00 08 70 2d c5 .............p-. ffffb780`b7eee674 8e c9 ff ff 56 57 49 e0-0e f8 ff ff 08 70 2d c5 ....VWI......p-. ffffb780`b7eee684 8e c9 ff ff 00 00 00 00-0e f8 ff ff 00 00 00 00 ................ ffffb780`b7eee694 8e c9 ff ff 00 00 a0 ff-80 b7 ff ff 10 e7 ee b7 ................ ffffb780`b7eee6a4 80 b7 ff ff 00 00 00 00-00 00 00 00 60 72 47 c7 ............`rG. ffffb780`b7eee6b4 8e c9 ff ff a0 e7 ee b7-80 b7 ff ff f0 e6 ee b7 ................ ffffb780`b7eee6c4 80 b7 ff ff ca 59 49 e0-0e f8 ff ff 00 00 00 00 .....YI......... ffffb780`b7eee6d4 00 00 00 00 28 00 00 00-00 00 00 00 02 00 00 00 ....(...........
或者,可以在监视或局部窗口中修改内存的内容。 在监视窗口中,可能会看到与当前帧无关的变量。 如果不符合上下文,修改它们就没有意义。
第 13 部分:结束 WinDbg 会话
<- 在主机系统上
如果想保留调试器,但又想连接到目标计算机,请使用 bc *
清除所有断点,这样目标计算机就不会尝试连接到主机调试器。 然后使用 g
命令让目标计算机再次运行。
要结束调试会话,请在主机系统上中断调试器,输入 qd
(退出并分离)命令,或者从菜单中选择停止调试。
0: kd> qd
有关详细信息,请参阅调试参考文档中的在 WinDbg 中结束调试会话(经典版)。
第 14 部分:Windows 调试资源
有关 Windows 调试的其他信息。 请注意,其中一些书籍会在示例中使用 Windows Vista 等较旧版本的 Windows,但所讨论的概念适用于大多数版本的 Windows。
书籍
《高级 Windows 调试》,作者:Mario Hewardt 和 Daniel Pravat
《在 Windows 调试中:Windows® 调试和跟踪策略实用指南》,作者 Tarik Soulami
《Windows 内部》,作者 Pavel Yosifovich、Alex Ionescu、Mark Russinovich 和 David Solomon
视频
碎片整理工具秀 WinDbg 第 13-29 集:</shows/defrag-tools/>
培训供应商:
OSR - https://www.osr.com/