驱动版Hello World(转)

本文介绍了一个简单的内核模式驱动程序示例——驱动版HelloWorld。通过这个示例,读者可以了解驱动程序的基本框架,包括创建设备、设置IRP派遣例程、与驱动通信等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 
  
驱动版Hello World

我们学习程序设计,都是从“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()一共有2个参数:1PDRIVER_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部分:1)IRP首部;2)IRP堆栈。IRP首部信息如下:

IRP首部:
Copy code

IO_STATUS_BLOCK IoStatus        包含I/O请求的状态
PVOID AssociatedIrp.SystemBuffer 如果执行缓冲区I/O,这个指针指向系统缓冲区
PMDL MdlAddress      如果直接I/O,这个指针指向用户缓冲区的存储器描述符表
PVOID UserBuffer      I/O缓冲区的用户空间地址


IRP堆栈:
Copy code

UCHAR MajorFunction            指示IRP_MJ_XXX派遣例程
UCHAR MinorFunction            同上,一般文件系统和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参数
ULONG OutputBufferLength
ULONG InputBufferLength
ULONG IoControlCode
PVOID Type3InputBuffer
}
PDEVICE_OBJECT DeviceObject      请求的目标设备对象的指针
PFILE_OBJECT FileObject            请求的目标文件对象的指针,如果有的话


操作IRP。对于不同的IRP函数,操作也是不同的:有的只操作IRP首部;有的只操作IRP堆栈;还有操作IRP整体,下面是一些常用的函数:

IRP整体:
Copy code

   名称                    描述                        调用者
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


IRP堆栈:
Copy code

   名称                                描述                  调用者
IoGetCurrentIrpStackLocation    得到调用者堆栈的指针          Dispatch
IoMarkIrpPending          为进一步的处理标记调用者I/O堆栈 Dispatch
IoGetNextIrpStackLocation 得到下一个驱动程序的I/O堆栈的指针 Dispatch
IoSetNextIrpStackLocation      将I/O堆栈指针压入堆栈          Dispatc


在驱动程序,IRP派遣例程起着很重要的作用,每个IRP派遣例程,几乎都有对应的Win32函数,下面是几个常用的:

IRP派遣例程:
Copy code

   名称                          描述                    调用者
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,调用IoCreateDevice()创建一个设备,并返回一个设备对象。
2,调用IoCreateSynbolicLink()创建一个符号连接,使Win32程序可以使用驱动程序
3,设置IRP_MJ_DEVICE_CONTROL派遣例程HelloWorldDispatch()和卸载例程HelloWorldUnLoad()。

如果Win32程序使用DeviceIoControl(),则执行HelloWorldDispatch()函数
4,调用IoGetCurrentIrpStackLocation()得到当前调用者的IRP指针
5,取得IO控制代码,完成后使用IoCompleteRequest()完成IRP操作

如果使用ControlService()停止驱动程序,则执行HelloWorldUnLoad()函数
4,调用IoDeleteSymbolicLink()删除符号连接
5,调用IoDeleteDevice()删除已建立的设备

驱动入口DriverEntry()
Copy code

//创建设备
IoCreateDevice(DriverObject,      //驱动程序对象
           0,              //扩展设备的大小,由于不需要,所以置0
           &DeviceNameString,    //设备名称
           FILE_DEVICE_UNKNOWN, //设备类型
           0,              //指示设备允许的操作
           FALSE,            //如果为TRUE,表示只能有一个线程使用该设备,为FALSE,则没有限制
           &lpDeviceObject);    //返回的设备对象

//创建符号连接
IoCreateSymbolicLink(&DeviceLinkString,    //存放符号连接的UNICODE_STRING
               &DeviceNameString); //设备名称

//派遣例程和卸载例程
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);


====================================================================================================完整代码:
Copy code

#ifndef __HELLOWORLD_C__
#define __HELLOWORLD_C__

#define DEBUGMSG

#include "HelloWorld.h"

//驱动入口
NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath)
{
   NTSTATUS ntStatus=STATUS_SUCCESS;
   PDEVICE_OBJECT lpDeviceObject=NULL;    //指向设备对象的指针
   UNICODE_STRING DeviceNameString;      //设备名称
   UNICODE_STRING DeviceLinkString;      //符号连接

   //调试信息
   #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("Error IoCreateDevice()/n");
     #endif
     goto Error;
   }

   RtlInitUnicodeString(&DeviceLinkString,DOS_DEVICE_NAME);

   //创建符号连接
   ntStatus=IoCreateSymbolicLink(&DeviceLinkString,&DeviceNameString);

   if (!NT_SUCCESS(ntStatus))
   {
     #ifdef DEBUGMSG
           DbgPrint("Error IoCreateSymbolicLink()/n");
     #endif
     goto Error;
   }

   //设置IRP派遣例程和卸载例程
   DriverObject->MajorFunction[IRP_MJ_CREATE]=//HelloWorldDispatch;
   DriverObject->MajorFunction[IRP_MJ_CLOSE]=//HelloWorldDispatch;
   DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=HelloWorldDispatch;
   DriverObject->DriverUnload=HelloWorldUnLoad;

   return ntStatus;

   Error:
       #ifdef DEBUGMSG
           DbgPrint("Error DriverEntry()/n");
       #endif

       return ntStatus;
}

NTSTATUS HelloWorldDispatch (IN PDEVICE_OBJECT DeviceObject,IN PIRP pIrp)
{
   NTSTATUS ntStatus=STATUS_SUCCESS;
   ULONG IoControlCodes=0;          //I/O控制代码
   PIO_STACK_LOCATION IrpStack=NULL;    //IRP堆栈

   //设置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;
   PDEVICE_OBJECT DeviceObjectTemp1=NULL;
   PDEVICE_OBJECT DeviceObjectTemp2=NULL;

   #ifdef DEBUGMSG
         DbgPrint("Starting HelloWorldUnLoad()/n");
   #endif

   RtlInitUnicodeString(&DeviceLinkString,DOS_DEVICE_NAME);
   IoDeleteSymbolicLink(&DeviceLinkString); //删除符号连接

   if (DriverObject)
   {
       DeviceObjectTemp1=DriverObject->DeviceObject;

       //删除设备
       while (DeviceObjectTemp1)
       {
           DeviceObjectTemp2=DeviceObjectTemp1;
           DeviceObjectTemp1=DeviceObjectTemp1->NextDevice;
           IoDeleteDevice(DeviceObjectTemp2);
       }
   }
}

#endif


====================================================================================================
驱动程序的编译需要使用DDK中的build实用程序,它是一个命令行程序,使用不是很方便。VC知识库有一篇在VC++ 6.0中编译驱动的文章,有兴趣可以去看看。

1,makefile
编译驱动程序,首先应该准备一个makefile,这个文件很简单,只有一句代码:
#
# DO NOT EDIT THIS FILE!!! Edit ./sources. if you want to add a new source
# file to this component. This file merely indirects to the real make file
# that is shared by all the driver components of the Windows NT DDK
#

!INCLUDE $(NTMAKEENV)/makefile.def

正如描述的那样,不要修改这个文件---它是通用的!

2,sources
准备的第二个文件就是sources,它描述了一些编译的细节。针对本文的程序,sources文件的内容是这样的:
TARGETNAME=HelloWorld    //驱动名称
TARGETPATH=.          //编译后SYS的路径
TARGETTYPE=DRIVER      //类型为驱动程序

SOURCES= HelloWorld.c    //只有一个源文件

有了这2个文件后,就可以使用build进行编译了。进入「开始」菜单/程序/Development Kits/Windows 2000 DDK,
分别有3个CMD程序:1)Checked 64 Bit Build Environment,“Debug”的64位版本;2)Checked Build Environment
“Debug”的32位版本;3)Free Build Environment,“Release”的32位版本。不用说,肯定是使用Free Build Environment。

New or updated MSVC detected. Updating DDK environment....

Setting environment for using Microsoft Visual C++ tools.
Starting dirs creation...Completed.

C:/NTDDK>cd/

C:/>cd HelloWorld

C:/HelloWorld>build
BUILD: Object root set to: ==> objfre
BUILD: /i switch ignored
BUILD: Compile and Link for i386
BUILD: Loading c:/NTDDK/build.dat...
BUILD: Computing Include file dependencies:
BUILD: Examining c:/helloworld directory for files to compile.
   c:/helloworld - 1 source files (127 lines)
BUILD: Saving c:/NTDDK/build.dat...
BUILD: Compiling c:/helloworld directory
Compiling - helloworld.c for i386
BUILD: Linking c:/helloworld directory
Linking Executable - i386/helloworld.sys for i386
BUILD: Done

   1 file compiled
   1 executable built

C:/HelloWorld>

现在C:/HelloWorld/i386目录下,就有了HelloWorld.sys。

====================================================================================================
驱动程序的安装如同安装服务一样,唯一不同的是,创建服务时,类型是内核驱动,其他跟操作服务没什么区别。

安装驱动程序流程:
1,调用OpenSCManager()打开服务控制管理器
2,调用CreateService()创建一个服务,服务类型为内核驱动
3,调用OpenService()取得服务句柄
启动服务
4,调用StartService()启动服务
停止服务
4,调用ControlService()停止服务
删除服务
4,调用DeleteService()删除服务
5,调用CloseServiceHandle()关闭服务句柄

操作驱动程序流程:
1,调用CreateFile()取得设备句柄
2,调用DeviceIoControl()传递I/O控制代码
3,调用CloseHandle()关闭设备句柄

http://www.xfocus.net/tools/200411/882.html
这里有一个完整的驱动安装程序,所以我就不写了,只给出操作驱动程序的代码

完整代码:
Copy code

#define DEBUGMSG

#include <windows.h>
#include <winioctl.h>
#include <stdio.h>

#define DEVICE_HELLO_INDEX 0x860

#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 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; //设备句柄
   DWORD RetBytes=0;

   //获得设备句柄
   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,&RetBytes,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,&RetBytes,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[EST]/n"
         "主页:[url]www.eviloctal.com[/url] or [url]www.ringz.org[/url]/n"
         "邮件:[email]dahubaobao@eviloctal.com[/email]/n"
         "OICQ:382690/n/n"
         "%s -start/t启动/n"
         "%s -stop /t停止/n/n"
         "本程序只是用做代码交流,如有错误,还请多多包含!/n"
         "============================================================================/n"
         ,Paramerter,Paramerter);
}

 

头文件HelloWorld.h代码:
Copy code

#ifndef __HELLOWORLD_H__
#define __HELLOWORLD_H__

#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);

#endif


====================================================================================================

参考资料:

《Windows 2000 DDK》

《Windows 2000 驱动程序设计》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值