我们学习程序设计,都是从“Hello World”开始的,驱动程序也不例外,今天我就写一个驱动版的“Hello World”来热热身,目的希望大家能对驱动程序的基本框架有所了解。
驱动程序分为2类,一个是 Kernel(内核) 模式驱动,另一个是 Windows (用户窗口层)模式驱动,2种模式本质是相同,但细节不同,本文介绍的是内核模式驱动和驱动程序的安装、使用。
驱动程序同普通的 EXE,DLL 一样,都属于PE文件,而且都有一个入口函数。但EXE中,入口函数是main() / WinMain() 和 Unicode 的 wmain() / wWinmain(),DLL的入口函数则可有可无,它是DllMain()。
所以驱动程序也有入口函数,而且是必须的,它是 DriverEntry(),再次提示,它是必须的,因为I/O管理器会首先调用驱动程序的DriverEntry(),它的作用就像DllMain()--完成一些初始化工作。
虽然我们有时候把 DriverEntry 比作main(),但二者在本质上不同,DriverEntry 的生命周期非常短,其作用仅是将内核文件镜像加载到系统中时进行驱动初始化,调用结束后驱动程序的其他部分依旧存在,并不随它而终止。
所以我们一般可把 DriverEntry 称为“入口函数”,而不可称为“主函数”。因此作为内核驱动来说,它没有一个明确的退出点,这应该是atexit无法在内核中实现的原因吧。
DriverEntry()一共有2个参数:
1)PDRIVER_OBJECT DriverObject,指向驱动程序对象的指针,我们操作驱动程序,全靠它,它是由 I/O 管理器传递进来的;
2)PUNICODE_STRING RegistryPath,驱动程序的服务主键,这个参数的使用并不多,但要注意,在DriverEntry()返回后,它可能
会消失,所以如果需要使用,记住先要保存下来。
DriverEntry() 的返回一个 NTSTATUS 值,它是一个 ULONG 值,具体的定义,请参见 DDK 中的 NTSTATUS.H 头文件,里边有详细
的定义。
既然要写驱动版的“Hello World”,就需要确定如何来与驱动程序通信,常用的共享内存,共享事件,IOCTL宏,或者直接用ReadFile() 或 WriteFile() 进行读写,在本文里我就采用一种简单的、但又很常用的 IOCTL 宏,它依赖的 IRP派遣例程是IRP_MJ_DEVICE_CONTROL,Win32程序使用 DeviceIoControl() 与驱动进行通信,根据不同的IOCTL宏,输出不同的调试信息。
为了简便,我并没有使用 ReadFile() 将信息读出来,而是直接用 DbgPrint() 输出,所以需要使用 DbgView 查看,其他调试工具也可以。PS:偷懒!
驱动程序与 I/O 管理器通信,使用的是 IRP,即 I/O 请求包。IRP 分为2部分:IRP 首部;IRP堆栈。
IRP 首部信息如下:
IRP 首部:
IO_STATUS_BLOCK IoStatus //包含 I/O 请求的状态
PVOID AssociatedIrp.SystemBuffer // 如果执行缓冲区 I/O,这个指针指向系统缓冲区
PMDL MdlAddress // 如果直接 I/O,这个指针指向用户缓冲区的存储器描述符表
PVOID UserBuffer // I/O 缓冲区的用户空间地址
IRP堆栈:
UCHAR MajorFunction //(主要类型) 指示 IRP_MJ_XXX派遣例程
UCHAR MinorFunction //(IRP 的子类型) 同上,一般文件系统和 SCSI 驱动程序使用它
union Parameters //MajorFunction的联合类型
{
struct Read //IRP_MJ_READ的参数
ULONG Length
ULONG Key
LARGE_INTEGER ByteOffset
struct Write //IRP_MJ_WRITE的参数
ULONG Length
ULONG Key
LARGE_INTEGER ByteOffset
struct DeviceIoControl //IRP_MJ_DEVICE_CONTROL和IRP_MJ_INTERNAL_DEVICE_CONTROL的参数
ULONG OutputBufferLength
ULONG InputBufferLength
ULONG IoControlCode
PVOID Type3InputBuffer
}
PDEVICE_OBJECT DeviceObject //请求的目标设备对象的指针
PFILE_OBJECT FileObject //请求的目标文件对象的指针,如果有的话
操作 IRP。对于不同的 IRP 函数,操作也是不同的:有的只操作 IRP 首部;有的只操作 IRP 堆栈;还有操作 IRP 整体,
下面是一些常用的函数:
IRP整体:
名称 描述 调用者
IoStartPacket 发送IRP到Start I/O例程 Dispatch (派遣)
IoCompleteRequest 表示所有的处理完成 DpcForIsr (连接中断和注册)
IoStartNextPacket 发送下一个IRP到Start I/O例程 DpcForIsr
IoCallDriver 发送IRP请求 Dispatch
IoAllocateIrp 请求另外的IRP Dispatch
IoFreeIrp 释放驱动程序分配的IRP I/O Completion (I/0完成)
IRP堆栈:
名称 描述 调用者
IoGetCurrentIrpStackLocation 得到调用者堆栈的指针 Dispatch
IoMarkIrpPending 为进一步的处理标记调用者I/O堆栈 Dispatch
IoGetNextIrpStackLocation 得到下一个驱动程序的I/O堆栈的指针 Dispatch
IoSetNextIrpStackLocation 将I/O堆栈指针压入堆栈 Dispatc
在驱动程序中,IRP 派遣例程起着很重要的作用,每个 IRP 派遣例程,几乎都有对应的Win32函数,下面是几个常用的:
IRP派遣例程:
名称 描述 调用者
IRP_MJ_CREATE 请求一个句柄 CreateFile
IRP_MJ_CLEANUP 在关闭句柄时取消悬挂的IRP CloseHandle
IRP_MJ_CLOSE 关闭句柄 CloseHandle
IRP_MJ_READ 从设备得到数据 ReadFile
IRP_MJ_WRITE 传送数据到设备 WriteFile
IRP_MJ_DEVICE_CONTROL 控制操作(利用IOCTL宏) DeviceIoControl
IRP_MJ_INTERNAL_DEVICE_CONTROL 控制操作(只能被内核调用) N/A
IRP_MJ_QUERY_INFORMATION 得到文件的长度 GetFileSize
IRP_MJ_SET_INFORMATION 设置文件的长度 SetFileSize
IRP_MJ_FLUSH_BUFFERS 写输出缓冲区或丢弃输入缓冲区 FlushFileBuffers FlushConsoleInputBuffer PurgeComm
IRP_MJ_SHUTDOWN 系统关闭 InitiateSystemShutdown
=================================================================================================================================
先介绍一下开始写我们的驱动版的“Hello World”的流程,程序很简单:
1,用 DriverEntry 驱动入口函数来将内核文件加载到系统文件中进行驱动化。
2,定义一个具有返回值的函数,来完成创建设备和相关功能等操作。
3,调用 IoCreateDevice() API函数来创建一个设备,并返回一个设备对象。
4,调用 IoCreateSynbolicLink() API函数来创建一个符号连接,使Win32程序可以使用驱动程序。
5,调用 RtlInitUnicodeString API 函数来初始化定义的设备名称和符号链接。
6,调用 IoCreateSymbolicLink API 函数将符号链接和设备进行绑定。
7,用 siwitch 语句来判断是否绑定成功,!NT_STATUS(Status)
8,定义一个具有返回值的函数,来完成 IRP 的派遣和完成的操作。
9,在在入口函数 DriverEntry 中注册派遣函数,设置 IRP_MJ_DEVICE_CONTROL 派遣例程 HelloWorldDispatch()和卸载例程 HelloWorldUnLoad()。如果 Win32 程序使用 DeviceIoControl(),则执行 HelloWorldDispatch() 函数。
10,调用 IoGetCurrentIrpStackLocation() 得到当前调用者的IRP指针。
11,定义一个无符号字符或无符号长整形的变量,来进行判断堆栈上的 IRP 的类型指针。
12,然后创建一个应用程序,用来和驱动进行通信,比如控制台应用程序,在_tmain()函数中编写获取文件句柄的代码。
13,调用 CreateFile 函数打开或创建一个文件,来得到这个文件的句柄。
11,取得IO控制代码,完成后使用IoCompleteRequest()完成IRP操作,如果使用ControlService()停止驱动程序,则执行HelloWorldUnLoad()函数。
10,调用IoDeleteSymbolicLink()删除符号连接。
11,调用IoDeleteDevice()删除已建立的设备 。
=================================================================================================================================
下面介绍代码循序:
驱动入口 DriverEntry()
//创建设备
IoCreateDevice(DriverObject, //驱动程序对象
0, //扩展设备的大小,由于不需要,所以置0
&DeviceNameString, //设备名称
FILE_DEVICE_UNKNOWN, //设备类型
0, //指示设备允许的操作
FALSE, //如果为TRUE,表示只能有一个线程使用该设备,为FALSE,则没有限制
&lpDeviceObject); //返回的设备对象
//创建符号连接
IoCreateSymbolicLink(&DeviceLinkString, //存放符号连接的UNICODE_STRING
&DeviceNameString); //设备名称
//注册派遣例程和卸载例程
DriverObject->MajorFunction[IRP_MJ_CREATE]=
DriverObject->MajorFunction[IRP_MJ_CLOSE]=
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=HelloWorldDispatch;
DriverObject->DriverUnload=HelloWorldUnLoad;
IRP派遣例程HelloWorldDispatch()
IrpStack=IoGetCurrentIrpStackLocation(pIrp); //得到当前调用者的IRP堆栈
//获取IO控制代码,并执行指定操作,这里只是DbgPrint()
IoControlCodes=IrpStack->Parameters.DeviceIoControl.IoControlCode;
switch (IoControlCodes)
{
......
IoCompleteRequest(pIrp,IO_NO_INCREMENT); //完成IRP操作
卸载例程HelloWorldUnLoad()
IoDeleteSymbolicLink(&DeviceLinkString); //删除符号连接
IoDeleteDevice(DriverObject->DeviceObject); //删除设备
=================================================================================================================================
完整代码:
- #ifndef __HELLOWORLD_C__
- #define __HELLOWORLD_C__
- #define DEBUGMSG
- #include <ntddk.h>
- #define DEVICE_HELLO_INDEX 0x860
- //2个IOCTL宏
- #define START_HELLPWORLD CTL_CODE(FILE_DEVICE_UNKNOWN,DEVICE_HELLO_INDEX,METHOD_BUFFERED,FILE_ANY_ACCESS)
- #define STOP_HELLPWORLD CTL_CODE(FILE_DEVICE_UNKNOWN,DEVICE_HELLO_INDEX+1,METHOD_BUFFERED,FILE_ANY_ACCESS)
- #define NT_DEVICE_NAME L"\\Device\\HelloWorld" //设备名称
- #define DOS_DEVICE_NAME L"\\DosDevices\\HelloWorld" //符号连接
- NTSTATUS HelloWorldDispatch (IN PDEVICE_OBJECT DeviceObject,IN PIRP pIrp);
- VOID HelloWorldUnLoad (IN PDRIVER_OBJECT DriverObject);
- //驱动入口
- NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath)
- {
- NTSTATUS ntStatus=STATUS_SUCCESS;
- PDEVICE_OBJECT lpDeviceObject=NULL; //指向设备对象的指针
- UNICODE_STRING DeviceNameString={0}; //设备名称
- UNICODE_STRING DeviceLinkString={0}; //符号连接
- //调试信息
- #ifdef DEBUGMSG
- DbgPrint("Starting DriverEntry()\n");
- #endif
- RtlInitUnicodeString(&DeviceNameString,NT_DEVICE_NAME); //初始化Unicode字符串
- //创建设备
- ntStatus=IoCreateDevice(DriverObject,0,&DeviceNameString,FILE_DEVICE_UNKNOWN,0,FALSE,&lpDeviceObject);
- //使用NT_SUCCESS宏检测函数调用是否成功
- if (!NT_SUCCESS(ntStatus))
- {
- #ifdef DEBUGMSG
- DbgPrint("IoCreateDevice() error reports 0x%08X\n",ntStatus);
- #endif
- return ntStatus;
- }
- RtlInitUnicodeString(&DeviceLinkString,DOS_DEVICE_NAME);
- //创建符号连接
- ntStatus=IoCreateSymbolicLink(&DeviceLinkString,&DeviceNameString);
- if (!NT_SUCCESS(ntStatus))
- {
- #ifdef DEBUGMSG
- DbgPrint("IoCreateSymbolicLink() error reports 0x%08X\n",ntStatus);
- #endif
- if (lpDeviceObject)
- IoDeleteDevice(lpDeviceObject);
- return ntStatus;
- }
- //设置IRP派遣例程和卸载例程
- DriverObject->MajorFunction[IRP_MJ_CREATE]=
- DriverObject->MajorFunction[IRP_MJ_CLOSE]=
- DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=HelloWorldDispatch;
- DriverObject->DriverUnload=HelloWorldUnLoad;
- return ntStatus;
- }
- NTSTATUS HelloWorldDispatch (IN PDEVICE_OBJECT DeviceObject,IN PIRP pIrp)
- {
- NTSTATUS ntStatus=STATUS_SUCCESS;
- PIO_STACK_LOCATION IrpStack=NULL; //IRP堆栈
- ULONG IoControlCodes=0; //I/O控制代码
- //设置IRP状态
- pIrp->IoStatus.Status=STATUS_SUCCESS;
- pIrp->IoStatus.Information=0;
- #ifdef DEBUGMSG
- DbgPrint("Starting HelloWorldDispatch()\n");
- #endif
- IrpStack=IoGetCurrentIrpStackLocation(pIrp); //得到当前调用者的IRP
- switch (IrpStack->MajorFunction)
- {
- case IRP_MJ_CREATE:
- #ifdef DEBUGMSG
- DbgPrint("IRP_MJ_CREATE\n");
- #endif
- break;
- case IRP_MJ_CLOSE:
- #ifdef DEBUGMSG
- DbgPrint("IRP_MJ_CLOSE\n");
- #endif
- break;
- case IRP_MJ_DEVICE_CONTROL:
- #ifdef DEBUGMSG
- DbgPrint("IRP_MJ_DEVICE_CONTROL\n");
- #endif
- //取得I/O控制代码
- IoControlCodes=IrpStack->Parameters.DeviceIoControl.IoControlCode;
- switch (IoControlCodes)
- {
- //启动
- case START_HELLPWORLD:
- DbgPrint("Starting \"Hello World\"\n");
- break;
- //停止
- case STOP_HELLPWORLD:
- DbgPrint("Stoping \"Hello World\"\n");
- break;
- default:
- pIrp->IoStatus.Status=STATUS_INVALID_PARAMETER;
- break;
- }
- break;
- default:
- break;
- }
- ntStatus=pIrp->IoStatus.Status;
- IoCompleteRequest(pIrp,IO_NO_INCREMENT);
- return ntStatus;
- }
- VOID HelloWorldUnLoad (IN PDRIVER_OBJECT DriverObject)
- {
- UNICODE_STRING DeviceLinkString={0};
- PDEVICE_OBJECT DeviceObjectTemp1=NULL;
- PDEVICE_OBJECT DeviceObjectTemp2=NULL;
- #ifdef DEBUGMSG
- DbgPrint("Starting HelloWorldUnLoad()\n");
- #endif
- RtlInitUnicodeString(&DeviceLinkString,DOS_DEVICE_NAME);
- if (DeviceLinkString.Buffer)
- IoDeleteSymbolicLink(&DeviceLinkString);
- if (DriverObject)
- {
- DeviceObjectTemp1=DriverObject->DeviceObject;
- while (DeviceObjectTemp1)
- {
- DeviceObjectTemp2=DeviceObjectTemp1;
- DeviceObjectTemp1=DeviceObjectTemp1->NextDevice;
- IoDeleteDevice(DeviceObjectTemp2);
- }
- }
- }
- #endif
用户态程序:
- #define DEBUGMSG
- #include <windows.h>
- #include <winioctl.h>
- #include <stdio.h>
- #define DEVICE_FILTER_INDEX 0x860
- #define START_HELLPWORLD CTL_CODE(FILE_DEVICE_UNKNOWN,DEVICE_FILTER_INDEX,METHOD_BUFFERED,FILE_ANY_ACCESS)
- #define STOP_HELLPWORLD CTL_CODE(FILE_DEVICE_UNKNOWN,DEVICE_FILTER_INDEX+1,METHOD_BUFFERED,FILE_ANY_ACCESS)
- #define erron GetLastError()
- #define MY_DEVICE_NAME "\\\\.\\HelloWorld"
- #define MY_DEVICE_START "-start"
- #define MY_DEVICE_STOP "-stop"
- BOOL DriverControl (TCHAR *Maik);
- void Usage (TCHAR *Paramerter);
- int main (int argc,TCHAR *argv[])
- {
- if (argc!=2)
- {
- Usage(argv[0]);
- return 0;
- }
- if (strcmpi(argv[1],MY_DEVICE_START)==0 || strcmpi(argv[1],MY_DEVICE_STOP)==0)
- DriverControl(argv[1]);
- else
- {
- Usage(argv[0]);
- return 0;
- }
- return 0;
- }
- BOOL DriverControl (TCHAR *Maik)
- {
- HANDLE hDevice=NULL; //设备句柄
- //获得设备句柄
- hDevice=CreateFile(MY_DEVICE_NAME,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
- if (hDevice==INVALID_HANDLE_VALUE)
- {
- #ifdef DEBUGMSG
- printf("CreateFile() GetLastError reports %d\n",erron);
- #endif
- return FALSE;
- }
- //启动
- if (strcmpi(Maik,MY_DEVICE_START)==0)
- {
- //传递启动的I/O控制代码
- if (!(DeviceIoControl(hDevice,START_HELLPWORLD,NULL,0,NULL,0,NULL,NULL)))
- {
- #ifdef DEBUGMSG
- printf("DeviceIoControl() GetLastError reports %d\n",erron);
- #endif
- CloseHandle(hDevice);
- return FALSE;
- }
- }
- //停止
- if (strcmpi(Maik,MY_DEVICE_STOP)==0)
- {
- //传递停止的I/O控制代码
- if (!(DeviceIoControl(hDevice,STOP_HELLPWORLD,NULL,0,NULL,0,NULL,NULL)))
- {
- #ifdef DEBUGMSG
- printf("DeviceIoControl() GetLastError reports %d\n",erron);
- #endif
- CloseHandle(hDevice);
- return FALSE;
- }
- }
- if (hDevice)
- CloseHandle(hDevice); //关闭句柄
- return TRUE;
- }
- void Usage (TCHAR *Paramerter)
- {
- fprintf(stderr,"============================================================================\n"
- " 驱动版Hello World\n"
- "作者:dahubaobao[E.S.T]\n"
- "主页:www.eviloctal.com\n"
- "OICQ:382690\n\n"
- "%s -start\t启动\n"
- "%s -stop \t停止\n\n"
- "本程序只是用做代码交流,如有错误,还请多多包含!\n"
- "============================================================================\n"
- ,Paramerter,Paramerter);
- }