zoukankan      html  css  js  c++  java
  • 驱动程序简单开发

    我们学习程序设计,都是从“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个参数: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部分: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和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  

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

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

    完整代码: 

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

    驱动程序的编译需要使用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 
    这里有一个完整的驱动安装程序,所以我就不写了,只给出操作驱动程序的代码 

    完整代码: 

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

    参考资料 

    《Windows 2000 DDK》 

    《Windows 2000 驱动程序设计》 


    附录代码: 


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



  • 相关阅读:
    2.变量
    1.注释
    MyEclipse使用教程:使用DevStyle增强型启动
    DevExpress WPF v19.1新版亮点:Data Editors等控件新功能
    测试工具Telerik Test Studio发布R2 2019|支持VS 2019
    MyEclipse使用教程:使用主题自定义工作台外观
    DevExpress WPF v19.1:Data Grid/Tree List等控件功能增强
    知名界面类控件Kendo UI for jQuery R2 2019 SP1发布|附下载
    MyEclipse使用教程:unattended安装
    跨平台开发框架DevExtreme v19.1.4正式发布|附下载
  • 原文地址:https://www.cnblogs.com/zmlctt/p/3978267.html
Copyright © 2011-2022 走看看