41
1 DriverStudio 工具包介绍 DriverStudio 是一套用来简化微软 Windows 平台下设备驱动程序的开发 调试和测试的 工具包 DriverStudio 当前的版本包括下列工具模块 DriverAgent DriverAgent Win32 应用程序提供直接访问硬件的功能 即使你没有任何设备 驱动程序开发的经验或经历 你也能编写出 DriverAgent 应用程序来直接访问硬件设备 DriverAgent 应用程序可以运行在 Windows 98, Windows 95, Windows NT Windows 2000 平台上 (当前版本不支持 Windows XP 平台 ) VToolsD VToolsD 是一个用来开发针对 Win9X (Windows 95 Windows 98)操作系统下设 备驱动程序 (VxD) 的工具 VToolsD 中包括生成驱动程序源代码的工具 run-time interface 以及一些驱动程序样本 可以用来作为各种类型的设备驱动程序的基础部分 DriverWorks DriverWorks 对于 Windows NT 下和 Windows 98 Windows 2000 共同支持的 Win32 驱动模型(WDM)设备驱动程序的开发提供完全的支持 DriverWorks 中包含一个非 常完善的源代码生成工具(DriverWizard) 以及相应的类库和驱动程序样本 它提供了在 C++ 下进行设备驱动程序开发的支持 DriverNetworks DriverNetworks 是针对 Windows 网络驱动开发人员的一个模块 在它的核心部分 DriverNetworks 是一个针对 NDIS drivers TDI clients (DriverSockets)C++ 的类库 DriverNetworks 中也有 Quick Miniport Wizard 用来直接开始一个 NDIS Miniport Intermediate Driver 工程 它可以让你快速的生成所有采用 DriverNetworks C++ 类库编写 NDIS 驱动程序的编译 安装和调试所需要的文件 SoftICE SoftICE 是一个功能极其强大的内核模式调试器 它支持在配置一台单独的计算 机或两台计算机下进行设备驱动程序的调试 BoundsChecker Driver Edition BoundsChecker 驱动程序版采用了 NuMega 针对应用程序开发的一种创新的错误检测 技术 BoundsChecker 驱动程序版中提供了参数确认和系统实施可以用来监视和跟踪在不 同的设备驱动程序与其它的操作系统的组件之间的所有的交互作用 配置向导提供了对于 BoundsChecker 采集到的各种类型的大量信息的精确的控制 BoundsChecker 驱动程序版当前支持 Windows NT, Windows 2000, Windows Millennium Edition Windows 98 (当前版本不支持 Windows XP) DriverWorkbench DriverWorkbench 可以使你进一步了解系统崩溃时的 dump 文件和当时的事件 由于与 BoundsChecker Driver Edition 集成在一起, DriverWorkbench 中可以看到在系统 崩溃前或其登录的时候由 BoundsChecker 采集到的数据 这种独特的查看系统的方式提供 了空前的能力 使得可以通过查看在系统崩溃之前系统实际活动的踪迹来发现问题之所在

DriverStudio 工具包介绍 - ourDEV.CN 网页不存在

Embed Size (px)

Citation preview

1

DriverStudio工具包介绍

DriverStudio 是一套用来简化微软 Windows 平台下设备驱动程序的开发 调试和测试的

工具包

DriverStudio 当前的版本包括下列工具模块DriverAgent DriverAgent 为 Win32 应用程序提供直接访问硬件的功能 即使你没有任何设备

驱动程序开发的经验或经历 你也能编写出 DriverAgent 应用程序来直接访问硬件设备DriverAgent 应用程序可以运行在 Windows 98, Windows 95, Windows NT 和 Windows2000平台上 (当前版本不支持Windows XP平台 )

VToolsD VToolsD 是一个用来开发针对Win9X (Windows 95 和 Windows 98)操作系统下设

备驱动程序 (VxD)的工具 VToolsD 中包括生成驱动程序源代码的工具 run-time 和interface 库 以及一些驱动程序样本 可以用来作为各种类型的设备驱动程序的基础部分

DriverWorks DriverWorks 对于 Windows NT 下和 Windows 98 与 Windows 2000 共同支持的

Win32 驱动模型(WDM)设备驱动程序的开发提供完全的支持 DriverWorks 中包含一个非常完善的源代码生成工具(DriverWizard) 以及相应的类库和驱动程序样本 它提供了在 C++下进行设备驱动程序开发的支持

DriverNetworks DriverNetworks 是针对Windows网络驱动开发人员的一个模块 在它的核心部分

DriverNetworks 是一个针对 NDIS drivers 和 TDI clients (DriverSockets)的 C++ 的类库DriverNetworks 中也有 Quick Miniport Wizard 用来直接开始一个 NDIS Miniport 或Intermediate Driver 工程 它可以让你快速的生成所有采用 DriverNetworks C++ 类库编写的 NDIS驱动程序的编译 安装和调试所需要的文件

SoftICE SoftICE 是一个功能极其强大的内核模式调试器 它支持在配置一台单独的计算

机或两台计算机下进行设备驱动程序的调试

BoundsChecker Driver EditionBoundsChecker 驱动程序版采用了 NuMega 针对应用程序开发的一种创新的错误检测

技术 BoundsChecker 驱动程序版中提供了参数确认和系统实施可以用来监视和跟踪在不同的设备驱动程序与其它的操作系统的组件之间的所有的交互作用 配置向导提供了对于

BoundsChecker 采集到的各种类型的大量信息的精确的控制BoundsChecker 驱动程序版当前支持Windows NT, Windows 2000, Windows Millennium

Edition和 Windows 98 (当前版本不支持Windows XP)DriverWorkbench DriverWorkbench 可以使你进一步了解系统崩溃时的 dump 文件和当时的事件

由于与 BoundsChecker Driver Edition 集成在一起, 在 DriverWorkbench 中可以看到在系统崩溃前或其登录的时候由 BoundsChecker 采集到的数据 这种独特的查看系统的方式提供

了空前的能力 使得可以通过查看在系统崩溃之前系统实际活动的踪迹来发现问题之所在

2

DriverWorkbench 中也包含了 TrueTime Driver Edition. TrueTime Driver Edition 是一个性能分析工具 可让 Windows NT 的设备驱动开发人员发现并修正驱动程序的性能瓶颈TrueTime Driver Edition 是用来满足对于设备驱动程序和内核模式下代码开发人员的需求一些额外的功能将出现在 TrueTime Driver Edition 的下一个版本中

DriverWorkbench 当前支持 Windows NT 和 Windows 9x. (当前版本不支持 WindowsXP)

FieldAgent FieldAgent 可让你对运行在客户的计算机上的驱动程序进行处理 由 FieldAgent

收集到的信息可用于追捕到那些可能只发生在特定的配置环境下问题的原因

FieldAgent 当前支持 Windows NT 和 Windows 9x. (当前版本不支持Windows XP)TrueCoverage Driver Edition 用来确保对于所有的代码都已经完全被测试的方法是使用代码覆盖率工具

TrueCoverage 可以帮助你检测代码中的哪些部分已经被测试 哪些部分还没有被测试 通

过在开发过程中测量和跟踪代码的执行情况和稳定程度 可以节约测试时间并提高代码的

可靠性 TrueCoverage 能够很轻松的收集驱动程序的覆盖率数据 在源代码的上下文中查

看这些数据 也可以将多次会话的结果合并以累加覆盖率的数据

TrueCoverage Driver Edition 当前支持 Windows NT 4.0 (Service Pack 3 or later) 和Windows 2000. (当前版本不支持Windows XP)使用 DriverWorks 或 VToolsD 来开发设备驱动程序的同时 DriverStudio 中的工具与

Microsoft DDK在一起也能很好的与其配合使用来开发出高质量的驱动程序

3

DriverStudio套件包的安装与运行环境设置

硬件和软件需求

在安装该产品前最好将你的系统进行备分 下列是安装和运行 DriverStudio 所需的要 求 PC-compatible Intel x86 系统 Microsoft Windows NT 4.0 (Service Pack 3 or later), Windows 2000, Whistler Build 2296. (Optional: 其中一些工具也支持 Windows 98 和 Windows 95.) 内存: 最少 32 MB, 推荐使用 64 MB

硬盘:完全安装需要 72 MB 针对 SoftICE的远程调试: NE2000-compatible 网卡或 3Com 网卡 针对 DriverWorks: Microsoft DDK,MS Visual C++

DriverStudio 也支持其它的编译器 当前支持的编译器的详细列表参见

http://www.numega.com/drivercentral/components/compatibility.shtml

安装 DriverWorks1. 在安装 DriverWorks之前 首先要保证你的计算机上安装了 Microsoft Visual C++以

及相应针对Windows NT 或WDM的 DDK2. 在安装完成后 启动 MSVC 5.0 或 6.0 启动 MSVC 最好的方法是使用 SetDDKGo

工具 位于开始|程序|Numega DriverStudio|Tools 菜单中 Setup DDk and Start MSVCSetDDKGo执行一系列的命令行参数来定义 BASEDIR环境变量

3. 在 FILE|Open Workspace 中打开 DriverStudio\DriverWorks\Source\vdwlibs.dsw 将

VDWLIBS工程载入4. 选择 Build|Batch Build 选中你需要编译的配置

5. 点击 Build编译所选择的库文件注意 库文件只需在安装完成后第一次使用前编译一次即可 以后要使用 DriverWorks

只需通过 SetDDKGo进入MSVC即可

安装 VtoolsD安装程序主要执行以下两个基本的功能

1 将 VtoolsD的组成文件复制到你指定的目录下2 创建配制文件 USER.MAK 用来定义 VtoolsD在系统中使用的符号USER.MAK 是用来定位 VtoolsD 在系统中生成 VxDs 时使用的工具的配置文件 它由

一系列的符号定义列表组成 每一个都告知 NMAKE 各个特定的程序在系统中驻留的位置 SETUP提示你输入工具所需变量的路径正确的 USER.MAK是安装成功的关键 在安装完成后要检查该文件

一些在 USER.MAK 中定义的符号是文件 一些是用来控制生成过程的符号 下列表

格说明了在 USER.MAK中定义符号的含义 Symbol MeaningCOMPILER 选择编译器的符号: MS5 或 BCB3DEBUGGER 选择调试器的符号: SOFTICE3, WDEB386, 或 NONEC32 32-bit 命令行编译器的完全路径每个符号定义语句的格式为 SYMBOL =

4

例如:C32 = C:\BCC32\BIN\BCC32.EXELINKMSVC2 MS可执行连接器的完全路径名TLINK32 Borland可执行连接器的完全路径名ASM6 Microsoft MASM 6可执行的完全路径名TASM32 Borland assembler的完全路径名NMSYM NuMega 符号文件生成器的完全路径名EDITBIN Microsoft EDITBIN 工具的完全路径名VTOOLSD 环境变量VTOOLSD 生成系统时需要定义环境变量 VTOOLSD. 该变量的值必须是 VTOOLSD

安装的根目录

例如:set VTOOLSD=C:\VTOOLSDSETUP 自动将该语句加入到你的 AUTOEXEC.BAT 文件中 如果你没有让 SETUP 这

么做 那么你就必须手动的添加或每次用 VTOOLSD在系统中生成 VxD时设置变量

安装 SoftICE安装之前

1. 如果要将 SoftICE 安装在 Windows NT 或 Windows 2000 上 你必须要有系统管理

员的权限

2. 如果要将 SoftICE 安装在 Windows Me 上 在安装之前必须下载并安装"MicrosoftTools for Windows Me"

3. 下列是各个显卡选项的需求列表 根据你自己的配置选择相应的驱动

l 一台显示器和显卡根据显卡的生产厂家和型号来决定

l 第二块单色显卡由主要的 第一台 显卡的生产厂家和型号来决定

l 第二台计算机由串口特性或网络特性 如果使用网络 来决定

l 第二块 VGA显卡找到能与另一块显卡共存的显卡的驱动

4. 选择使用的鼠标的类型 串口或 PS/2 如果使用了串口鼠标 搞清楚其连接的端口

是 COM1还是 COM25. 退出所有的Windows应用程序安装

1. 选择好安装目录2. 在显示适配器选择窗口 选择你将要使用的显示选项

l 若要使用通用显卡驱动 选中"Universal Video Driver"复选框l 若在只有一块显卡的计算机上使用 SoftICE 选择显卡的生产厂家和型号 如果显卡

未列出 在 COMPATIBILIBY 框中选择相同的图形处理芯片 如果相同的芯片也没有 选

择 STANDARD VGA 640X480 pixels .

5

l 若在计算机的第二块单显上使用 SoftICE 选择 DISPLAY SOFTICE ON ATTACHEDMONOCHROME MONITOR 然后选择主显卡的生产厂家和型号 如果显卡未列出 在

COMPATIBILIBY 框中选择相同的图形处理芯片 如果相同的芯片也没有 选择

STANDARD VGA 640X480 pixels .l 若在另外一台计算机上使用 SoftICE 选择主显卡的生产厂家和型号 如果显卡未列

出 在 COMPATIBILIBY 框中选择相同的图形处理芯片 如果相同的芯片也没有 选择

STANDARD VGA 640X480 pixels .l 若在计算机的第二块 VGA 卡上使用 SoftICE 选择 STANDARD VGA 640X480

pixels3. 如果你选好了相配的显卡设置 点击 TEST 来测试显卡的设置 如果测试不成功

参见步骤 74. 如果采用了通用显卡驱动并且测试成功 系统会弹出一个对话框告诉你测试已通

过 如果你测试其它任何显卡 将会有一个彩色的文本模式的屏幕出现大约 5秒钟如果 SoftICE与你的显卡不兼容 测试失败 你看到的是一个黑屏

5. 如果在Windows NT或Windows 2000上安装 按照下列选项决定 SoftICE加载的方式

BOOTSYSTEMAUTOMATICMANUAL6. 选择鼠标的安装方式7. 如果在Windows 95或Windows 98下安装 选择系统配置窗口选项

安装 DriverNetworks

编译库文件和驱动程序

随着 Windows 2000 DDK 的引入 网络驱动开发人员必须在同一台开发机器上对付两

种 DDK环境和两种或更多的目标平台: Windows NT 4 DDK 和 Windows 2000 DDK.对于一个给定的驱动推荐使用下列的 DDKNT 4 DDK 针对 NDIS 4 miniport 驱动 (目标平台: Windows NT 4 ,Windows 95)Windows 2000 DDK 针对 NDIS 5 miniport 驱动 (目标平台: Windows 2000, Windows

98, Windows Me)Windows 2000 DDK 针对 Intermediate Drivers 和 TDI Clients(目标平台: Windows NT

4 , Windows 2000, Windows 98, Windows Me)对于用 DriverNetworks开发驱动程序来说选择正确的环境是非常关键的为驱动程序选择正确的 Build环境DriverNetworks? 鼓励但不要求一定要使用Microsoft Visual Studio 6.0 IDE 作为驱动的

开发环境

DriverStudio 提供两种途径在 Visual Studio IDE中来选择正确的 build 环境1 使用 DriverStudio中自带的 SetDDKgo.exe工具在指定的 DDK环境中启动Microsoft

Visual Studio 6.0 IDE

6

2. 在Microsoft Visual Studio 6.0 IDE的工具条中使用 Set DDK add-on 按钮设置合理的DDK 环境

利用 SetDDKGo.exe 方法,一种使用 -K 命令行参数指定 DDK的路径例如 SetDDKGo -K c:\ddkw2k.如果不使用 -K 参数, SetDDKgo.exe 使用注册表中默认的 DDK路径编译库文件

在使用 DriverNetworks? 编译驱动程序之前 必须先编译 DriverNetworks? 库文件. 在IDE下打开 DriverNetworks? 工作组文件, dnw.dsw. 其中由两个工程组成: KNdisLib.dsp 和Tdiclient.dsp. 该工程中 依次包括针对不同的 NDIS 版本 checked/free 和目标平台的多种配置

下列部分简单描述了编译所需的必须的步骤

DriverNetworks? libraries under Microsoft Visual Studio IDE 6.0. 假设你的开发用机上安装了 NT 4.0 和 Windows 2000 DDK.如果只安装了一种 DDK 跳过相关的步骤

1 启动 Microsoft Visual Studio IDE 6.0 IDE.2 单击 New Workspace 并在$(DRIVERNETWORKS)\source目录中找到 dnw.dsw 打

开该工程.3 在 IDE工具条中单击 DDK 按钮 选择 Windows 2000 DDK 的路径.注意通常会从

Set DDK对话框中提示4 从 IDE Build 菜单中选择 Batch Build.你将会看到在 DriverNetworks 库配置表中的

所有配置列表 选择除了 KNdisLib - Win32 NDIS 4 Miniport Free 和 Checked以外的所有配置.

5 选中 Selection Only 检查框并点击 Build.6 如果也安装了 NT 4.0 DDK 回到步骤 3 指定 DDK 4.0 目录.在第 4 步中只选择

KNdisLib - Win32 NDIS 4 Miniport Free 和 Checked配置并编译

7

DriverStudio开发一个 USB驱动程序的过程

很多写Windows Device Driver的开发人员基本上都是使用Windows DDK进行开发的但是 现在也有不少人都开始借助一些辅助工具 笔者去年开始接触到 DriverStudio 发现

它真的是一个不错的开发工具 不仅写代码的时候思路清晰 而且和 DDK的结合很好当然 也有很多人觉得用DriverStudio不够正宗 或者说不能很好的理解Windows Device

Driver 的架构 我感觉这就有点像 MFC 和 SDK 的关系 关于这个问题在很多地方都有争

论 每个人都有自己的最爱 都有自己的习惯 只要你能把事情做好 我想用什么方法应

该都是一样的 如果你已经习惯了用 DDK 开发 那完全还可以继续用下去 如果你觉得

DriverStudio不错 那尝试用一个可以给你按照 OOP概念来编程的工具有什么不好呢

下面介绍一下用 DriverStudio 开发一个 USB 驱动程序的过程 这个 USB 设备有 3 个双向端点 每个端点的配置如下

EP 类型 地址 buffer(Bytes)0 IN/OUT Control 0x80/0x00 16/161 IN/OUT Bulk 0x81/0x01 16/162 IN/OUT Bulk 0x82/0x02 64/64我们的驱动程序需要实现的功能就是控制设备上的 LED 灯的亮和灭 以及通过 Endpoint 2对设备进行读写

由于 DriveStudio 由几个部分组成 我们写这个驱动程序只要用到 DriverWorks 因此下面

我们就简称它为 DW 在这里 我们假定读者已经正确的安装了 DW 并且已经编译好了

各个库文件

1. 首先 我们通过快捷方式 Setup DDK and Start MSVC 来启动 VC IDE 这个快捷方式

所指向的程序 会进行一些必要的设置 然后再启动 VC IDE 这样我们的程序就可以使

用 DDK和 DW的头文件和库了2. 从 VC IDE 的菜单"DriverStudio"中选择"DriverWizard", 在如图 1 所示的对话框中, 写上项目名称. 在这里, 我们将这个项目称为: TEST, 所在的目录为 D:\TEST. 然后点按钮"Next>".

8

图 13. 在接下来的这个对话框中(如图 2), 我们需要选择驱动程序的类型. 由于 USB 设备驱动程序是WDM类型的, 所以我们选择第二项并且点按钮"Next >".

图 24. 在第 3 个对话框中(如图 3), 选择我们的驱动程序所操作的总线类型. 这里, 我们选择USB. 在USB Vendor ID和USB Product ID中填入USB设备的VID和 PID. 假定我们的USB设备的 VID和 PID分别是 16进制的 0471和 1801. 然后点按钮"Next >". 关于 VID和 PID的规定请参考 USB-IF的规范.

9

图 35. 在接下来的对话框中(如图 4), 我们需要加入Endpoint 1和Endpoint 2的定义. 由于在USB中规定 Endpoint 0是必须存在的, 所以我们不需要对 Endpoint 0进行定义. 点"Add..."按钮,弹出一个如图 5所示的对话框. 我们将它修改成如图 6所示. 其中, 按照 USB的规定, 对于端点, 它的地址是 1; 按照前面说明的设备的特点, Endpoint 1的最大的包大小为 16字节, 因此在"Max Transer Size"中填入 16; Endpoint Name可以通过"Suggest Name"得到. 按照这些原则, 继续设置其他的配置, 以使对话框 4变成如图 7所示. 接下来, 继续按"Next >"按钮.

图 4

10

图 5

图 6

11

图 76. 在如图 8所示的对话框中, 可以填入我们需要的 Driver Class的名字和文件名. 一般我们不需要更改. 继续按"Next >"按钮.

图 87. 在如图 9 所示的对话框中, 因为不需要给其他的驱动程序提供接口, 也不需要提供 Flush功能, 所以不需要任何修改, 直接按"Next >"按钮.

12

图 98. 在如图 10所示的对话框中, 我们选择给端点 2产生 BULK Read的代码, 并且按"Next >"按钮. DW会给我们产生一套对端点 2进行读的代码, 不用修改, 就可以直接使用.

图 109. 在如图 11所示的对话框中, 我们选择给端点 2产生 BULK Write的代码, 并且按"Next >"按钮. 这样, DW也会给我们产生一套对端点 2进行写的代码, 不用修改, 就可以直接使用.

13

图 1110. 对于如图 12的对话框, 我们直接按"Next >"按钮. 这里是设置是否要将 I/O请求排队, 在这里, 我们不需要排队.

图 1211. 在如图 13所示的对话框中, 我们不需要创建任何注册表项, 所以直接按"Next >"按钮.

14

图 1312. 如图 14 所示的对话框, 是让我们设置一些驱动程序的属性, 比如接口, 缓冲区之类的.一般的都可以使用缺省设置. 继续按"Next >"按钮.

图 1413. 在如图 15 所示的对话框中, 是让我们给驱动程序增加一些 IOCTL 接口. 我们只增加一个如图 16所示的 IOCTL来控制 USB设备的 LED灯. 然后按"Next >"按钮.

15

图 15

图 1614. 在最后一个如图 17 所示的对话框中, 可以设置一些驱动程序的属性, 产生一个 console测试程序. 按下"Finish"按钮, 就结束了Wizard.

16

图 17这样, 我们就创建好了一个基本的驱动程序, 下面来看看还要做哪些工作才可以和我们的设备以及上层的应用程序通讯.

把函数 NTSTATUS TESTDevice::TEST_IOCTL_LED_Handler(KIrp I)改成如下面的样子NTSTATUS TESTDevice::TEST_IOCTL_LED_Handler(KIrp I){

NTSTATUS status = STATUS_INVALID_PARAMETER;

t << "Entering TESTDevice::TEST_IOCTL_LED_Handler, " << I << EOL;

__try{

// TODO: Verify that the input parameters are correct// If not, return STATUS_INVALID_PARAMETER

if(I.IoctlOutputBufferSize() || !I.IoctlBuffer() ||(I.IoctlInputBufferSize() != sizeof(UCHAR)))__leave;

// TODO: Handle the the ZBUARD_IOCTL_LED_ON request, or// defer the processing of the IRP (i.e. by queuing) and set// status to STATUS_PENDING.

PURB pUrb = m_Lower.BuildVendorRequest(NULL, // transfer buffer0, // transfer buffer size0, // request reserved bits(UCHAR)(*(PUCHAR)I.IoctlBuffer()),// request. 1 = LED_ON, 0 = LED_OFF

17

0 // Value);

// transmitstatus = m_Lower.SubmitUrb(pUrb, NULL, NULL, 5000L);

}__finally{

// TODO: Assuming that the request was handled here. Set I.Information// to indicate how much data to copy back to the user.

I.Information() = 0;I.Status() = status;

}

return status;}这个函数是控制 LED灯的 它是通过 USB Vendor Request来向设备传送的 其中 request=1的时候表示让 LED亮 request=0的时候让 LED灭 它是通过 DeviceIoControl由上层应用程序传下来

再看看读写部分 经过检查 NTSTATUS TESTDevice::Read(KIrp I)和 NTSTATUSTESTDevice::Write(KIrp I)可以发现 DW 已经给我们写好了读写的代码 我们可以直接使

用了 这些代码就是在上面的第 8和第 9步中产生的代码

最后 修改编译一下 DriverStudio 产生的测试程序 Test_TEST 程序 我们就可以通过命令

行来测试我们的驱动程序了 对于 LED 的控制 我们可以直观的在设备上看到 但对于读

写的操作就需要和 firmware程序配合 这已经超出了本文的范围 不在这里讨论了

通过上面 我们可以看到有了 DriverStudio 就可以快速的产生一个驱动程序 然后在里面

作一些小的改动就可以使用了 即使是写一个比较复杂的 USB 驱动程序 我们也可以不用

管一些系统的 IRP 处理 只要专注于我们自己的特定应用就可以了 而且它把一个驱动程

序概括成几个类的概念 并且 DW还附带有一些很有用的 STL类 在 VC IDE里面有了一个很清晰直观的表示 这样 对一些从上层应用转向驱动程序的开发人员 或者一些对

C++/OOP 很熟悉但不太了解系统内核的开发人员 都比较容易上手 即使对于推崇直接用

DDK 编程的人来说 通过阅读 DriverStudio 附带的源代码 也可以对驱动程序的开发有一

个更加深入的了解

18

用 DriverWorks为 Windows 98 , Windows Me Windows

NT Windows 2000 和 Windows XP 开发驱动程序简介

DriverWorks 通过提供强大并且先进的代码生成向导 DriverWizard 还有库和例子

中成千上万行经过严格测试的代码 简化了设备驱动程序的开发

Windows NT 和 WDM 驱动程序接口为面向对象方法的应用提供了良好的条件DriverWorks 充分利用操作系统面向对象的优良特性 与微软的 DDK 相比 它为设计基

于 Windows NT 和 WDM 驱动程序设计模型 的驱动程序提供了更加友好的方式 使用

Driverworks 设计人员能以更快的速度开发 NT 和 WDM 驱动程序 在大多数情况下

DriverWorks 函数库的使用 可以大大减少驱动程序的代码长度 利用这些精心设计的函

数 你几乎可以完成所有的工作 当然 DDK的 C语言级的 API函数也是可用的

注意 Windows NT 设备驱动程序能运行在 Windows NT 或 Windows 2000 上 而 WDM设备驱动程序能在 Windows 98 , Windows Me Windows 2000 和 Windows XP 上运行

DriverStudio 把那些每个驱动程序都需要的代码都封装成类库 库代码自动地处理例行

的操作 这极大地简小了任务的复杂度

而且 C++编译器提供了 ANSI C 所不具备的优点 包括改进的类型安全检查 内连函

数优化 以及更好的代码组织

DriverWizard——DriverWorks 的代码向导 与微软的 Visual C++ 紧密的集成 超过

1500 行的驱动程序源代码框架只需几次鼠标点击就可完成 这些代码还包含了详细的注

释 另外 DriverWizard 还能生成专为特殊设备定制的代码 比如 USB设备 PCI设备即插即用设备 ISA 设备 等等

DriverStudio对 Microsoft Developer Studio 的支持远不止用 DriverWizard 构造一个新工程这么简单 DriverWorks 提供了完整的和Microsoft Developer Studio 相似的开发环境包括 checked 和 free 编译环境 相似的代码编辑器 错误代码定位 以及类浏览器

编译库

在你利用 DriverWorks 开始工作之前 你必须编译需要的库文件 你可以在 MicrosoftVisual Studio 环境中 或者用命令行方式编译库文件

下面介绍怎样在 VC环境中编译库 1.启动 Visual C++ 2. 选 择 菜 单 File|Open Workspace 打 开 位 于

DriverStudio\DriverWorks\Source\vdwlibs.dsw的工作空间文件 3.选择菜单 Build|Batch Build,在弹出的对话框中选择你想编译的库 4.点击 Build编译你选择的库 怎样用命令行方式编译库 这里省略

19

测试你的安装

如果你想用命令行 不是在Microsoft Visual Studio中 编译一个简单的驱动程序例子

你必须先运行两个批处理文件 后缀为.bat的文件 这两个文件在 DDk和 Visual C++中你也可以简单的通过运行开始菜单 程序 /Development kits/Windows 2000 DDK 中的Checked Build Environment 或 Free Build Enviroment 来完成此操作 第一批处理文件是 SETENV.BAT 并且它要求 2 个参数 第一个是 DDK 的根目录第二个是"free" 或 "checked" 第二个参数告诉编译系统是否在驱动程序中加入调试信息

第二批处理文件是 VCVARS32.BAT 它为编译器建立路径

编译例子程序

对 Windows NT 4.0 来 说 最 简 单 的 例 子 在

DriverStudio\DriverWorks\Examples\NT\HELLO 文件夹里 你可以编译这个例子 然后用

DriverMonitor装载它 对 WDM 驱动程序( 用于Windows 98 , Windows Me, Windows 2000 和 WindowsXP ),最简单的例子在 DriverStudio\DriverWorks\Examples\WDM\HELLOWDM 文件夹里你编译它 然后用控制面板里的添加新硬件向导来加载

如果你有 Microsoft Visual C++ 5.0 或更新的版本 你可以打开工作空间文件

%DRIVERWORKS%\examples\nt\hello\hello.dsw 然后在 Visual C++环境中编译 编译

WDM 驱动程序 请用 HELLOWDM 例子如果你想从命令行编译 请按下面的方法设置工作目录 然后运行 Build程序编译

cd\ProgramFiles\Numega\DriverStudio\DriverWorks\ examples\nt\hellobuild

请查看输出文件 build.log ”(或 buildchk.log ”或 bldfre.log ”)看是否有错误

在Windows 98和Windows Me 中装载驱动程序 首先拷贝 HELLOWDM.INF 文件到系统文件夹 然后打开控制面板 运行添加新硬件

向导 在添加新硬件向导的第一步中 选择从 列表中选择硬件 注意 HELLOWDM 在

其他硬件中 当询问制造商时 选择从磁盘安装 定位到 INF 文件所在的目录 如果你在

安装时运行了 DriverMonitor 你将会看到驱动程序弹出的消息 安装完成后 打开控制面

板中的系统图标 安装的设备会出现在设备管理器中

在Windows NT, Windows 2000, 和 Windows XP中加载驱动程序 对于Windows 2000 和 Windows XP 中的WDM 驱动程序 方法与前面介绍的类似

对于 Windows NT Windows 2000 和 Windows XP 上的非 WDM 驱动程序 运行

DriverMonitor 证实你的驱动程序工作正常 请注意 HELLO.SYS 将被创建在.\I386\*目录此出的*取决于你的编译环境是 checked 还是 free

20

当你从 DriverMonitor 装载 HELLO.SYS 时 你应该在窗口中看到确认信息 然后是

驱动程序发出的 HELLO消息

注意 在 Windows NT 下装载设备驱动程序,你需要以管理员帐户登录如果你没有足够的权限安装内核模式驱动程序 DriverMonitor 会提示错误消息

在从 DriverMonitor 退出前 请卸载驱动程序

用 NuMega DriverStudio进行并行口 EPP模式的

WDM编程

WDM Win32 Driver Model 即Windows驱动程序模型 是Microsoft力推的全新驱动程序模式 旨在通过提供一种灵活的方式来简化驱动程序的开发 在实现对新硬件支持

的基础上减少并降低所必须开发的驱动程序的数量和复杂性 WDM 用于开发 Windows 98和Windows 2000设备驱动程序

WDM驱动程序开发工具通常用 NuMega DriverStudio 因为它可以集成到 VC开发环境中 这样就可以像生成其他工程一样生成驱动程序框架 省去了大量的编写代码

的时间 后面要介绍的功能实现都是在用 VC6.0集成环境生成的驱动程序框架中编写的编写一个设备驱动程序 除了要掌握驱动程序模型之外 还要了解相应设备的硬件知

识 本文要介绍的是并行口 EPP 模式的 WDM 编程 主要介绍并行口 EPP 模式 并用实

际的例子介绍并行口 EPP 模式驱动程序的一些功能实现 如何在驱动程序中实现并行口

EPP 的基本 I/O 如何利用控制码实现上层应用程序对并行口的通讯 如何截获中断和挂

接中断服务例程 如何通过在驱动程序中设置信号来激活上层应用程序定义的事件 以实

现驱动程序对上层应用程序的通讯

一 并行口 EPP模式介绍高级并行接口 EPP可以进行高速的双向数据传输 EPP可以区分两种类型的信息 他

们通常分别

被定义为数据和地址 由于这种接口可以实现快速的方向转换 因此他特别适用于进行较

小数据块的传输 需要经常交换数据传输方向的设备

1 并行口 EPP模式的 I/O地址及其特性定义1) 并行口基地址

378 新系统通用 通常是 LPT1 也可以是 LPT2 通常使用中断 IRQ7278 通常是 LPT2 也可以是 LPT1 LPT3 只能用此基地址 通常使用中断 IRQ5注 1 下面都是针对 378基地址 LPT1介绍

2) 并行口 EPP模式的寄存器表 表 1

21

表 1. 并行口 EPP模式的寄存器

3) 状态寄存器 379 和控制寄存器 37A 的定义 表 2

D7 D6 D5 D4 D3 D2 D1 D0S7 S6 S5 S4 S3 S2 S1 S0

状态寄存器

379WaitBusy Int

Ack 用户定义 引脚 12 13 15Timeout超时

C7 C6 C5 C4 C3 C2 C1 C0

控制寄存器

37A双向控制

1出 0入

中断允许

1允许

地址选通nAStrb0有效

init0有效

数据选通nDStrb0有效

write0有效

表 2. 状态寄存器 379 和控制寄存器 37A 的定义

4) 并行口 25芯引脚信号表 表 3

Pin SPP Signal EPP Signal IN/OUT Status/Control Register1 Strobe Write Out /C0

2-9 Data 0-7 Data 0-7 In-Out

寄存器 地址 读/写数据寄存器 Data [Base+0] 378 读/写状态寄存器 Status [Base+1] 379 读

控制寄存器 Control [Base+2] 37A 写

地址寄存器 Address [Base+3] 37B 读/写扩 展 数 据 寄 存 器

Ext_Data[Base+4] 37C 读/写

未定义 [Base+5] 37D未定义 [Base+6] 37E未定义 [Base+7] 37F

22

10 Ack Interrupt In S611 Busy Wait In /S712 Paper Out / End Spare In S513 Select Spare In S414 Auto Linefeed Data Strobe Out /C315 Error / Fault Spare In S316 Initialize Reset Out /C217 Select Printer Address Strobe Out /C3

18-25 Ground Ground GND Ground表 3. 并行口 25芯引脚信号表.

2. 并行口 EPP模式寄存器的读写1) 先对控制寄存器 Control 初始化如果禁止中断用 out 37A 0x80 ,如果使用中断用 out 37A 0x90

2) 写一个寄存器的两条基本指令out 37B,addr // 将 addr写入用户设备地址寄存器

写 out 37C,data // 将数据 data写入 addr指向的用户设备空间单元读 in 37C // 从 addr指向的用户设备空间单元中读取数据

23

二 并行口 EPP模式驱动程序的功能实现下面是并行口 EPP模式驱动程序接口框图 图 1

图 1. 并行口 EPP模式驱动程序接口框图

1. 并行口 EPP的基本 I/O 首先定义类 KIoRange的一个实例 以对应 EPP

KIoRange m_ParPortIos;status = m_ParPortIos.Initialize( 0x378, // LPT1 Bus address

TRUE, //InCpuIoSpace 8, // Device size TRUE // Map to system space );

下面就可以用类 KIoRange的成员函数来访问 EPP的寄存器了 // EPP的寄存器相对于 EPP基址的偏移量

#define CONTROL 2 //对应 EPP的控制寄存器 37A#define ADDRESS 3 //对应 EPP的地址寄存器 37B#define EDATA 4 //对应 EPP的扩展数据寄存器 37C

//设置控制寄存器 m_ParPortIos.outb(CONTROL 0x80);// EPP读 m_ParPortIos.outb(ADDRESS,addr); //addr是实际编程中你要访问的设备

单元的地址

UCHAR data = m_DeviceIos.inb(EDATA)// EPP写 m_ParPortIos.outb(ADDRESS,addr); //addr是实际编程中要访问的设备单

并行口 EPP驱动程序

上层应用程序

用户设备

ReadFile/WriteFile DeviceIoControl

In / out中

激活应用程序事件

24

元的地址

m_DeviceIos.outb(EDATA,data); //data是实际要写入的数据2. 利用控制码实现上层应用程序对并行口的通讯

在上层应用程序中 要对设备进行访问 首先到创建设备连接对象 通过调

用函数 CreateFile,如下 其中 ParPortDevice0是设备的符号连接名char *sLinkName = "\\\\.\\ParPortDevice0";m_hParPort = CreateFile(sLinkName,

GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

创建成功后就可使用该句柄来对设备进行读写访问,如 ReadFile(m_hParPort,…);

WriteFile(m_hParPort,…);另外还有一个重要的功能就是通过定义一些特殊的控制码来对设备进行特殊

的交互 如

DeviceIoControl(m_hParPort,IOCTLCODE,….); 其中 IOCTLCODE 是在上层应用程序中根据要访问的设备和对设备的访问方式定

义的控制码

驱动程序在 DeviceControl(KIrp I)中作相应的处理 I/O控制码的定义原型如下 #define CTL_CODE( DeviceType, Function, Method, Access )

( \ ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) |

(Method) \)

DeviceType表示设备类型 不同的设备有不同的值 并行口的 DeviceType 值为 0x00000016

Access 表示允许对设备进行的访问 #define FILE_ANY_ACCESS 0

#define FILE_SPECIAL_ACCESS (FILE_ANY_ACCESS)#define FILE_READ_ACCESS ( 0x0001 )#define FILE_WRITE_ACCESS ( 0x0002 )

Method表示对设备访问的方式 #define METHOD_BUFFERED 0

#define METHOD_IN_DIRECT 1#define METHOD_OUT_DIRECT 2#define METHOD_NEITHER 3

Function 用来在 DeviceType,Access,Method 都相同的情况下 区分不同的控

制码

其中 DeviceType,Function,Method,Access 都是 4 字节 16 进制常量 关于这几个常

量的定义请详见 wdm.h 或 ntddk.h DeviceType<<16 表示将常量 DeviceType

25

左移 16位 具体通讯方式如下 首先 在上层应用程序中定义 I/O控制码 #define CPUTESTDEVICE_IOCTL_MEM_READ 0x00160000 0x00160000的含义是允许对并行口进行带有缓冲的任何访问 调 用 函 数 DeviceIoControl(m_hParPort,

CPUTESTDEVICE_IOCTL_MEM_READ, ) 然后 在驱动程序中响应 I/O控制码 #define CPUTESTDEVICE_IOCTL_MEM_READ 0x00160000 在 DeviceControl Kirp I 例程中作如下处理

NTSTATUS ParPortDevice::DeviceControl(KIrp I) { ……

ParPortDevice *pDev = (ParPortDevice *)KDevicePTR(I.DeviceObject()); switch (I.IoctlCode())

{ ……

case CPUTESTDEVICE_IOCTL_MEM_READ: pDev->MemRead(I); //MemRead(KIrp I)为自定义的成员

函数

break; …… } …… }

3. 截获中断和挂接中断服务例程 许多驱动程序都要处理硬件中断 当用户设备要通知上层应用程序读数据时

它可以发一个硬件中断 驱动程序截获此中断 再通过在驱动程序中设置信号来通

知上层应用程序 但在处理硬件中断之前 一定要使硬件的中断允许有效 对于 EPP是使用下面这条语句实现的

m_ParPortIos.outb(Control,0x90); 实现中断的截获和挂接方法如下

a) 定义类 KInterrupt的一个实例 KInterrupt m_TheInterrupt;b) 在设备类中声明一个成员函数 TheIsr作为中断服务例程 ISR

class ParPortDevice : public KPnpDevice{

……

public:MEMBER_ISR (HelloDevice, TheIsr);

……#ifdef _COMMENT_ONLY

26

BOOLEAN TheIsr void ;#endif ……}

c) 在 OnStartDevice 例程中获取包括中断的设备资源并初始化中断和挂接ISR

ParPortDevice::OnStartDevice(KIrp I){……m_ParPortIos.outb(Control,0x90); //置中断允许位PCM_RESOURCE_LIST pResList = I.TranslatedResources(); //获取设备资源//初始化中断并挂接中断服务例程 TheIsrstatus = m_TheInterrupt.InitializeAndConnect(

pResList,LinkTo(TheIsr),this);

……}

4. 通过在驱动程序中设置信号来激活上层应用程序定义的事件驱动程序有时需要主动通知上层应用程序来执行某一个操作 以实现硬件响应

的实时性

这可以通过在上层应用程序中创建一个事件 将该事件句柄放入输入缓冲区中通过

函数 DeviceIoControl 传给驱动程序 并创建一个线程来守候该事件被激活 具体实

现如下

在应用程序中

#define CPUTESTDEVICE_IOCTL_SETUP_SIGNAL 0x00160004声明一个类的成员

public:HANDLE m_hDeviceEvent;

BOOL CApptestDlg::OnInitDialog(){

…… m_hDeviceEvent = ::CreateEvent(NULL,FALSE,FALSE,NULL);

DWORD count;::DeviceIoControl(m_hDevice,CPUTESTDEVICE_IOCTL_SETUP_SIGNAL,

&m_hDeviceEvent,4,NULL,0,&count,NULL);……

}//守候中断的线程UINT ThreadProc( LPVOID pParam ){

27

HANDLE * pm_hEvent = (HANDLE *) pParam;

if (pm_hEvent == NULL ) return 1; // if pm_hEvent is not valid

WaitForSingleObject(*pm_hEvent, INFINITE); …… return 0; // thread completed successfully}

在驱动程序中

#define CPUTESTDEVICE_IOCTL_SETUP_SIGNAL 0x00160004

NTSTATUS ParPortDevice::DeviceControl(KIrp I) { ……

switch (I.IoctlCode()){

…… case CPUTESTDEVICE_IOCTL_SETUP_SIGNAL:

Kevent *m_pEventToSignal; HANDLE hEvent;

hEvent = *(HANDLE*)I.IoctlBuffer(); //从输入缓冲区中得到事件句柄

//驱动程序在非分页内存中分配事件的指针空间m_pEventToSignal = new(NonPagedPool) KEvent(hEvent);status = m_pEventToSignal != NULL?STATUS_SUCCESS :

STATUS_INSUFFICIENT_RESOURCES;m_pEventToSignal->set(); //通过 set成员函数来通知上层应用程序

break; …… }

…… }

28

如何用 DS构造一个简单的 USB过滤驱动程序

一 基本原理

我们知道 WDM 和 KDM 是分层的 在构造设备栈时 IO 管理器可以使一个设备对象附加到另外一个初始驱动程序创建的设备对象上 与初始设备对象相关的驱动程序决定的

IRP,也将被发送到附加的设备对象相关的驱动程序上 这个被附加的驱动程序便是过滤驱

动程序 如右图 过滤驱动可以在设备栈的任何层次中插入 IO 管理器发出的 IRP 将会沿着右图的顺序从上往下传递并返回 因此 我们可以使用过滤驱动程序来检查 修改 完

成它接收到的 IRP 或者构造自己的 IRP 上面这种文字是很枯燥的 好在 前人 已经写过一些范例以供我们更好地理解这些

概念 读过Waltz Oney的 Programming Windows Driver Mode 一书的读者大概都知道WaltzOney 提供的范例中有一个关于 USB 过滤器(第九章)的例子 而在此基础上 USB DesignBy Example (http://www.usb-by-example.com)的作者 John Hyde实现了一个 USB键盘过滤驱动程序 即给此程序增加了一个 拦截(Intercept)”功能来处理 USB键盘的 Report以实现特定的功能 当驱动程序在 IRP_MJ_INTERNAL_DEVICE_CONTROL 设置的完成例程从USB 设备拦截到一个 Get_Report_Descriptor 时 拦截程序将此 Descriptor 中的 USAGE 值从 Keyboard”改为 UserDefined” 再返回给系统

我们可以从这个例子中获得一些灵感 比如 在 Win2k 下 键盘是由 OS 独占访问的 我

们可以通过这种方式使之可以让用户自由访问 我们也可以拦截其他 Report_Descriptor将部分键重新定义 以满足特殊的要求 如果你愿意再做一个用户态的程序 你还可以将

你拦截到的键值传递给你的用户态程序 以实现象联想 实达等国内电脑大厂出品的那些

键盘上的各种实用的功能

二 程序的实现

Waltz Oney 和 John Hyde 的例子已经写得很详细了 读者可以不用修改一个字节便顺利地

编译生成一个过滤驱动程序 本文的目的在于使用 DriverStudio 组件 Driverworks 来实现同样的功能

相信读者读到这篇文章时 已经对 DriverStudio 有了很多的了解 DriverStudio 作为一个以C++为基础的 快速 驱动开发工具 它封装了基本上所有的 DDK的函数 其集成在 VC++中的 DriverWizard 可以很方便地引导你完成设备驱动程序开发的全过程 能根据你的硬

件种类自动生成设备驱动程序源代码 并提供了很多范例程序 当然 这些例子中便包含

一个 USB Filter驱动程序的框架 在不侵犯版权的前提下 充分利用现有共享的 免费的

授权的代码是我们的一贯作法 我们下面便以此范例为基础来作修改

我们的目的是做一个 HID 小驱动程序 hidusb.sys 的 Lower Filter 它附加在 人机接口设

备 通过拦截 USB 的 Get_Report_Descriptor 来修改其返回值 当它发现该 Descriptor的 Usage 为 Keyboard”时 将其改为 UserDefined” 如此我们便可以完全控制这只键盘

具体做法是 拦截 IRP_MJ_INTERNAL_DEVICE_CONTROL 并检查其 IOCTL 代码及URB 如果满足 IOCTRL 功能代码为 IOCTL_INTERNAL_USB_SUBMIT_URB 以及 URB功能代码为 URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE 的条件 即上层驱

29

动发来 Get_Report_Descriptor 请求时 设置一个完成例程 在这个完成例程中 我们将判

断 Usage的值 将 Usage由 6(Keyboard)”时 将其改为 0(UserDefined)”打开 C:\Program Files\NuMega\DriverStudio\DriverWorks\Examples\wdm\usbfilt 目录(具体目录依你的 DriverStudio 所安装的目录不同而不同) 再打开工程文件 usbfilt.dsw 我们先看

一下代码

程序由两个类组成 一个是 Driver类 一个是 Device类 Driver类包括 入口函数 DriverEntry:DECLARE_DRIVER_CLASS(UsbFilterDriver, NULL)/////////////////////////////////////////////////////////////////////// Driver Entry//NTSTATUS UsbFilterDriver::DriverEntry(PUNICODE_STRING RegistryPath){ T << "UsbFilterDriver::DriverEntry\n";

m_Unit = 0; return STATUS_SUCCESS;

// The following macro simply allows compilation at Warning Level 4 // If you reference this parameter in the function simply remove the macro. UNREFERENCED_PARAMETER(RegistryPath);} AddDevice函数NTSTATUS UsbFilterDriver::AddDevice(PDEVICE_OBJECT Pdo){ T << "UsbFilterDriver::AddDevice\n"; UsbFilterDevice * pFilterDevice = new ( static_cast<PCWSTR>(NULL), FILE_DEVICE_UNKNOWN, static_cast<PCWSTR>(NULL), 0, DO_DIRECT_IO ) UsbFilterDevice(Pdo, m_Unit); if (pFilterDevice) { NTSTATUS status = pFilterDevice->ConstructorStatus(); if ( !NT_SUCCESS(status) ) { T << "Failed to construct UsbFilterDevice" << (ULONG) m_Unit << " status = " << status << "\n";

30

delete pFilterDevice; } else { m_Unit++; } return status; } else { T << "Failed to allocate UsbFilterDevice" << (ULONG) m_Unit << "\n"; return STATUS_INSUFFICIENT_RESOURCES; }}

这两段代码基本上和自动生成的代码差不多 AddDevice 的作用是构造一个过滤器的实例

关键的代码在 Device 类 在这个类里 我们把过滤器插入设备栈 并拦截 IRP 用自己的

完成例程来实现特定的功能

Device构造函数UsbFilterDevice::UsbFilterDevice(PDEVICE_OBJECT Pdo, ULONG Unit) : KWdmFilterDevice(Pdo, NULL){ T << "UsbFilterDevice::UsbFilterDevice\n"; // Check constructor status if ( ! NT_SUCCESS(m_ConstructorStatus) ) { return; } // Remember our unit number m_Unit = Unit; // initialize the USB lower device m_Usb.Initialize(this, Pdo); NTSTATUS status = AttachFilter(&m_Usb); //Attach the filter if(!NT_SUCCESS(status)) { m_ConstructorStatus = status; return; } SetFilterPowerPolicy(); SetFilterPnpPolicy();

31

}在 DDK 中 我们用 IoAttachDevice 将设备对象插入设备栈中 DriverStudio 封装了这个函数 在 DriverStudio 中 其他驱动程序需要用 Initialize 来初始化设备对象和接口 对于过

滤驱动 我们关键是需要 Attachfilter将其附加在堆栈中对于大部分如 IRP_MJ_SYSTEM_CONTROL 等 IRP 我们所做的只需用 PassThrough(Irp)将其直接往设备栈下层传递 不需要做任何工作 这些代码我们就不一一列举了 下面的

部分才是本文的关键

我们知道 HIDUSB.SYS是使用内部 IOCTRL发出 URB给 USB类驱动程序(USBD)读取数据的 那么 HIDUSB 首先必须构造一个 IRP_MJ_INTERNAL_DEVICE_CONTROL它的 IOCTL功能码为 IOCTL_INTERNAL_USB_SUBMIT_URB(发出 URB的内部 IOCTL)另外 因为我们要检查并修改的是 USB 键盘某个接口的报告描述 那么这个 URB 应该是URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE 如下

NTSTATUS UsbFilterDevice::InternalDeviceControl(KIrp I){ T << "UsbFilterDevice::InternalDeviceControl\n"; // Pass through IOCTLs that are not submitting an URB//不是我们感兴趣的 IOCTL不要理它 if (I.IoctlCode() != IOCTL_INTERNAL_USB_SUBMIT_URB) return DefaultPnp(I);

PURB p = I.Urb(CURRENT); // get URB pointer from IRP

//不是我们感兴趣的 URB 也不要理它

if (p->UrbHeader.Function !=URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE) return DefaultPnp(I);//符合要求的 IRP才被设置完成例程 return PassThrough(I, LinkTo(DeviceControlComplete), this);}在设置好条件以后 再来实现完成例程 所有的检查 修改等动作都是在完成例程里面完

成的

NTSTATUS UsbFilterDevice::DeviceControlComplete(KIrp I){ PURB p = I.Urb(CURRENT); if(p) {//拦截到设备返回的描述表 char* DescriptorBuffer = (char*)p->UrbControlDescriptorRequest.TransferBuffer;//指向第三个字节 表示设备 Usage属性的值 DescriptorBuffer += 3;//如果值为 6则改成 0 6表示 hid键盘 0表示未知设备//在设备管理器里面 原来的 hid兼容键盘就不复存在了 取而代之的则是 hid兼容设备 if ((*DescriptorBuffer&0xff) == 6) *DescriptorBuffer = 0;

32

} return I.Status();}读者可以对照 DriverWorks 中的例子 直接替换掉 或者修改 上面这两个函数 再编译

一下 便可以得到一个完整的键盘过滤器驱动程序

其实 只要弄清楚了我们需要做些什么动作 在 DriverStudio 里面只需要写少量的关键代码 便可实现我们的要求 其余的大部分工作 或有范例可供参考 或有 Driver Wizard自动生成

从上面可以看出 我们只需要修改这两个函数 拦截合适的 IRP 便可以在完成例程

里面实现我们特定的要求 正如开头所说 我们也可以拦截其他的 IRP 拦截其他的 URB或者拦截特定键盘的按键键值 将之传递到用户态 以方便实现联想 实达等随机配备的

多功能键盘的功能

三 使用 INF安装驱动 在完成了驱动以后 还必须把它安装到系统里面 驱动程序才会起作用 一般来说

我们都必须为我们的驱动程序提供一个 inf 文件 以便于用户安装或者维护 对于新手来

说 过滤驱动程序的 inf或许有些棘手 所以 针对本文所描述的驱动 我们提供一个Win98下的安装范例 usbkey.inf 范例中 ;”后的文字是注解 以方便读者理解

; usbkey.INF;; Installs Lower Level Filter for a HID keyboard device;; (c) Copyright 2001 SINO Co., Ltd.;[Version];”CHICAGO”表示Win9x平台Signature="$CHICAGO$";键盘所属类名Class=HIDClassGUID={745a17a0-74d3-11d0-b6fe-00a0c90f57da}驱动程序提供者 此信息会显示在设备属性的 常规 页

Provider=%USBDBE%LayoutFile=layout.inf;显示在驱动程序文件详细资料窗口DriverVer=11/12/2001,4.10.2222.12

;[ControlFlags];ExcludeFromSelect = *

33

;驱动程序安装目录 inf会将我们的驱动程序安装到如下目录;记得 Destinationdir后面一定要带一个 s”[DestinationDirs]DefaultDestDir = 10,system32\drivers

;要增加的注册表项[ClassInstall]Addreg=HIDClassReg

[HIDClassReg]HKR,,,,%HID.ClassName%HKR,,Icon,,-20

;制造商[Manufacturer]%USBDBE%=USBDBE

[USBDBE];我们所要附加过滤驱动程序的设备 ID 这个 ID可以从 IC的规范上得来 也可以

;用 hidview.exe读出 或者从注册表 HKLM\Enum\hid和 usb项找出%HID.DeviceDesc% = Keypad_Inst, USB\VID_05AF&PID_0805&MI_00

;要安装的文件和需要修改的注册表项;Install usbkey driver[Keypad_Inst]CopyFiles=Keypad_Inst.CopyFilesAddReg=Keypad_Inst.AddReg

[Keypad_Inst.CopyFiles]hidusb.syshidparse.syshidclass.sysusbfilt.sys

[Keypad_Inst.AddReg]HKR,,DevLoader,,*ntkernHKR,,NTMPDriver,,"hidusb.sys"

[Keypad_Inst.HW]AddReg=Keypad_Inst.AddReg.HW

;Lowerfilters表示是低层过滤驱动 如果是上层过滤驱动 则必须改为 upperfilters[Keypad_Inst.AddReg.HW]

34

HKR,,"LowerFilters",0x00010000,"usbfilt.sys"

;HID设备所需要安装的文件和注册表中需要修改的地方;Install USBHIDDevice[USBHIDDevice]CopyFiles=USBHIDDevice.CopyAddReg=USBHIDDevice.AddReg

[USBHIDDevice.Copy]hidclass.syshidusb.syshidparse.sys

[USBHIDDevice.AddReg]HKR,,DevLoader,,*ntkernHKR,,NTMPDriver,,"hidusb.sys"

;以下定义需要在上面某些地方使用时替换的字符串[strings]USBDBE = "SINO Co., Ltd."HID.DeviceDesc = "SINO USB MultiKeyboard"HID.HIDDeviceDesc = "Human Interface Devices"HID.DefaultDevice = "HID Default Device"HID.ClassName = "Human Input Devices (HID)"HID.SvcDesc = "Microsoft HID Class Driver"

其实最简单的写 inf 的方式 是找一些类似设备的 inf 文件或范例来修改 在不侵权的前提

下 充分利用现有资源是我们的一贯原则

用 vtoolsd实现虚拟设备驱动程序

由于 Windows 对系统底层操作采取了屏蔽的策略 因而对用户而言 系统变得更为安全

但这却给众多的硬件或者系统软件开发人员带来了不小的困难 因为只要应用中涉及到底

层的操作 开发人员就不得不深入到 Windows 的内核去编写属于系统级的虚拟设备驱动程序 Win 98与Win 95设备驱动程序的机理不尽相同 Win 98不仅支持与Windows NT 5.0兼容的WDM(Win32 Driver Mode)模式驱动程序 而且还支持与Win 95兼容的虚拟设备驱动程序 VxD(Virtual Device Driver) 下面介绍了基于Windows 9x平台的虚拟环境 虚拟设

备驱动程序 VxD 的基本原理和设计方法 并结合开发工具 VToolsD 给出了一个为可视电话音频卡配套的虚拟设备驱动程序 VxD的设计实例

35

1 Windows 9x的虚拟环境Windows 9x 作为一个完整的 32 位多任务操作系统 它不像 Window 3.x 那样依赖于

MS-DOS 但为了保证软件的兼容性 Windows 9x除了支持Win16应用程序和Win32应用程序之外 还得支持 MS-DOS 应用程序的运行 Windows 9x 是通过虚拟机 VM(VirtualMachine)环境来确保其兼容和多任务特性的

所谓 Windows虚拟机 通常简称为 Windows VM 就是指执行应用程序的虚拟环境

它包括 MS-DOS VM和 System VM两种虚拟机环境 在每一个 MS-DOS VM中都只运行一个 MS-DOS 进程 而 System VM 能为所有的 Windows 应用程序和动态链接库DLL(Dynamic Link Libraries)提供运行环境 每个虚拟机都有独立的地址空间 寄存器状态

堆栈 局部描述符表 中断表状态和执行优先权 虽然 Win16 Win32 应用程序都运行在System VM环境下 但Win16应用程序共享同一地址空间 而Win32应用程序却有自己独立的地址空间

在编写应用程序时 编程人员经常忽略虚拟环境和实环境之间的差异 一般认为虚拟

环境也就是实环境 但是 在编写虚拟设备驱动程序 VxD 时却不能这样做 因为 VxD 的工作是向应用程序代码提供一个与硬件接口的环境 为每一个客户虚拟机管理虚设备的状

态 透明地仲裁多个应用程序 同时对底层硬件进行访问 这就是所谓虚拟化的概念

VxD在虚拟机管理器 VMM(Virtual Machine Manager)的监控下运行 而 VMM实际上是一个特殊的 VxD VMM 执行与系统资源有关的工作 提供虚拟机环境 能产生 调度

卸载 VM 负责调度多线程占先时间片及管理虚拟内存等工作 VxD 与 VMM 运行在其

他任何虚拟机之外 VxD事实上就是实现虚拟机的软件的一部分与大多数操作系统一样 Windows 也是采用层次式体系结构 VMM 和 VxDs 构成了

Win 95的 ring0级的系统核心 应用程序运行在 ring3级 ring1 ring2级未被使用 具有

系统的最高优先权 Windows 还提供一些以"drv"为后缀名的驱动程序 主要是指串行口的

通信程序和并行口的打印机程序 这些程序与 VxD 不同 它们是运行在 ring3 级上的 图

1可以使你更好地理解Windows的虚拟环境图

2 深入理解 VMM和 VxD如前所述 VxD是 Virtual Device Driver的缩写 但有人将它理解为虚拟任何驱动程序

实际上 VxD 并非仅指那些虚拟化的某一具体硬件的设备驱动程序 比如某些 VxD 能够虚拟化设备 而某些 VxD 作为设备驱动程序却并不虚拟化设备 还有些 VxD 与设备并没有什么关系 它仅向其他的 VxD或是应用程序提供服务

VxD 可以随 VMM 一起静态加载 也可以根据需要动态加载或卸载 正是由于 VxD与 VMM之间的紧密协作 才使得 VxD具有了应用程序所不具备的能力 诸如可以不受限

制地访问硬件设备 任意查看操作系统数据结构 如描述符表 页表等 访问任何内存区

域 捕获软件中断 捕获 I/O端口操作和内存访问等 甚至还可以截取硬件中断

尽管 VxD使用 32位平面存储模式(flat memory model) 但它的代码和数据仍使用分段

管理 段有六种类型 即实模式初始化 保护模式初始化 可分页 不可分页 静态和只

调试(debug only) 每种类型又有代码段和数据段之分 所以 VxD 共有 12 个段 实模式代

码段和数据段为 16位 分段模式 其他段则是 32位 平面模式 实模式初始化 段包

含了在 Windows 初始化过程的最初阶段 VMM 变为保护模式之前要执行的代码 静态加载

的 VxD此时可以查看Windows启动前的实模式环境 决定是否继续加载 并通知 VMM加载完毕后 VMM 进入保护模式并执行保护模式初始化代码 同样将执行结果再通知

36

VMM 初始化完成后 实模式初始化 段和 保护模式初始化 段即被遗弃 VxD 的大部分代码都在其他的某一段中 可分页 段允许虚拟存储管理器(Virtual Memory Manager)进行分页管理 大多数的 VxD 代码都应当在 可分页 段 不可分页 段的内容主要包

括 VxD 的主入口点 硬件中断处理函数 所访问的数据以及能被另一个 VxD 中断处理函数调用的异步服务 静态 段仅用于可以动态加载的 VxD 当 VxD 卸载后 静态代码

段和数据段都保留在内存中 只调试 段只是 VMM在 Soft-ICE for Win 95等调试环境下才将其载入

VMM 是通过 VxD 的设备描述符块 DDB(Device Descriptor Block)来识别的 DDB 向VMM提供了 VxD的主入口点 还向应用程序和其他的 VxD提供了入口点 VMM利用这个主入口点将 VM 及 Windows 自身的状态通知给 VxD 然后 VxD 通过相应的工作来响应这些事件 由于 VxD 不仅仅服务于一个物理设备 比如多个串口 或仅与一个 VM 发生

联系 所以 VxD需要产生自己支持的数据结构(Supporting Data Structures)来保存每一个设备 每一个 VM 的配置和状态信息 VxD 用一个或多个设备上下文结构来保存设备信息如 I/O 端口基地址 中断向量等 VxD 将自己的每个 VM 的状态信息保存在 VMM 的 VM控制块中

VMM 提供的服务包括 事件服务 内存管理服务 兼容执行和保护模式执行的服务

登录表服务 调度程序服务 同步服务 调试服务 I/O 捕获服务 处理错误和中断服务

VM中断和回调服务 配置管理程序服务以及其他杂项服务

以上内容仅涉及到 VxD设计的一小部分 作为 VxD的开发人员必须掌握更多的知识首先是操作系统的知识 如地址空间 执行上下文 资源加锁 进程间通信和异步事件处

理等方面的知识 其次 对 Intel 处理器应有较深入的理解 包括寄存器 机器指令集 保

护机制 分页机制 以及虚拟 8086模式 最后 还必须熟悉 VMM提供的各类服务和接口熟悉Windows其他的系统 VxD

3 开发工具 VToolsD简介VToolsD 是专门用于开发 VxD 程序的一种工具软件 它包括 VxD 框架代码生成器

QuickVxD C运行库 VMM/VxD服务库 VxD的 C++类库 VxDLoad和 VxDView等实用工具以及大量的 C C++例程 由 VC++ BC++的 32 位编译器编译生成的 VxD 程序可以脱离 VToolsD环境运行

利用 QuickVxD 可以方便 快捷地生成 VxD 的框架 即生成后缀名为 h cpp 和 mak的三个文件 源文件包含了运行 VxD 的基本组件 其中包含控制消息处理 API 入口点以及 VxD服务等函数框架 并且还定义了标志 设置了编译参数 声明了类 然后在 C++环境下 向生成的各个处理函数体内添加自己的代码 最后使用编译器 NMAKE 生成标准的 VxD程序由于 VxD运行在 ring0级 所以调试程序相当困难 我使用的调试工具是 Soft-ICE for

Win 95目前 VToolsD 的最新版本为 3.0 它支持设备访问体系结构 DAA(Device Access

Architecture) 所编写的程序代码将可以在所有Windows平台 包括Win 95 Win 98以及Windows NT 上共享 当然也可以使用 Microsoft公司的 DDK(Device Developer Kit)来开发 VxD 但 DDK不能像 VToolsD那样通过屏蔽系统及 VxD的底层技术细节提供丰富的 C运行库和 C++类库 而是让开发人员充分享用面向对象编程方法的方便与快捷 因此仅就

该点而言 使用 DDK是不方便的

4 VxD程序设计实例

37

我在开发可视电话音频卡的设计过程中 用 VToolsD 2.03 VC++ 5.0为自制的 PC/XT总线扩展卡开发了虚拟设备驱动程序 Audcard.vxd 该卡每 20ms 申请一次中断 中断由应

用程序动态载入系统的 Audcard.vxd 响应并加以处理 中断服务程序 ISR(Interrupt ServiceRoutine)结束后 调用函数 Shell_PostMessage( )向应用程序窗口发送自定义消息 应用程序

接受消息后 再通过函数 DeviceIoControl( )与 VxD的接口函数 OnW32DeviceIoControl( )互传缓冲区数据 程序结束即可动态卸载 VxD

当中断发生时 处理器转换为 ring0 级保护模式 Windows 系统并不像 DOS 那样通过中断描述符表 IDT(Interrupt Descriptor Table)直接指向中断处理过程 而是由 IDT入口指向VMM 中的程序 该程序将判断是否为中断调用 如果是 则把中断控制权交给虚拟可编

程中断控制器 VPICD(Virtual Programmable Interrupt Controller Device) VPICD实际上是一个重要的 VxD VPICD再将其交给另一个注册了该中断的 VxD 如 Audcard.vxd 来处理

VxD程序是通过调用 VPICD服务 VPICD_Virtualize_IRQ来注册中断的虚拟设备驱动程序 Audcard.vxd 的部分源代码 Audcard.h 和 Audcard.cpp 在网上 网址

为 www.pccomputing.com.cn 此应用程序使用了下列函数 CreateFile()动态加载 VxDCloseHandle()并动态卸载 VxD PreTranslateMessage()截获消息 DeviceIoControl()与 VxD互传缓冲区数据 虚拟设备驱动程序 Audcard.vxd 经调试后工作正常 未发生过任何丢失

数据或死机的现象

下面是虚拟设备驱动程序 Audcard.vxd 的部分源代码 Audcard.h 和 Audcard.cpp 限于

篇幅 由 QuickVxD自动生成的 Audcard.mak未列出Audcard.h

//AUDCARD.h - include file for VxD AUDCARD#include#define DEVICE_CLASS AudcardDevice#define AUDCARD_DeviceID UNDEFINED_DEVICE_ID#define AUDCARD_Init_Order UNDEFINED_INIT_ORDER#define AUDCARD_Major#define AUDCARD_Minor 0#define MY_IRQ 5 //定义 5号中断class MyHwInt:public VHardwareInt{ public: MyHwInt():VHardwareInt(MY_IRQ,0,0,0){} virtual VOID OnHardwareInt(VMHANDLE);};class AudcardDevice : public VDevice{ public: virtual BOOL OnSysDynamicDeviceInit(); virtual BOOL OnSysDynamicDeviceExit(); virtual DWORD OnW32DeviceIoControl(PIOCTLPARAMS pDIOCParams); MyHwInt* pMyIRQ;};class AudcardVM : public VVirtualMachine{ public:

38

AudcardVM(VMHANDLE hVM);};class AudcardThread : public VThread{ public: AudcardThread(THREADHANDLE hThread);};

Audcard.cpp//AUDCARD.cpp - main module for VxD AUDCARD#define DEVICE_MAIN#include "audcard.h"Declare_Virtual_Device(AUDCARD)#define WM_USER_POSTVXD 0x1000//自定义消息#undef DEVICE_MAINAudcardVM::AudcardVM(VMHANDLE hVM) : VVirtualMachine(hVM) {}AudcardThread::AudcardThread(THREADHANDLE hThread) : VThread(hThread) {}BOOL AudcardDevice::OnSysDynamicDeviceInit() //动态加载时初始化{ ......//硬件初始化 pMyIRQ=new MyHwInt();if(pMyIRQ&&pMyIRQ->hook()) //挂接中断{ pMyIRQ->physicalUnmask(); //允许中断 return TRUE; } else return FALSE;}BOOL AudcardDevice::OnSysDynamicDeviceExit()//动态卸载过程{ delete pMyIRQ; return TRUE;}DWORD AudcardDevice::OnW32DeviceIoControl(PIOCTLPARAMS pDIOCParams)//与Win32应用程序的接口函数{ ......}VOID MyHwInt::OnHardwareInt(VMHANDLE hVM){ ...... // 中断处理 SHELL_PostMessage(AppWnd,WM_USER_POSTVXD ,0,0,0,NULL);

39

//向应用程序窗口发送消息 sendPhysicalEOI(); //通知 VPICD中断结束}

Driver studio 例子说明(英文)

VtoolsD Source Code Examples :Name DescriptionAPC Demonstrates how to communicate with a Win32 application using AsynchronousProcedure Calls.APPYTIME Demonstrates "appy time events" which are used to call Win16 apps and DLLs.APPYTIME Demonstrates use of appy time events from C++.CALLASM Shows how to add an assembly language module to your VxD.CALLSRV Shows how a VxD in C can provide services to other VxDs.CALLSRVP Shows how a VxD in C++ can provide services to other VxDs.CFSD A simple Character File System Device.CLASSTUT Sends messages to be displayed by a Windows application.COMMCLNT Demonstrates how to write a VCOMM client to read and write to a serial port.COMMHOOK Shows how to hook VCOMM port services.DBGTRAP Trap VxD debug output for display in DBGMON program.FILTMOUS Filters mouse events.HELLO Basic "hello, world" VxD to demonstrate C++ framework.HOOKCSVC Illustrates how to hook VxD/VMM services in C.HOOKVIRQ Intercept IRQ virtualization, allowing your driver to get interrupt first.HOTKEY Demonstrates how to install a hotkey handler.HWDOSINT Shows how to use VPICD services to interface with hardware. (C++ driver)HWDOSINT Shows how to pass hardware interrupts to ring 3 handlers. (C driver)HWINT Shows how to handle hardware interrupts by monitoring the real time clock. (C++driver)HWINT Shows how to handle hardware interrupts by monitoring the real time clock. (C driver)IFSHOOK Demonstrates how to hook all file activity using the IFS hook services. INITFIO Demonstrates how to perform file i/o at initialization time.IOCTL Shows a simple mechanism for Win32/ VxD communication.MAPDEV Demonstrates how to write a VxD for a memory mapped device.MINIMOUS Functional mouse driver for serial using VCOMM services for serial input.NDISMAC Framework for an NDIS MAC driver.NDISPROT Framework for an NDIS Protocol driver.OVRLAPIO Shows how to use overlapped i/o when calling a driver from a Win32 application.PCICFG PCI configuration within a VxD.PNP Illustrates the basic structure of a Plug and Play driver and INF file.POSTMSG Shows how to post a message from a VxD to a Windows application.

40

R0FILEIO Use IFS functions to read/write file from within VxD.REALINIT Shows how to use a real-mode initialization segment in a VxD.REGFSD Exposes the registry as a file system, assigning a driver letter and allowing the user tobrowse the registry keys as if they were folders and files.SIMPLE Demonstrates the basic use of the C framework.SLAVEDMA Initiate DMA (slave) within a driver.THUNK Demonstrates the correct syntax for event, interrupt, and exception handling callbacks.TIMEOUT Shows how to generate a periodic callback to a VxD.VCOMPORT Shows the basic framework for a VCOMM serial port driver.VDOSKEY Demonstrates how a TSR (DOSKEY) can be re-written as a VxD.VSD Simple IOS layered block device driver.W32INTF Shows how to communicate with a Win32 application using shared events and sharememory.DriverWorks: Source Code Examples for Windows NT :Name DescriptionBASICPCI A skeletal driver for a PCI device. Illustrates assignment of resources and access tothe PCI configuration space. See MASTRDMA for a full PCI example.BUSADDR A demonstration of usage of class KPeripheralAddress and related classes. Showshow to map a physical region of a memory mapped device to process space, and how to do portI/O. This example accesses hardware specific to Intel platforms.COMMFILT A filter driver for serial devices. Includes a GUI application.CONTROLR A demonstration of class KController. Illustrate serialization of IRPs for devicessharing common physical resources represented by a controller object.FILE A demonstration of file I/O from a kernel mode driver.HELLO The simplest driver, with detailed comments about the driver structure. You can buildthis driver and load it with DriverMonitor to confirm your setup.INTRDEMO A demonstration of handling hardware interrupts.KBDCLASS A keyboard class driver, based on the DDK sample.KBFILTER A filter driver for the keyboard.MAPMEM A demonstration of memory mapped devices.MASTRDMA A driver for a Datel PCI416L analog to digital board, demonstrating bus masterDMA.PARPORT A contention handler (class driver) for the parallel port, based on the DDK sample.PCIENUM A driver to enumerate all the PCI devices in the system.PORTIO A driver to support a simple utility program that enables you to read and write portsinteractively.RAMDISK Implements a RAM disk, under the control of a GUI application.SERIAL A driver for 8250 style UARTs, using an extensible SerialDevice class. This samplecan be used to replace the standard system-provided serial driver.SLAVEDMA A driver for the National Instruments ATM-IO-16X analog to digital board,illustrating how to perform slave (system) DMA.THREADS A demonstration of system threads.DriverWorks: Source Code Examples for WDM (Windows 98/NT 5):Name Description

41

BASICUSB Shows how to access the USB configuration descriptor, how to set up aconfiguration, and how to create interface and pipe objects to model a USB device.HELLOWDM Shows how to implement a 'simple' WDM driver, including Plug and Playmessage handling using NuMega's policy interfaces.HIDMOUSE A WDM HID minidriver that enables control keys to simulate mouse movements.PCIWDM Provides a framework for a monolithic driver of a PCI device on in the WDMenvironment.STREAM Implements a video capture driver using kernel streaming classes.USBBULK Shows how to use bulk data transfer between the host and a USB peripheral.