首页 电脑 电脑学堂 查看内容

驱动版HelloWorld

2009-8-24 10:12 797 0

摘要: 我们学习程序设计,都是从“Hello World”开始的,驱动程序也不例外,今天我就写一个驱动版的“Hello World”来热热身,目的希望大家能对驱动程序的基本框架有所了解。 驱动程序分为2类,...
关键词: pos 内核 asdf struct static include count file 文件 主设备

我们学习程序设计,都是从“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首部: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               同上,一般文件系统和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整体:     名称                            描述                                   调用者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堆栈:    名称                                             描述                         调用者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,调用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()//创建设备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); ====================================================================================================完整代码:#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>buildBUILD: Object root set to: ==> objfreBUILD: /i switch ignoredBUILD: Compile and Link for i386BUILD: 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 directoryCompiling - helloworld.c for i386BUILD: Linking c:\helloworld directoryLinking Executable - i386\helloworld.sys for i386BUILD: 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这里有一个完整的驱动安装程序,所以我就不写了,只给出操作驱动程序的代码 完整代码: #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"             "主页:www.eviloctal.com or www.ringz.org\n"             "邮件:[email protected]\n"             "OICQ:382690\n\n"             "%s -start\t启动\n"             "%s -stop \t停止\n\n"             "本程序只是用做代码交流,如有错误,还请多多包含!\n"             "============================================================================\n"             ,Paramerter,Paramerter);} 头文件HelloWorld.h代码: #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 驱动程序设计》
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系 [邮箱地址] 删除

路过

雷人

握手

鲜花

鸡蛋

最新评论

返回顶部