Windows98/2000驱动程序编写方法 (上) 杨全胜
1. 驱动程序的开发环境 对于VxD的开发,需要的开发环境是: Visual C++ 5.0/6.0 Windosw 95DDK 1. 驱动程序的开发环境 对于VxD的开发,需要的开发环境是: Visual C++ 5.0/6.0 Windosw 95DDK 如果想加快开发步骤,建议使用第三方的VToolsD开发工具,它将DDK的东西全部封装成C++的类,可以直接用Visual C++编写程序,而无须使用汇编。而且它提供的 QuickVxd能够方便快速地建立VxD程序的框架。
对于WDM的开发,又分几种情况: 对于Windows 98系统 Visual C++ 5.0 Windows 98DDK 2)对于Windows Me/2000 Visual C++ 6.0 Windows 2000DDK 3)对于Windows XP Visual C++6.0/.net Windows XPDDK 同样,为了方便起见,也可以使用第三方的开发工具Driver Works,它也是将DDK的内容封装成类,而且提供一个快速方便地生成驱动框架的工具。
2. 驱动程序开发工具包DriverStudio 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 是一个功能极其强大的内核模式调试器,它支持在配置一台单独的计算机或两台计算机下进行设备驱动程序的调试。
DriverMonitor DriverMonitor不仅可以显示WDM和VxD在操作系统核心层次输出的调试语句,还可以装载和卸载VxD驱动和NT4系统的驱动程序。 EZDriverInstaller EZDriverInstaller是一个无需经过设备管理器或“添加新硬件”功能就能为Windows 2000/XP动态加载和卸载WDM驱动程序的小实用程序。 SetDDKGo SetDDKGo用来设置设备驱动程序创建的环境。当我们用Visual Studio(VC++)编译驱动程序源程序的时候,需要用SetDDKGo来设置环境变量,之后SetDDKGo会自动启动Visual Studio(VC++)编译环境。
2.2 DriverStudio 2.7的安装 安装需要的软硬件环境 PC-compatible Intel x86 系统 Windows 2000, (Optional: 其中一些工具也支持 Windows 98 和 Windows 95.) 内存: 最少32 MB, 推荐使用64 MB 硬盘:完全安装需要 72 MB 针对 SoftICE的远程调试: NE2000-兼容网卡或 3Com 网卡 针对 DriverWorks: Microsoft DDK,MS Visual C++
安装步骤: 在安装DriverWorks之前,首先要保证你的计算机上已经安装了Microsoft Visual C++以及相应针对Windows NT 或WDM的DDK的正确版本。 所有这些包括DriverStudio的安装都必须以系统管理员身份启动系统。并且要按照下面的顺序安装。 第一步: 安装Microsoft Visual Studio C++ 6.0 (注意Windows 2000DDK暂时对Visual Studio.NET不支持)
第二步: 安装2000DDK(Driver Development Kits)。 注意: 1)如果本地机的操作系统为Windows2000 请确认已经安装了Windows SevicePack1或Windows SevicePack2在安装DDK的时候请选择完全安装。 2)安装中,不需要安装64BIT IA64Binaries 3)安装好后,对于2000DDK不需要手动配置环境变量,只需在开始菜单中点击Checked Build Envirment 则DDK会自动调用setenv配置环境变量,并监测相应的SDK以及Visual Studio IDE
第三步: 安装DriverStudio 2.7(按照安装提示安装)。 注意, DriverStudio 2.7,包括最新的DriverStudio 3.0不能在Windows 2000的SP4和WindowsXP上使用,会引起蓝屏。遇到这种情况,请在安装好后不要重新启动机器,而是立即打上补丁。打补丁的方法是用新的siwvid.sys(补丁的内容)替换掉\%system%\system32\drivers\下的老的siwvid.sys
DriverStudio安装后的设置: 1)使用SetDDKGo工具定义BASEDIR环境变量并启动MSVC 5.0或6.0 ,
2)打开下列地址上的建立库文件工程
3)选择Build|Batch Build(编译|批构件),打开下面的窗口,从中选则需要编译的配置。 注意不要选择IA64的各项,Checked是调试版本,Free是发布版本
4)点击Build编译所选择的库文件。 注意:库文件只需在安装完成后第一次使用前编译一次即可。以后要使用DriverWorks,只需通过SetDDKGo进入MSVC即可。
3. VtoolsD开发VxD简介 生成简单框架
设备名 动态装载 编程语言 生成调试用目标代码
与应用程序通信的控制消息
设备类名 虚拟机类名 线程类名
头文件 // MYFIRST.h - include file for VxD MYFIRST #include <vtoolscp.h> #define DEVICE_CLASS MyfirstDevice #define MYFIRST_DeviceID UNDEFINED_DEVICE_ID #define MYFIRST_Init_Order UNDEFINED_INIT_ORDER #define MYFIRST_Major 1 #define MYFIRST_Minor 0 #define DIOC_MY_IO CTL_CODE(FILE_DEVICE_UNKNOWN, 1, METHOD_NEITHER, FILE_ANY_ACCESS) // 定义一个句柄用于应用程序与VxD通信
class MyfirstDevice : public VDevice { public: virtual DWORD OnW32DeviceIoControl(PIOCTLPARAMS pDIOCParams); }; class MyfirstVM : public VVirtualMachine MyfirstVM(VMHANDLE hVM); class MyfirstThread : public VThread MyfirstThread(THREADHANDLE hThread);
.cpp文件 // MYFIRST.cpp - main module for VxD MYFIRST #define DEVICE_MAIN #include "myfirst.h" Declare_Virtual_Device(MYFIRST) #undef DEVICE_MAIN MyfirstVM::MyfirstVM(VMHANDLE hVM):VVirtualMachine (hVM) {} MyfirstThread::MyfirstThread(THREADHANDLE hThread): VThread(hThread) {} DWORD MyfirstDevice::OnW32DeviceIoControl (PIOCTLPARAMS pDIOCParams) { char* msg="欢迎进入虚拟机世界!"; char* caption="Hello World!";
switch(pDIOCParams->dioc_IOCtlCode) { case DIOC_OPEN:dout<<"I am Opening!"; break; //调用CreateFile函数时响应该分支代码 case DIOC_MY_IO:dout<<"I am working!"<<endl; SHELL_Message(pDIOCParams->dioc_hvm, MB_OK, msg, caption,0,0,0); break; //调用DeviceIoControl函数时响应该分支下的代码 case DIOC_CLOSEHANDLE:dout<<"I am Closing!";break; //调用CloseHandle函数时响应该分支代码 } return 0;
# MYFIRST.mak - makefile for VxD MYFIRST DEVICENAME = MYFIRST DYNAMIC = 1 FRAMEWORK = CPP DEBUG = 1 OBJECTS = myfirst.OBJ !include $(VTOOLSD)\include\vtoolsd.mak !include $(VTOOLSD)\include\vxdtarg.mak myfirst.OBJ: myfirst.cpp myfirst.h
Win32环境下的控制台程序 #include <iostream.h> #include <windows.h> #include <winioctl.h> #define DIOC_MY_IO CTL_CODE(FILE_DEVICE_UNKNOWN, 1, METHOD_NEITHER, FILE_ANY_ACCESS) //定义一个句柄用于应用程序与VxD通信 void main() { HANDLE hDevice; hDevice=CreateFile ("\\\\.\\myfirst.vxd", 0, 0,0,OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, 0); // 文件名的路径一定是以\\\\.\\打头 ,默认的VXD的文件的目录是C:\Windows\system,如果VXD在d:\myvxd目录下,则这里应该写成"\\\\.\\d:\\myvxd\\myfirst.vxd"
if(hDevice==INVALID_HANDLE_VALUE) { cout<<"Open VxD error"<<GetLastError()<<endl; exit(1); } DeviceIoControl(hDevice,DIOC_MY_IO,NULL,0,NULL,0, NULL,NULL); //使用句柄DIOC_MY_IO与VxD交互 CloseHandle(hDevice); //关闭设备句柄
VToolsD的类库
I/O类 class VIOPort { public: VIOPort(DWORD port); //构造函数 BOOL hook(); //挂钩一个端口处理程序 BOOL unhook(); //摘钩一个端口处理程序 VOID localEnable(VMHANDLE); //允许监控指定虚拟机端口的I/O操作 VOID localDisable(VMHANDLE);//禁止监控指定虚拟机端口的I/O操作 VOID globalEnable(); //允许监控所有虚拟机端口的I/O操作 VOID globalDisable(); //禁止监控所有虚拟机端口的I/O操作 virtual DWORD handler(VMHANDLE, DWORD port,CLIENT_STRUCT* pRegs, DWORD iotype, DWORD outdata);//端口处理程序 protected: DWORD m_port; BYTE m_thunk[IOPORTTHUNKSIZE]; };
下面我们给出一个例子,该例子捕获端口0xf0,当应用程序要读该端口时,它将上次写入该端口的资料返回给应用程序。用VToolsD生成的时候,基本上和生成Myfirst的方法一样,只是在Windows95控制消息中只选择SYS_DYNAMIC_DEVICE_EXIT和SYS_DYNAMIC_DEVICE_INIT两个消息而不选择W32_DEVICEIOCONTROL。 // IOPORT.h - include file for VxD IOPORT #include <vtoolscp.h> #define DEVICE_CLASS IoportDevice #define IOPORT_DeviceID UNDEFINED_DEVICE_ID #define IOPORT_Init_Order UNDEFINED_INIT_ORDER #define IOPORT_Major 1 #define IOPORT_Minor 0 #define PORT_NUM 0xf0 //要捕获的端口地址 ……
………… class IoportPort : public VIOPort { public: IoportPort():VIOPort(PORT_NUM){}; DWORD handler(VMHANDLE hVM, DWORD port,CLIENT_STRUCT* pRegs,DWORD iotype, DWORD outdata); };
// IOPORT.cpp - main module for VxD IOPORT #define DEVICE_MAIN #include "ioport.h" Declare_Virtual_Device(IOPORT) #undef DEVICE_MAIN IoportPort* pio; IoportVM::IoportVM(VMHANDLE hVM) : VVirtualMachine(hVM) {} IoportThread::IoportThread(THREADHANDLE hThread) : VThread(hThread) {}
BOOL IoportDevice::OnSysDynamicDeviceInit() { // 动态装载初始化的时候调用 pio=new(IoportPort); if(pio) { if(pio->hook()) // 挂上端口映射 dout<<"hook success"<<endl; } else return FALSE; else return FALSE; return TRUE;
BOOL IoportDevice::OnSysDynamicDeviceExit() { // 驱动程序被卸下时调用 if(pio) { if(pio->unhook()) //将I/O映射卸载下来 dout<<"unhook success"<<endl; } delete pio; return TRUE; DWORD IoportPort::handler(VMHANDLE hVM, DWORD port,CLIENT_STRUCT* pRegs,DWORD iotype, DWORD outdata) static WORD count=0; static DWORD buffer[20]; static BYTE inppt=0; count++; dout<<"entering handler"<<endl; dout<<"entered count"<<count<<endl;
switch(iotype) { case BYTE_INPUT: dout<<"buffer byte"<<(BYTE)buffer[inppt]<<endl; inppt++; return(BYTE)buffer[inppt-1]; case WORD_INPUT: dout<<"buffer word"<<(WORD)buffer[inppt]<<endl; inppt++; return(WORD)buffer[inppt-1]; case DWORD_INPUT: dout<< "buffer dword"<<(WORD)buffer[inppt]<<endl; inppt++; case BYTE_OUTPUT:buffer[count-1]=outdata;dout<<"byte output data:"<<(BYTE) outdata<<endl; break; case WORD_OUTPUT:buffer[count-1]=outdata;dout<<"word output data:"<<(WORD) outdata<<endl; break; case DWORD_OUTPUT:buffer[count-1]=outdata;dout<<"dword output data:"<< (DWORD)outdata<< endl; default:break; } //对于输入,处理程序将数据输入到端口的缓冲,对于输出,处理程序将缓冲中的资料输出到端口 return 0; }
下面给出调用端口0xf0的Win32控制台程序。 #include "conio.h" #include <iostream.h> #include <windows.h> #include <winioctl.h> #define PORT_NUM 0xf0 void main() { char* srcdata="This is IO Sample!!"; char* desdata=new char(20); HANDLE hDevice; char ch; _cprintf("1=load IOPORT VXD, x=exit, Others=Do not load IOPORT VXD\n"); ch=_getch(); while(ch!='x') if(ch=='1') hDevice=CreateFile ("\\\\.\\Ioport.vxd",0,0,0,OPEN_EXISTING,FILE_FLAG_DELETE_ON_CLOSE,0);
if(hDevice==INVALID_HANDLE_VALUE) { cout<<"Open VXD error"<<GetLastError()<<endl; exit(1); } desdata[0]='\0'; _asm mov dx,PORT_NUM mov edi,srcdata mov ecx,20 byteoutput: mov al,[edi] out dx,al inc edi loop byteoutput mov esi,desdata byteinput: in al,dx mov [esi],al inc esi loop byteinput
if(ch=='1') { CloseHandle(hDevice); //关闭设备句柄 _cprintf("scrdata and desdata should be the same!\n"); } _cprintf("srcdata:%s\n",srcdata); _cprintf("desdata:%s\n",desdata); _cprintf("1=load IOPORT VXD, x=exit, Others=Do not load IOPORT VXD\n"); ch=_getch();
中断类 class VHardwareInt { public: VHardwareInt(int irq, DWORD flags, DWORD tout, PVOID refdata); //构造函数 ~VHardwareInt(); //析构函数 virtual BOOL hook(); //挂钩虚拟IRQ的处理程序 virtual BOOL unhook(); //摘钩虚拟IRQ的处理程序 virtual VOID OnHardwareInt(VMHANDLE);//硬件中断信号处理程序 virtual VOID OnVirtualInt(VMHANDLE); //虚拟中断信号处理程序VPICD给VxD的) virtual VOID OnVirtualEOI(VMHANDLE); //当虚拟机发出EOI命令时被调用 virtual VOID OnVirtualMask(VMHANDLE, BOOL bMask); //虚拟机改变屏蔽位的时候被调用 virtual VOID OnVirtualIRET(VMHANDLE); //当虚拟机中中断处理程序返回控制给VPICD时被调用 VOID assert(VMHANDLE); //在指定虚拟机中请求虚拟中断 VOID deassert(VMHANDLE); //撤消指定虚拟机中的虚拟中断 DWORD getStatus(VMHANDLE hVM); //返回虚拟IRQ的部分状态信息
DWORD getCompleteStatus(VMHANDLE hVM); //返回虚拟IRQ的全部状态信号 BOOL testPhysicalRequest(); //测试物理IRQ的状态信息 VOID sendPhysicalEOI(); //向物理PIC发出中断结束EOI信号 VOID physicalMask(); //物理屏蔽IRQ VOID physicalUnmask(); //物理允许IRQ VOID setAutoMask(); //指示可编程中断控制器执行智能的IRQ屏蔽 static LONG convertIntToIRQ(int vecno, VMHANDLE hVM); //将中断号转换成IRQ号 static LONG convertIRQToInt(int irq, VMHANDLE hVM); //将IRQ号转换成中断号 static BOOL forceDefaultOwner(int irq, VMHANDLE hVM); //控制IRQ的默认处理 protected: WORD m_irq; WORD m_flags; DWORD m_timeout; PVOID m_refdata; IRQHANDLE m_handle; BYTE m_thunk[HWITHUNKSIZE]; private: VHardwareInt(); };
class VSharedHardwareInt : public VHardwareInt { public: VSharedHardwareInt(int irq, DWORD flags, DWORD timeout,PVOID refdata); virtual BOOL hook(); virtual BOOL OnSharedHardwareInt(VMHANDLE); //中断处理程序 protected: BYTE m_sthunk[SHWINTTHUNKSIZE]; private: VSharedHardwareInt(void); };
下面我们使用VsharedHardwareInt给一个VxD的例子,该例子捕获鼠标中断。该例子也是用VtoolsD生成的可动态加载的VxD框架,然后在VC6中修改的,我们只给出修改的部分。 // HARDINT.h - include file for VxD HARDINT … #define IRQ_NUM 12 //根据你的鼠标情况指定,COM1为4,COM2为3,PS/2为12 class HardintDevice : public VDevice { public: virtual BOOL OnSysDynamicDeviceInit(); virtual BOOL OnSysDynamicDeviceExit(); }; class HardintInt : public VSharedHardwareInt HardintInt() : VSharedHardwareInt(IRQ_NUM,0,0,0){}; BOOL OnSharedHardwareInt(VMHANDLE hVM);
// HARDINT.cpp - main module for VxD HARDINT … HardintInt* pshint; BOOL HardintDevice::OnSysDynamicDeviceInit() { VMHANDLE hVM; hVM=VMD_Get_Mouse_Owner(); dout<<"Mouse's VMHANDLE"<<(DWORD)hVM<<endl; pshint=new(HardintInt); if(pshint) if(pshint->hook()) dout<<"shared interruppt hooked"<<endl; } else dout<<"shared interruppt hook failed!"<<endl; return FALSE;
else return FALSE; return TRUE; } BOOL HardintDevice::OnSysDynamicDeviceExit() { if(pshint) if(pshint->unhook()) dout<<"shared interrupt unhooked"<<endl; delete pshint; BOOL HardintInt::OnSharedHardwareInt(VMHANDLE hVM) static WORD count=0; count++; dout<<"hardware(Mouse) int count:"<<count<<endl; return FALSE;
热键类 class VHotKey { public: VHotKey(BYTE scan, scanType_t st, DWORD shift, DWORD flags, PVOID refData=0, DWORD delay=0); //构造函数 ~VHotKey() {unhook();}; //析构函数 virtual VOID handler(BYTE scan, keyAction_t ka, DWORD shift,PVOID refData, DWORD elapased); //热键处理程序 BOOL hook(); //挂钩热键处理程序 BOOL unhook(); //摘钩热键处理程序 VOID localEnable(BOOL enable, VMHANDLE hVM); //在指定的虚拟机中允许或禁止热键事件 VOID reflectToVM(VMHANDLE hVM, DWORD shift); //指示VxD把热键事件传到指定虚拟机中 static VOID cancelState(); //取消热键
protected: DWORD m_handle; BYTE m_scanCode; scanType_t m_scanType; DWORD m_shiftState; BYTE m_flags; PVOID m_refData; DWORD m_timeout; BYTE m_thunk[HKTHUNKSIZE]; }
下面这个例子截获Ctrl+P键,当按下这两个键时,将弹出一个窗口,说明已经捕获。该例子也是用VtoolsD生成的可动态加载的VxD框架,然后在VC6中修改的,我们只给出修改的部分。 // HOTKEY.h - include file for VxD HOTKEY … #define C_SCAN 0X19 //P键的扫描码 class HotkeyDevice : public VDevice { public: virtual BOOL OnSysDynamicDeviceInit(); virtual BOOL OnSysDynamicDeviceExit(); }; class HotkeyHot : public VHotKey HotkeyHot():VHotKey(C_SCAN, SCAN_NORMAL, HKSS_Ctrl, CallOnPress,"热键 被按下!",0){ }; void handler(BYTE scan, keyAction_t keyEvent, DWORD shift, PVOID refData, DWORD elapased);
// HOTKEY.cpp - main module for VxD HOTKEY … HotkeyHot* pkey; BOOL HotkeyDevice::OnSysDynamicDeviceInit() { pkey=new(HotkeyHot); if(pkey) if(pkey->hook()) dout<<"hot key hooked"<<endl; } else return FALSE; return TRUE;
BOOL HotkeyDevice::OnSysDynamicDeviceExit() { if(pkey) if(pkey->unhook()) dout<<"hot key unhooked"<<endl; } delete pkey; return TRUE; void HotkeyHot::handler(BYTE scan, keyAction_t keyEvent, DWORD shift, PVOID refData, DWORD elapased) dout<<"entering handler"<<endl; SHELL_Message(Get_Cur_VM_Handle(),MB_OK,(char*)refData,"CTRL+P Pressed",0,0,0); //显示一个消息框 pkey->reflectToVM(Get_Cur_VM_Handle(),shift); //将按键信息传送到当前虚拟机中