• 1 简述
  • 2 为什么选择Hello World
  • 3 我的驱动开发环境
  • 4 Hello Word驱动源代码编写
  • 5 编译Hello World驱动
  • 6 安装Hello World驱动
  • 7 调试Hello World驱动
  • 8 总结

1 简述

从今天开始,我们开始学习Windows下的驱动开发。与时俱进,我们采用的是WDF(Windows Driver Foundation)驱动框架。一方面,该框架对于简化了驱动,提供了PNP和电源管理的默认实现,使得我们暂时不必关注一些复杂的东西,简化了学习难度。另一方面,我们本着由浅入深的原则,一开始对于驱动框架中的东西不必要所有的都弄懂,先有一个概念,随着学习的深入,自己动手实践,有些东西慢慢地就理解了。

2 为什么选择Hello World

老实说,对于选择什么程序作为第一个学习的驱动程序,一直纠结于是否要用Hello World。最后还是决定使用它,一方面,这绝对是最简单的,简单到大家都不愿意看,也给刚刚起步的人一个驱动的直观印象。另一方面,也是最重要的,那就是第一个程序的重要性不在于这个程序本身。更重要的作用是帮助我们磨刀,我们要让这个简单的KMDF程序从编写到编译成功,并成功的安装运行。熟悉整个过程,熟悉工具的使用,毕竟,实践才是学习的最好方法。

3 我的驱动开发环境

由于太穷,手头只有一台笔记本电脑,当然我不能直接用来开发驱动,万一什么地方出问题系统直接炸了那不是呵呵了。所以掏出利器VMware Workstation,在虚拟机中装Win7肯定也不行,太卡,因此装上XP。本机Win7装上驱动开发环境WDK,Visual Studio 2008,用来编写编译驱动,然后将编译好的驱动安装到XP虚拟机中。本机Win7下装上WinDbg进行联机调试,这样,编辑编译调试环境都有了。

说明一下,虽然装了Visual Studio 2008,但是我们并不用它来编译驱动,它仅用作代码编辑,我习惯使用它。如果你愿意,也可以仅仅使用记事本,但是这样没有语法高亮,写出来代码黑乎乎一片,看起来并不具有一个良好的心情。不过这因人而异,随自己所好。编译所需的makefile和sources等文件我们都自己编写,有开发环境可以自动生成,但是学习时并不提倡这样,手动进行这些过程可以帮助我们理解驱动的工作过程,同时当我们编写的驱动安装失败或者无法工作时,也给解决方法提供一些灵感。

总结一下,我在开始一个最简单的驱动开发的时候用到了如下工具:

  1. VMware Workstation虚拟机,安装Win XP SP3.
  2. Visual Studio 2008开发环境。
  3. WinDbg调试工具

4 Hello Word驱动源代码编写

这个驱动的功能就是在设备被安装的时候打印出“Hello World!”字符串,在驱动被卸载的时候打印出“Thanks For Using!”字符串。顺便说一下,对于代码,我并不会一句一句地讲那么详细,因为网上并不缺乏驱动教程。你在看本教程之前,应该先去看看相关的教程和书籍,本教程的使用的例子基本上都来自于相关书籍和教程。那么,我写下这个教程有什么意义?一方面,学习是需要整理的,经常需要理一理自己学过的东西,有助于自己进步,所以我把它整理出来便于自己学习进步。另一方面,网上的教程和书籍一下子灌输了太多的东西,看着看着就迷失了。在内核下写程序,很重要的功力就是对于内核的了解,但一开始我们知道的甚少,一下子灌输内核的许多东西会造成很难学习,迷失不知怎么进行下一步。而且,这些书籍中的实例对于初学者过于复杂,没有循序渐进的过程,难免打击学习信心。所以我在教程中尽量简化,一步一步深入,慢慢地体会驱动开发。这也是我自己的学习过程。

回到我们的Hello World驱动,再编写它之前,你应该对于设备堆栈和设备安装和移除的过程有一些了解,你可以看看《Windows 7设备驱动程序开发》第7章“即插即用和电源管理”的部分内容。在设备插入时,框架会调用EvtDriverDeviceAdd回调函数,在该函数中创建设备对象。驱动卸载时,框架调用EvtDriverUnload回调函数,所以,我们只需要自己实现这两个回调函数,然后向框架注册即可。

下面是完整的代码:

#include <ntddk.h>
#include <wdf.h>// 添加设备回调函数
NTSTATUS EvtDriverDeviceAdd( IN WDFDRIVER Driver,IN PWDFDEVICE_INIT DeviceInit);
// 驱动卸载回调函数
VOID EvtDriverUnload( IN WDFDRIVER Driver );NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath )
{NTSTATUS status;WDF_DRIVER_CONFIG config;// 注册EvtDriverDeviceAdd回调函数WDF_DRIVER_CONFIG_INIT(&config, EvtDriverDeviceAdd);// 注册EvtDriverUnload回调函数config.EvtDriverUnload = EvtDriverUnload;// 创建驱动对象status = WdfDriverCreate( DriverObject, RegistryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, WDF_NO_HANDLE);return status;
}NTSTATUS EvtDriverDeviceAdd(IN WDFDRIVER Driver, IN PWDFDEVICE_INIT DeviceInit)
{NTSTATUS status;WDFDEVICE hDevice;PAGED_CODE();// 创建设备对象status = WdfDeviceCreate(&DeviceInit, WDF_NO_OBJECT_ATTRIBUTES, &hDevice);if( !NT_SUCCESS(status) ){KdPrint(("EvtDriverDeviceAdd failed.\n"));return status;}KdPrint(("Hello World!\n"));return status;
}VOID EvtDriverUnload( IN WDFDRIVER Driver )
{KdPrint(("Thanks For Using!\n"));
}

5 编译Hello World驱动

在编译驱动之前,除了源代码(.h和.c),还要一些用于编译的其它文件。这部分内容需要先涉及一些驱动安装相关的知识,了解驱动安装运行需要哪些文件,我们才好准备这些内容。这部分内容最完整的资料是WDK文档和MSDN文档,下面将一些重要内容做简单介绍,但是更详细的内容,读者还是要自己看文档。

MSDN给出了驱动开发和安装的七个步骤,如下:

  1. 了解Windows设备和驱动安装的基础知识。
  2. 了解驱动包及其组成。
  3. 了解INF文件。
  4. 创建你自己的驱动包。
  5. 开发和测试过程中给你的驱动包进行测试签名。
  6. 给你的驱动包进行发布签名。
  7. 发布驱动包。

也就是说,我们最后开发的所有东西都通过一个驱动包发布出去,商业的驱动包最后都打包成一个.exe,引导用户一步一步安装完成即可。最开始学习时,不需要做到这一步,但是我们需要提供一个最简单的驱动包。虽然不需要提供自安装功能,起码要包含安装运行所需要的所有内容。

根据MSDN,驱动包中包含以下文件:驱动文件、安装文件以及其它文件。其中驱动文件是由我们所写的源代码编译而成的.sys动态库,安装文件包含INF文件和签名文件(.cat),其它文件则是一些说明文件。测试签名本来是应该做的,但是这个过程并不简单,对于初学者来说,写INF文件已经是一个繁琐复杂的过程了。所以我们暂时先忽略它,相关的内容以后单独作一节来写。

现在我们就明确编译阶段的任务,生成.sys驱动,INF安装文件。与WDK中的示例一样,现在我们要编写几个文件:makefile,makefile.inc,sources,和INX。

Makefile和makefile.inc文件直接从示例中拷贝即可,基本保持不变,如果读者想弄清楚具体内容,需要知道makefile的相关知识。至于其中的一些宏定义,WDK中bin目录下有一个setenv.bat文件,从快捷方式打开WDK编译环境时会运行这个批处理文件(如图5.1),设置环境变量,这个文件中有一部分宏定义。


图5.1

下面的重点是INX文件的编写。INX文件实际上是一个包含字符串变量的INF文件,其中的字符串变量表示了版本信息,通过一个INX文件可以编译出用于不同平台和框架版本的INF文件。编译它的工具为Stampinf.exe,位于WDK中bin目录下。关于INF文件的中文最详尽清楚的教程,应该是张佩、马勇和董鉴源编著的《竹林蹊径 深入浅出Windows驱动开发》,里面对于驱动安装和测试有比较详细地描述。读者应该先看一下该书及相关文档。而且,读者一定要去看看该书,应用我的一些术语说法引用自该书,书中解释得很清楚,我只是教大家写一个完整的INX文件。

那么我们直接开始编写INX文件了,编写的过程中请参考WDK示例。此处说一下,第一个INX文件不要直接拷贝示例然后修改,最好从一个空白文件一行一行写起。你只有知道一个INX文件包含哪些部分,每部分如何组成的,以后拷贝出问题后才知道怎么修改。

INX文件与ini文件格式类似,因此,可以新建一个HelloWorld.ini文件,便于在Notepad++中提供语法高亮,阅读起来更容易,一般INX文件与驱动同名。不过,ini的注释为单行,而INX文件中的注释不需要另起一行。

先添加版本域,这也是一个必选的域:

----------------------------------------------------------------------------------
; 版本域
[Version]
Signature="$WINDOWS NT$"  ; 固定
Class=HUSTSample  ; 我们的驱动不属于已有的设备类,定义一个新的设备类
ClassGuid={FDA3877E-5FF3-4c18-8235-7FEA16EE04E2}  ; 设备类的GUID
Provider=%ProviderName%  ; 驱动的作者,由字符串域中的ProviderName指定
DriverVer=01/12/2016,21.12.36.570  ; 驱动版本; 字符串域
[Strings]
ProviderName=”HUSTD10”  ; 驱动作者
----------------------------------------------------------------------------------

版本域中涉及到新添加的设备类,同样在《竹林蹊径 深入浅出Windows驱动开发》有比较详细的描述。Provider引用了字符串域中的内容,还有其它部分也在字符串域定义了相关内容,所以这里的字符串域并不是最后的内容,后面还会定义新的内容,读者需要把这些内容组合到一起。此处需要一个设备类的GUID,如果安装了Visual Studio,那么Tools目录下有一个guidgen.exe工具,可用来生成GUID,当然也可以去网站在线生成一个GUID,如http://www.guidgen.com/。

由于我们新添加了自己的设备类HUSTD10,需要在注册表的HKLM\SYSTEM\CurrentControlSet\Control\Class下添加相关注册表项,因此,INX文件要添加的下一个域为设备类安装域:

----------------------------------------------------------------------------------
; 设备类安装域
[ClassInstall32]
Addreg=SampleClassReg  ; 写注册表之类,要写入的内容放在SampleClassReg子域中
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
; SampleClassReg子域
[SampleClassReg]
HKR,,,0,%ClassName%  ; 设备类的名称,由字符串域的ClassName决定
HKR,,Icon,,-5  ; 设备管理器中显示的图标,使用5号系统图标; 字符串域
[Strings]
ClassName=”HUST Sample Device”  ; 设备类的名称
----------------------------------------------------------------------------------

搞定了设备类之后,我们需要给我们的设备指定厂商、产品和设备本身的相关信息,先指定厂商域:

----------------------------------------------------------------------------------
; 厂商域
[Manufacturer]
%MfgName%=Standard,NT$ARCH$  ; 厂商名由字串串域MfgName决定,产品域有两个,
;一个为Standard,一个为Standard.NT$ARCH$(带版本信息); 字符串域
[Strings]
MfgName=”HUSTD10”  ; 厂商名称
----------------------------------------------------------------------------------

下面是两个产品域:

----------------------------------------------------------------------------------
; Standard产品域
[Standard]
%HelloWorld.DeviceDesc%=HelloWorld_Device, root\HelloWorld  ; 设备描述(可以等同理
;解为设备名)由字符串域中的HelloWorld.DeviceDesc指定,设备安装域为HelloWorld_Device,
;硬件ID为root\HelloWorld; Standard.NT$ARCH$产品域
[Standard.NT$ARCH$]
%HelloWorld.DeviceDesc%=HelloWorld_Device, root\HelloWorld  ; 同上; 字符串域
[Strings]
HelloWorld.DeviceDesc=”HelloWorld”  ; 设备描述(等同理解为设备名)
----------------------------------------------------------------------------------

写了厂商域和产品域之后,接下来就是设备本身的信息了,也就是设备域,或者设备安装域,是很重要的一个部分,也被称作DDInstall域。

----------------------------------------------------------------------------------
; 设备安装域
[HelloWorld_Device.NT]
CopyFiles=Drivers_Dir  ; 需要拷贝的文件列表,由Drivers_Dir子域指定,一般这个文件就
;是我们编写的驱动文件.sys了; Drivers_Dir子域
[Drivers_Dir]
HelloWorld.sys  ; 我们编写的驱动文件
----------------------------------------------------------------------------------

有人会注意到前面产品域中定义的设备域为HelloWorld_Device,为什么后面却变成了HelloWorld_Device.NT?实际上我们不仅仅只有HelloWorld_Device.NT,还可以定义HelloWorld_Device.NTx86,HelloWorld_Device.NTIA64,或者HelloWorld_Device.NTAMD64域,它们表示不同的硬件平台版本,当然也可以不写版本,就写HelloWorld_Device,这与HelloWorld_Device.NT是相同的,表示与硬件平台无关。

下面定义设备安装域一个重要的子域——服务子域,用来向系统注册服务,为设备的运行提供支持。

----------------------------------------------------------------------------------
; 设备域服务子域
[HelloWorld_Device.NT.Services]
AddService=HelloWorld, %SPSVCINST_ASSOCSERVICE%, HelloWorld_Service_Inst  ; 添加服务
;指令,添加的服务名为HelloWorld,%SPSVCINST_ASSOCSERVICE%为一个标记,指定如何增加服
;务,在Setupapi.h中定义,HelloWorld_Service_Inst为服务安装子域; 字符串域
[Strings]
SPSVCINST_ASSOCSERVICE = 0x00000002  ; 服务安装标记
----------------------------------------------------------------------------------

下面是服务安装子域:

----------------------------------------------------------------------------------
; HelloWorld_Service_Inst服务安装子域
[HelloWorld_Service_Inst]
DisplayName = %HelloWorld.SVCDESC%  ; 服务显示名称,与服务名不同
ServiceType = 1  ; 服务类型SERVICE_KERNEL_DRIVER
StartType = 3  ; 启动类型SERVICE_DEMAND_START
ErrorControl = 1  ; 错误控制级别SERVICE_ERROR_NORMAL
ServiceBinary = %12%\HelloWorld.sys  ; 镜像文件路径,即.sys文件路径; 字符串域
[Strings]
HelloWorld.SVCDESC = “Hello World Service”  ; 服务显示名称
----------------------------------------------------------------------------------

接下来是设备安装域另一个重要的子域——辅助安装器子域(CoInstallers),该子域的作用是把一个或多个DLL文件记录到注册表中,并把它们拷贝到相关目录下。

----------------------------------------------------------------------------------
;设备域辅助安装器子域
[HelloWorld_Device.NT.CoInstallers]
AddReg = HelloWorld_Device_CoInstaller_AddReg  ; 写注册表子域
CopyFiles = HelloWorld_Device_CoInstaller_CopyFiles  ; 拷贝文件列表; 写注册表子域
[HelloWorld_Device_CoInstaller_AddReg]
HKR,,CoInstallers32,0x00010000,"WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll,WdfCo-
Installer"  ; 注册表项为CoInstallers32,WdfCoInstaller为入口函数; 拷贝文件列表
[HelloWorld_Device_CoInstaller_CopyFiles]
WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll  ; 拷贝对应版本的.dll
----------------------------------------------------------------------------------

接着是Wdf域,安装框架的辅助安装程序之后,框架将在安装驱动程序时读取这部分。

----------------------------------------------------------------------------------
; Wdf域
[HelloWorld_Device.NT.Wdf]
KmdfService = HelloWorld, HelloWorld_wdfsect  ; HelloWorld为操作系统分配给驱动程序
;的内核模式服务的名称,HelloWorld_wdfsect是一个子域; HelloWorld_wdfsect子域
[HelloWorld_wdfsect]
KmdfLibraryVersion = $KMDFVERSION$  ; KMDF库的版本号
----------------------------------------------------------------------------------

由于INX文件中包含了文件拷贝指令CopyFiles,需要几个额外的域来指定文件位置,它们是源磁盘域([SourceDisksNames])、源文件域([SourceDisksFiles])和目的域([DestinationDirs])。

----------------------------------------------------------------------------------
; 源磁盘域
[SourceDisksNames]
1 = %DiskId1%,,,””  ; 只有一个磁盘,ID为1,磁盘描述为字符串域中的DiskId1; 源文件域
[SourceDisksFiles]
; 本INX文件CopyFiles涉及两个文件的拷贝,HelloWorld.sys和辅助安装器WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll,分别指定它们
HelloWorld.sys = 1  ; 1号磁盘
WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll = 1  ; 1号磁盘; 目的域
[DestinationDirs]
DefaultDestDir = 12  ; 默认目录,12是Windows定义的,等同于%SystemRoot%\system32\drivers
HelloWorld_Device_CoInstaller_CopyFiles  = 11  ; 除了默认目录外,还有一个CopyFiles子域,
;此子域不使用默认目录,定义为11,等同于%SystemRoot%\system32。其它没有特殊定义的
;CopyFiles子域则使用默认目录; 字符串域
[Strings]
DiskId1 = “HUSTD10 HelloWorld Disk #1”  ; 1号磁盘描述
----------------------------------------------------------------------------------

好了,到此INX文件就写好了,下面给出一个完整版本的INX文件:

----------------------------------------------------------------------------------
; 完整的Hello Word驱动INX文件
; 参考WDK示例 echo.inx;******************************************************************************
; 版本部分
;******************************************************************************; 版本域
[Version]
Signature="$WINDOWS NT$"  ; 固定
Class=HUSTSample  ; 我们的驱动不属于已有的设备类,定义一个新的设备类
ClassGuid={FDA3877E-5FF3-4c18-8235-7FEA16EE04E2}  ; 设备类的GUID
Provider=%ProviderName%  ; 驱动的作者,由字符串域中的ProviderName指定
DriverVer=01/12/2016,21.12.36.570  ; 驱动版本;******************************************************************************
; 设备类部分
;******************************************************************************; 设备类安装域
[ClassInstall32]
Addreg=SampleClassReg  ; 写注册表之类,要写入的内容放在SampleClassReg子域中; SampleClassReg子域
[SampleClassReg]
HKR,,,0,%ClassName%  ; 设备类的名称,由字符串域的ClassName决定
HKR,,Icon,,-5  ; 设备管理器中显示的图标,使用5号系统图标;******************************************************************************
; 产商及设备安装部分
;******************************************************************************; 厂商域
[Manufacturer]
%MfgName%=Standard,NT$ARCH$  ; 厂商名由字串串域MfgName决定,产品域有两个,一个
;为Standard,一个为Standard.NT$ARCH$(带版本信息); Standard产品域
[Standard]
%HelloWorld.DeviceDesc%=HelloWorld_Device, root\HelloWorld  ; 设备描述(可以等
;同理解为设备名)由字符串域中的HelloWorld.DeviceDesc指定,设备安装域为HelloWo-
;rld_Device,硬件ID为root\HelloWorld; Standard.NT$ARCH$产品域
[Standard.NT$ARCH$]
%HelloWorld.DeviceDesc%=HelloWorld_Device, root\HelloWorld  ; 同上; 设备安装域
[HelloWorld_Device.NT]
CopyFiles=Drivers_Dir  ; 需要拷贝的文件列表,由Drivers_Dir子域指定,一般这个文件
;就是我们编写的驱动文件.sys了; Drivers_Dir子域
[Drivers_Dir]
HelloWorld.sys  ; 我们编写的驱动文件; 设备域服务子域
[HelloWorld_Device.NT.Services]
AddService=HelloWorld, %SPSVCINST_ASSOCSERVICE%, HelloWorld_Service_Inst  ; 添加
;服务指令,添加的服务名为HelloWorld,%SPSVCINST_ASSOCSERVICE%为一个标记,指定如何
;增加服务,在Setupapi.h中定义,HelloWorld_Service_Inst为服务安装子域; HelloWorld_Service_Inst服务安装子域
[HelloWorld_Service_Inst]
DisplayName = %HelloWorld.SVCDESC%  ; 服务显示名称,与服务名不同
ServiceType = 1  ; 服务类型SERVICE_KERNEL_DRIVER
StartType = 3  ; 启动类型SERVICE_DEMAND_START
ErrorControl = 1  ; 错误控制级别SERVICE_ERROR_NORMAL
ServiceBinary = %12%\HelloWorld.sys  ; 镜像文件路径,即.sys文件路径;******************************************************************************
; 辅助安装器部分
;******************************************************************************;设备域辅助安装器子域
[HelloWorld_Device.NT.CoInstallers]
AddReg = HelloWorld_Device_CoInstaller_AddReg  ; 写注册表子域
CopyFiles = HelloWorld_Device_CoInstaller_CopyFiles  ; 拷贝文件列表; 写注册表子域
[HelloWorld_Device_CoInstaller_AddReg]
HKR,,CoInstallers32,0x00010000,"WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll,
WdfCoInstalle-r"  ; 注册表项为CoInstallers32,WdfCoInstaller为入口函数; 拷贝文件列表
[HelloWorld_Device_CoInstaller_CopyFiles]
WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll  ; 拷贝对应版本的.dll; Wdf域
[HelloWorld_Device.NT.Wdf]
KmdfService = HelloWorld, HelloWorld_wdfsect  ; HelloWorld为操作系统分配给驱动
;程序的内核模式服务的名称,HelloWorld_wdfsect是一个子域; HelloWorld_wdfsect子域
[HelloWorld_wdfsect]
KmdfLibraryVersion = $KMDFVERSION$  ; KMDF库的版本号;******************************************************************************
; 文件域部分
;******************************************************************************; 源磁盘域
[SourceDisksNames]
1 = %DiskId1%,,,””  ; 只有一个磁盘,ID为1,磁盘描述为字符串域中的DiskId1; 源文件域
[SourceDisksFiles]
; 本INX文件CopyFiles涉及两个文件的拷贝,HelloWorld.sys和辅助安装器WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll,分别指定它们
HelloWorld.sys = 1  ; 1号磁盘
WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll = 1  ; 1号磁盘; 目的域
[DestinationDirs]
DefaultDestDir = 12  ; 默认目录,12是Windows定义的,等同于%SystemRoot%\system32\
;drivers
HelloWorld_Device_CoInstaller_CopyFiles  = 11  ; 除了默认目录外,还有一个Copy-
;Files子域,此子域不使用默认目录,定义为11,等同于%SystemRoot%\system32。其它没
;有特殊定义的CopyFiles子域则使用默认目录;******************************************************************************
; 字符串部分
;******************************************************************************; 字符串域
[Strings]
ProviderName="HUSTD10"  ; 驱动作者
ClassName="HUST Sample Device"  ; 设备类的名称
MfgName="HUSTD10"  ; 厂商名称
HelloWorld.DeviceDesc="HelloWorld"  ; 设备描述(等同理解为设备名)
SPSVCINST_ASSOCSERVICE = 0x00000002  ; 服务安装标记
HelloWorld.SVCDESC = "Hello World Service"  ; 服务显示名称
DiskId1 = "HUSTD10 HelloWorld Disk #1"  ; 1号磁盘描述
----------------------------------------------------------------------------------

该INX文件相对于最终的INF文件,多了表示版本的几个字符串变量,它们分别是: ARCH ARCH, KMDFCOINSTALLERVERSION KMDFCOINSTALLERVERSION和 KMDFVERSION KMDFVERSION。 ARCH ARCH表示硬件平台,使用x86编译环境, ARCH ARCH 会被替换为“x86”, KMDFCOINSTALLERVERSION KMDFCOINSTALLERVERSION表示辅助安装器(Co-installer)的版本, KMDFVERSION KMDFVERSION表示KMDF库的版本。它们都会在生成INF文件时被替换,读者可以自行比较。

INX文件生成INF文件是通过WDK中bin目录下的StampInf.exe工具完成的,后面我们通过批处理命令在makefile中调用它。现在,我们先手动运行它来看看。进入WDK编译环境,切换到HelloWorld.inx目录下,输入stampinf -?查看帮助。

Stampinf直接修改文件,因此我们把HelloWorld.inx复制一份,命名为HelloWorld.inf,输入命令: stampinf -f HelloWorld.inf -a x86 -k 1.9,命令表示x86硬件平台,KMDF主版本号为1,次版本号为9。Stampinf还可以给DriverVer打上时间戳。

可以使用另一个工具来检查生成的inf文件的语法,这个工具为ChkInf,位于tools\Chkinf目录下,是一个脚本。切换到tools\Chkinf目录下,输入:chkinf D:\HUSTD10\HelloWorld.inf /O D:\Temp 命令,chkinf会将结果放到d:\temp目录下,用浏览器查看即可。

好了,Inf文件就说这么多,接下来还有一个文件要编写,即sources文件,这个文件比较简单,就不一步一步写了。

----------------------------------------------------------------------------------
TARGETNAME=HelloWorld  # 编译后.sys文件名
TARGETTYPE=DRIVER  # 编译类型为驱动KMDF_VERSION_MAJOR=1   # KMDF主版本INF_NAME=HelloWorld  # 通过 INX生成的INF文件名,这个变量在makefile.inc中被引用
INCLUDES=$(INCLUDES);..\..\inc  #头文件目录
NTTARGETFILE0=$(OBJ_PATH)\$(O)\$(INF_NAME).inf
PASS0_BINPLACE=$(NTTARGETFILE0)  #通过INX生成的INF文件路径C_DEFINES= $(C_DEFINES)  #一些编译器的开关SOURCES=driver.c  # 源代码文件列表----------------------------------------------------------------------------------

现在,编译所需的文件就都准备好了,打开编译环境,进入项目目录,输入build命令编译,编译生成了HelloWorld.sys,HelloWorld.inf以及用于调试的符号文件。下面我们来安装驱动。

6 安装Hello World驱动

由于HelloWorld驱动为纯软件驱动,所以驱动不能像我们插上U盘那样自动安装,因为即插即用管理器检测不到新的设备。而且驱动程序没有测试签名,只能用管理员身份,以批准驱动的安装。

(1)准备驱动包。把前面编译生成的HelloWorld.sys和HelloWorld.inf拷贝出来,然后把WDK中redist\wdf\x86目录下的辅助安装dll拷贝一份,里面有调试版本(checked)和Release版本两个版本的库。我们使用的调试版本编译的,所以把调试版本也带上。这样,驱动包内容有:


图6.1

把它拷贝到虚拟机WinXP中。

(2)在控制面板中找到“添加硬件”向导,单击“是,我已经连接了此硬件”,然后在列表底部选择“添加新的硬件设备”,单击“安装我手动从列表选择的硬件”,在顶部选择“显示所有设备”,单击“从磁盘安装”,选择HelloWorld.inf文件(或者直接输入目录路径),点击“确定”,然后型号列表中显示“HelloWorld”,并且下面提示“这个驱动程序没有经过数字签署”,点击“下一步”,继续“下一步”,出现如下对话框:


图6.2

提示需要HelloWorld.sys,点击“浏览”选择该文件,然后确定。等待安装完成,如图6.2。这时候在设备管理器中查看,多了一个”HUST Sample Device”设备类,下面有一个HelloWorld设备,如图6.3。


图6.3

读者可以右键点击该设备,把属性和INF中相关内容比对一下。同样,读者也应该自己去比对一下注册表的相关条目。驱动的卸载也在设备管理器中完成。


图6.4

7 调试Hello World驱动

内核中输出的内容需要一些工具才能看到,这些工具有好几个。开发程序调试是必须的,因此我们安装调试器WinDbg。它不仅可以看到内核输出,还可以调试驱动程序,尤其联机调试很方便。

WinDbg搭配虚拟机环境的搭建在谭文、杨潇和邵坚磊编著的《寒江独钓——Windows内核安全编程》中的第1章有详细地描述,这里就不再赘述了。

还记得我们在HelloWorld驱动中打印出来的两条信息吗?在设备安装的回调函数中我们打印了”Hello World!”,设备卸载的回调函数中打印了”Thanks For Using!”。现在我们在WinDbg中来看看。双机调试连接上虚拟机,然后在设备管理器中右键点击HelloWorld设备,选择“停用”,弹出的警告框选择“是”,这时候在WinDbg中打印出了”Thanks For Using!”,如图7.1。然后在设备管理器中右键点击HelloWorld设备,选择“启用”,这时候在WinDbg中又打印出了”Hello World!”,如图7.2。


图7.1


图7.2

为了调试HelloWorld驱动,我们需要在WinDbg中设置系统符号表路径和HelloWorld符号文件路径。网络顺畅的情况下,内核符号表我们设置微软符号服务器就行了,WinDbg会自己从服务器下载。如果网络不顺,那你需要把内核符号表下载到本地,这时候要注意版本。
例如我编译的是XP SP3的驱动,那么我需要下载XP的符号表,而不是Win7的。

我们使用符号服务器,在C盘建立一个本地缓存文件夹symbols,然后在WinDbg的Symbol File Path中添加srv*c:\symbols*http://msdl.microsoft.com/download/symbols。HelloWorld的符号文件就是pdb文件,和编写应用程序exe生成的类似。同样,添加其路径,用分号分隔,如图7.3。


图7.3

此时,如果WinDbg在BUSY的话,用Ctrl+Break使其断下,可以看到其加载内核符号的信息,如图7.4。此时,C:\symbols目录下已经有了pdb文件的缓存。


图7.4

为了调试HelloWorld,我们在DriverEntry入口函数中手动加入一个断点指令int 3,这部分内容同样在《寒江独钓——Windows内核安全编程》有比较详细的描述,包括后面的单步调试执行等等,我就不写了。更改代码后,重新编译驱动包,然后在设备管理器中选择HelloWorld设备,右键点击选择“更新设备驱动程序”即可,不需要卸载之后重装。调试操作后面的教程中还会涉及到,读者先跟着《寒江独钓——Windows内核安全编程》做就行了。

8 总结

好了,到此为止,又臭又长的HelloWorld就写完了。虽然很长,却也没扯什么题外话,看起来比较乏味无聊。能力有限,反正看这个东西的人在乎的是内容,而不是好不好玩。毕竟没有多少人愿意学Windows内核。总结一下本节内容,基本上都是在做准备工作,没有什么编程的东西。但是不要小看准备工作,一方面准备工作出错,驱动跑不起来会浪费大量时间,另一方面,从准备工作中也可以学到不少东西。代码相关的所有东西我都打包放到github上,读者可以自己去下载,地址为https://github.com/hustd10/Windows-Driver。

我写出来的操作都是在XP下的,Win7大同小异。文中我省略给出相关文档资料的部分,读者如果不熟悉相关内容,自己一定要找来看看,把每一部分弄懂才能把知识串起来。另外,实践出真知,我尽量写得详细一些,不是让大家看完作罢,而是让大家能够像我一样实际运行起来。如果读者发现本文中的错误,请发邮件告知,邮箱1530904175@qq.com。我不是专业的驱动开发人员,写这个教程的时候我自己也在学,错误难免,而且很多内容都是“借鉴”过来的,望大家指正和谅解,谢谢你的阅读。

PS:之前用Word写,今天改用Markdown编辑器,本来是准备写完几套之后才发出来的,刚用编辑器,就顺便发出来看看页面编辑效果。涉足Windows内核纯属爱好,与我研究生专业无关,平时较忙,但该教程会持续更新。如果能够解决一些你的疑问,那么我的目的就达到了。

kmdf驱动教程1——从Hello World开始相关推荐

  1. WDF开发USB设备驱动教程(1)

    PDF下载地址(1.2版):链接地址 CY001开发板讨论帖:链接地址 注:本文档新版本已出,请在博客中查找,或下载PDF全文文档. 链接地址WDF开发USB设备驱动教程 by 张佩 文档说明 作者写 ...

  2. Mongodb python驱动教程

    2019独角兽企业重金招聘Python工程师标准>>> Mongodb python驱动教程 安装 使用python驱动mongodb需要下载.安装PyMongo包 Windows用 ...

  3. 《Android开发案例驱动教程》

    <Android开发案例驱动教程> 作者:关东升,赵志荣 Java或C++程序员转变成为Android程序员 采用案例驱动模式展开讲解知识点,即介绍案例->案例涉及技术->展开 ...

  4. 计算机无线网怎么安装教程,全民wifi驱动怎么安装_电脑安装全民wifi驱动教程

    全民wifi是许多用户都喜欢安装的一款免费wifi共享软件,在使用之前要先安装全民wifi驱动才可以正常使用,但是许多用户可能还不知道全民wifi驱动怎么安装,所以,本文就给大家演示一下电脑安装全民w ...

  5. c语言程序设计工作任务,C语言程序设计任务驱动教程

    <高等院校计算机任务驱动教改教材:C语言程序设计任务驱动教程>强调动脑.动手,强调"做中学.做中会".每个教学单元的语法知识条理化,程序编写渐进化,通过"知识 ...

  6. 小米5s+刷+android+8.0,【小米5S标准版 解账户锁线刷包】MIUI V8.0.10.0 刷机 工具+驱动+教程!紫火提供版...

    [小米5S标准全网 解账户锁线刷包]含刷机工具+驱动+教程!机客盟提供版,基于官方MIUI V8.0.10.0.MAGCNDH适用于2015711版纯净,稳定,流畅,省电版 刷机包里面包含 刷机工具 ...

  7. 黑苹果核心显卡clover驱动教程

    文章目录[隐藏] 根据下面的1-7步骤进行清理残留 添加 Lilu + WhateverGreen 驱动 获取 IGPU 的设备路径(这一步我没有操作也成功驱动核显了) ig-platform-id ...

  8. alc236黑苹果驱动_黑苹果核心显卡驱动教程

    大家在黑苹果安装完后经常出现核显没有驱动上,表现为查看显存只有6M.7M之类,会有卡顿,浏览器新建标签页会花屏等现象. 开始之前请注意你的显示器接口以及是DVI.HDMI.DP之类的高清接口,使用VG ...

  9. 鲁大师linux系统打印机驱动怎么安装,鲁大师怎么安装打印机驱动?鲁大师安装打印机驱动教程...

    鲁大师是一款多功能系统工具,它不但可以帮我们检测电脑可以用来安装各种驱动,包括打印机驱动.关于鲁大师怎么安装打印机驱动估计还有很多人不清楚,接下来就让小编给大家详细的说一说鲁大师安装打印机驱动的方法. ...

最新文章

  1. [算法] [常微分方程] [欧拉法 改进欧拉法 经典R-K算法]
  2. minheight能继承吗_借父母名买房到底归谁?其他兄妹能继承吗?
  3. 原版销售累计超过150 000册的经典JavaScript入门书
  4. 安装Windows 64 位 mysql 最新版本解压包中没有data目录和my-default.ini及服务无法启动的快速解决办法...
  5. python能print中文吗_win10中文版,python的print不能打印中文字符?
  6. wxWidgets:可用类概述
  7. 张平文院士:展示计算数学的魅力
  8. mysql convert报错_部署mysql版本项目问题记录
  9. 那年学过的Java笔记一SE基础
  10. Oracle数据库创建表空间
  11. 作词家下岗系列:教你用 AI 做一个写歌词的软件!
  12. 五阶段--使用 Kibana 操作 ES/ 搜索
  13. Java Swing 课程设计 ---- 实验室设备管理系统
  14. id在python中是什么意思_Python中的id函数是什么意思
  15. EOF和BOF的区别
  16. inhibit_all_warnings! 忽略cocoapods警告 添加后编译失效
  17. WPS文档:格式显示,页码标注,公式居中编号右对齐,公式编号不能在行中间显示
  18. springboot+vue酒店电子商务平台
  19. 《炬丰科技-半导体工艺》利用microLED显示技术缓解芯片间通信瓶颈
  20. Windows系列系统 修改键盘默认对应键值(修改ctrl与fn位置, 解决键盘重要键损坏问题)

热门文章

  1. oracle 汉字正则表达式,在Oracle中使用正则表达式
  2. 关于ts的一些泛型关键字用法
  3. Python标准库之Turtle
  4. STM32 温度采集及WIFI电路设计
  5. 详解大端模式和小端模式【转】
  6. 软件工程项目实训05
  7. 如何把Geohash的值解码成经度纬度?
  8. Gazebo下多机器人协同控制
  9. 花旗银行的ATM机的易用点
  10. SSRF攻击Redis写入webshell