2.3 驱动的调试
虽然在驱动代码中通过DbgPrint函数打印的日志可以反映出驱动的运行情况,但对程序的错误诊断是远远不够的,在实际的调试中,还需结合单步运行代码、设置代码断点、观察内存等多种方式。下面为读者介绍如何单步调试驱动。
为了安全起见,驱动调试应该在虚拟机中进行,本书中所有提及的驱动调试,都是指通过Vmware(或其他虚拟机)运行一个虚拟机操作系统,在该操作系统中运行被调试的驱动,开发者在物理机器上使用内核调试工具(如 Windbg),通过网络、USB、串口、1394等方式连接到虚拟机操作系统,进行驱动调试。为了表述清晰,下面把物理机器称为调试机器,把运行被调试驱动的虚拟机称为被调试机器。
本书第1章中提到,驱动开发可以使用新版的Visual Studio+WDK配套的开发环境(如Visual Studio 2017,下面简称Visual Studio为VS),也可以使用老版本带独立编译环境的WDK开发环境,这两种环境在调试内核驱动的方式上存在一些差异。下面重点对这两种方式进行介绍。
2.3.1 基于VS+WDK环境调试
对于在VS+WDK集成环境中开发驱动的读者来说,驱动调试变的尤其简单,原因是微软已经优化了一系列的操作配置,使得驱动调试与用户态EXE程序调试的操作基本相同。
在介绍具体的调试前,首先介绍一下调试的环境,笔者物理机器的系统为Windows 10,Visual Studio的版本为2017,配合WDK 10版本。由于调试需要通过虚拟机进行,所以笔者在电脑上安装了Vmware 14,Vmware虚拟机内安装一个Windows 10操作系统,虚拟机的网卡使用NAT方式,具体情况如下所示。
调试机器:Windows 10
调试机器IP:192.168.116.1
虚拟机:vmware 14
被调试机器:Windwos 10
被调试机器IP:192.168.116.139
读者没有必要照搬上面的配置,根据自身需要设置即可。本次调试准备使用网络作为调试机器与被调试机器之间的连接方式,众所周知,Windows系统内置了一个防火墙,这个防火墙可能会对调试所用的网络进行拦截,为了避免不必要的麻烦,建议读者关闭调试机器的防火墙,或配置特殊的防火墙放行规则,不管怎样,开发者都要保证调试机器与被调试机器之间的网络畅通。
下面为读者介绍如何配置被调试机器,在被调试机器中:以管理员权限运行cmd(命令提示符),在命令中输入:bcdedit /debug on,然后回车。这条命令的作用是把被调试机器设置成调试模式。
在命令行中输入:bcdedit /dbgsettings net hostip:192.168.116.1 port:50010并回车,这条命令的意思是使用网络进行调试的连接方式,hostip指调试机器的IP,在笔者电脑上是192.168.116.1,port表示所使用网络的端口,建议范围是49152 至65535,笔者指定的端口为50010。在上面的命令执行完之后,cmd命令行上会显示一个Key,读者需要保存这个Key,用于后面调试机器的配置。如图2-6所示是笔者的被调试机器执行命令后的截图。
以上就是被调试机器的配置,下面为读者介绍调试机器的配置,首先打开VS,在VS菜单栏中找到“Driver”,在“Driver”的下拉菜单中找到“Test”→“Configure Devices”,如图2-7所示。
图2-6 被调试机器的配置
图2-7 VS的设备配置
点击“Add New Device”弹出的配置对话框,在“Display name”下面输入设备的名字,例如:MyFirstDevice;在“Device Type”下面的下拉框中选择“Computer”;在Network host name下面输入被调试机器的hostname,也可以输入IP,在本例中,笔者输入被调试机器的IP;在最后一项Provisioning Options中,选择第二项“Manually configure debuggers and do not provision”,意思是说手动配置被调试机器的调试选项以及手动分发驱动文件。整体配置完成后如图2-8所示。
图2-8 Device基础信息配置
完成配置后请点击“下一步”按钮,进入“configure debugger settings”配置页,在“Windows Debugger – Kernel Mode”下面,找到“Connection Type”,在下拉框中选择“Network”;在“Port Number”中填入被调试机器中配置的端口值50010;在“Key”中填入被调试机器中生成的Key,参考图2-6,本例中Key为34kqi8ifbxa1x.1npyzog63k8tg.18s7l0a2v59z3.iy8og23m26m5;在HostIp中填入调试机器的IP,本例为192.168.116.1;如果被调试机器只有一个网卡,最后一个“bus Parameters”可以不填,否则需要根据PCI规范,填入相应设备的总线号(Bus number)、设备号(Device Number)以及功能号(Function number),笔者的被调试机器只有一块网卡,所以这个值留空,如图2-9所示。完成配置后点击“完成”按钮。这样就完成了VS的配置。
图2-9 调试器配置
一切准备妥当后,下面准备以第1章的FirstDriver驱动来作为调试对象,开始调试前,首先修改一下FirstDriver驱动的入口函数,在DriverEntry入口函数中加入一个断点KdBreakPoint(),这样当FirstDriver运行的时候,就会在DriverEntry触发这个断点而停止下来,这是一个常用的技巧。
请注意,KdBreakPoint只对Debug版的驱动有效,如果需要对Release版本的驱动放置断点代码,请使用DbgBreakPoint。修改后的DriverEntry函数如下:
首先对FirstDriver工程进行编译,生成FirstDriver.sys文件,然后在VS菜单中,找到“调试”→“附加到进程”,在弹出的对话框中,选择“连接类型”为“Windows Kernel Mode Debugger”,“连接目标”选择为刚才我们配置好的MyFirstDevice,在“可用进程”中选择“Kernel”,如图2-10所示。
图2-10 附加到内核调试器
单击“附加”按钮后,会在VS界面上弹出一个“Debugger Immediate Window”界面,界面上显示:
这表示当前调试器使用网络连接方式,正在等待被连接。接下来重启被调试机器,让被调试机器的调试设置生效。
被调试机器在重启过程中,会主动连接设置的50010端口,连接建立好之后,可以在VS的“Debugger Immediate Window”中看到如图2-11所示的信息。
图2-11 成功建立连接
最后请把FirstDriver.sys文件放入被调试机器中,如笔者把该文件放置在被调试机器中的C盘下,然后根据前面介绍的sc create、sc start等命令运行该驱动,由于驱动的DriverEntry函数中被放置了断点KdBreakPoint,所以在执行到KdBreakPoint时调试器会中断下来,如图2-12所示。
读者可以在上图界面中按下F10单步调试驱动代码,非常方便。
以上是VS调试驱动的基本步骤,请读者务必亲自操作一遍。在图2-8的配置当中,笔者选择的是“Manually configure debuggers and do not provision”方式,另外一种方式为“Provision device and choose debugger settings”,这种方式需要在被调试机器中安装一套调试工具,配置起来相对麻烦,由于篇幅有限,这里不再赘述,请感兴趣的读者自行研究。
图2-12 VS入口函数断点
2.3.2 基于Windbg调试
下面为读者介绍另外一种调试方法:基于Windbg工具。实际上通过VS调试驱动,内部也是基于Windbg的内核。
如果读者安装较新版的WDK,可以在WDK的安装目录中找到Windbg,笔者把WDK安装在C:\Program Files (x86)\Windows Kits目录下,则64位的Windbg位置为:C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Windbg.exe;32位的Windbg位置为:C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Windbg.exe。
如果读者安装较老版本的WDK,WDK内不包含Windbg,可能需要到微软的官方网站下载独立的Windbg安装包。
假设读者已经成功部署了Windbg,下面为读者介绍如何通过Windbg来调试驱动,上面介绍了如何通过网络作为介质连接调试机器与被调试机器,下面换一个方式,选用串口(COM口)作为连接介质。
与VS调试驱动类似,笔者准备了一台Windows 10的被调试机器(虚拟机)。在被调试机器内,以管理员权限打开cmd(命令提示符),然后输入:
bcdedit /debug on 并按“回车”键,接着输入:bcdedit /dbgsettings serial baudrate:115200 debugport:2并按“回车”键。第一条命令读者应该很熟悉了,表示把机器设置成调试模式,第二条命令是设置通过串口2来作为连接介质,串口的波特率为115200。命令执行完之后如图2-13所示。
使用bcdedit的方法配置调试,只适用于Vista及以上系统,对于Vista以下的系统(如XP),需要通过修改boot.ini文件,在系统盘(一般是C盘)中找到boot.ini文件,以记事本的方式打开,在[operating system]下面直接修改启动参数,下面给出一个示例:
图2-13 设置串口调试
上面介绍的是在现有的启动项上修改启动参数,在某些场景下,还可以通过bcdedit(XP下是boot.ini文件)增加一个启动项,新增的启动项用于调试。由于本例中的虚拟机的直接用途就是调试,所以直接修改现有的启动项即可。
配置完成后,请关闭虚拟机内的操作系统,然后在Vmware的配置界面,新增一个串口设备,设置该串口使用“命名管道”,名字为:\\.\pipe\com_2,具体配置如图2-14所示。
图2-14 串口设备参数
到此为止,被调试机器所需的设置已经全部完成。接下来开始配置Windbg,首先找到Windbg.exe文件所在的目录,请参考本节前面所提及的位置,在笔者电脑上是C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Windbg.exe,然后右键点击Windbg.exe文件,选择“发送到”→“桌面快捷方式”。在桌面上找到Windbg的快捷方式,点击右键,选择“属性”,在“快捷方式”分页下,找到“目标”,填入:"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Windbg.exe" -b -k com:pipe,port=\\.\pipe\com_2,resets=0。Windbg启动后会去连接\\.\pipe\com_2管道。
一切准备就绪后,首先开启虚拟机,然后打开桌面的Windbg快捷方式,稍微等待一下,Windbg就可以连接上被调试机器了,如图2-15所示。
与VS调试驱动类似,请读者把FirstDriver.sys放在被调试机器的系统中,如C:\FirstDriver.sys,然后通过sc create、sc start命令启动驱动,驱动执行入口函数中的KdBreakPoint会中断到Windbg中,如图2-16所示。
图2-15 Windbg调试
图2-16 Windbg入口函数断点
上面介绍了两种内核调试的方法,细心的读者应该可以发现,这两种方式大同小异,无论是通过VS调试还是通过Windbg调试,调试的命令都是相同的,读者可以选择适合自己操作习惯的方式。笔者更喜欢通过Windbg调试,原因是笔者一般会使用多个虚拟机同时调试,Windbg界面清爽,内存占用低,启动速度更快。
驱动调试是驱动开发环节中最基本的一环,请读者务必掌握。另外,内核调试以及调试技巧隶属于调试范畴,读者需要在实践中不断学习与积累,本书重点是内核驱动开发,对调试技巧有迫切需求的读者,请查阅其他资料。