zoukankan      html  css  js  c++  java
  • 驱动程序入门篇

    驱动程序分为两类: 一个是 Kernel(内核) 模式驱动,另一个是 Windows (用户窗口层)模式驱动。

    这两种模式本质是相同,但细节不同。本文介绍的是内核模式驱动和驱动程序的安装与使用。

    驱动程序同普通的 .exe,.dll 一样,都属于 PE 文件,而且都有一个入口函数。但 .exe 中,入口函数是 main() / WinMain() 和 Unicode 的 wmain() / wWinmain(),.dll 的入口函数则可有可无,它是 DllMain()。

    所以驱动程序也有入口函数,而且是必须的,它是 DriverEntry() 函数。再次提示,它是必须的! 因为 I/O 管理器会首先调用驱动程序的DriverEntry() 函数,它的作用就像 DllMain() --完成一些初始化工作。

    虽然我们有时候把 DriverEntry() 比作 main(),但二者在本质上不同,DriverEntry() 的生命周期非常短,其作用仅是将内核文件镜像加载到系统中时进行驱动初始化,调用结束后驱动程序的其他部分依旧存在,并不随它而终止。

    所以我们一般可把 DriverEntry() 称为“入口函数”,而不可称为“主函数”。因此作为内核驱动来说,它没有一个明确的退出点,这应该是 atexit 无法在内核中实现的原因吧。什么是 atexit() 函数呢?它是注册终止函数(即main执行结束后调用的函数) !

    DriverEntry()一共有 2 个参数:

               1)PDRIVER_OBJECT DriverObject,指向驱动程序对象的指针。我们操作驱动程序,全靠它,它是由 I/O 管理器传递进来的;

               2)PUNICODE_STRING RegistryPath,驱动程序的服务主键,这个参数的使用并不多,但要注意,在DriverEntry() 返回后,它可能
    会消失,所以如果需要使用,记住先要保存下来

    DriverEntry() 等内核函数的返回值一般是 NTSTATUS 类型的。使用宏NT_SUCCESS(NTSTATUS status)可检测返回状态是否成功。它是一个 ULONG 值(无符号长整型,另一种表示形式:Unsigned long  变量名),具体的定义,请参见 DDK 中的 NTSTATUS.H 头文件,里边有详细的定义。

    在DriverEntry中,一般需要做一下几件事情: 设置驱动卸载例程、 注册 IRP 的派遣函数、 创建设备对象等!并且驱动卸载例程与 IRP 派遣函数都是对驱动对象设置的。

    既然要写驱动程序就需要确定如何来与驱动程序通信,常用的共享内存,共享事件,IOCTL宏,或者直接 ReadFile() 或 WriteFile() 进行读写,在本文里我就采用一种简单的、但又很常用的 IOCTL 宏,它依赖的 IRP 派遣例程 IRP_MJ_DEVICE_CONTROL,Win32程序使用 DeviceIoControl() 与驱动进行通信,根据不同的 IOCTL 宏,输出不同的调试信息。

    驱动程序与 I/O 管理器通信,使用的是 IRP,即 I/O 请求包。IRP 分为2部分:IRP 首部;IRP堆栈。

    在驱动程序中,IRP 派遣例程起着很重要的作用,每个 IRP 派遣例程,几乎都有对应的 Win32 API 函数。

    下面介绍 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 写输出缓冲区或丢弃输入缓冲区 FlushFileBuffersFlushConsoleInputBuffer PurgeComm
    IRP_MJ_SHUTDOWN 系统关闭 InitiateSystemShutdown

    提到派遣例程,必须理解 IRP(I/O Request Package),即"输入/输出请求包"这个重要数据结构的概念。Ring3(用户层)通过 DeviceIoControl 等函数向驱动发出 I/O 请求后,在内核中由操作系统将 I/O 请求包转化为 IRP 的数据结构,并"派遣"到对应驱动的派遣函数中,如图下图所示。

    Ring3(用户层)应用程序调用kernel32.dll导出的DeviceIoControl函数后,会调用到ntdll.dll导出的NtDeviceIoControlFile函数,进而调用到系统内核模块提供的服务函数NtDeviceIo ControlFile,该函数会将I/O请求转化为IRP包,并发送到对应驱动的派遣例程函数中。对于其他I/O相关函数,如CreateFile、ReadFile、WriteFile、GetFileSize、SetFileSize、CloseHandle等也是如此。

                                                                新建位图图像

    如上图,从 Ring3 (用户层)的 I/O 请求到内核的 IRP 结构的请求包

    一个 IRP包 该发往驱动的哪个派遣例程函数,是由 IRP 结构中的 MajorFunction (IRP主类型)属性决定的,MajorFunction 属性的值是一系列宏,具体可在网上查询。

    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堆栈指针压入堆栈 Dispatch

    /************************************************************************************************
    *                                 驱动程序与应用程的通讯示例                                                                                                     *
    *************************************************************************************************
    * 应用程序类型:ControlAppliaction                                                                                                                             *
    * 应用程序名称:Appinformation                                                                                                                                 *
    * 驱  动 名 称:AppDriver                                                                                                                                           *
    * 符号链接名称:AppSymbolLink                                                                                                                                 *
    *************************************************************************************************
    * 简述:编写一个驱动程序与应用程之间的通信讯实例,让应用程序输入数据,进而发送 I/O 请求,当                                             *
    * 系统接的 I/O 管理器在接收到应用层的设备读写请求后,将请求封装为一个 IRP 请求(包括 IRP 头部                                            *
    * 和 IRP STACK_LOCATINO 数组),然后根据 IRP 的类型发送到相应类型派遣例程,派遣例程再把接                                           *
    * 受到的 IRP发送到对应的设备的设备栈的最顶层的那个设备驱动(如果顶层的设备对象的派遣函数结                                              *
    * 束了IRP的请求,则这次IO请求结束,如果没有将 IRP 请求结束,那么操作系统将IRP转发到设备栈的                                           *
    * 下一层设备处理。如果这个设备的派遣函数依然没结束该IRP请求,则继续向下层设备进行转发)。                                               *
    * IO请求结束后驱动会把处理后的数据返回应用程序。                                                                                                         *
    *************************************************************************************************/

    先介绍一下驱动程序和应用程序将要使用到的 Win32 API 函数:

    1、DriverEntry(): 驱动程序的入口函数,它将内核文件镜像加载到系统中时进行驱动初始化。

    2、DriverUnload():负责删除在DriverEntry中创建的设备对象并且将设备对象所关联的符号链接删除,另外,DriverUnload还负责对一些资源进行回收。

    3、IoDeleteDevice():此 API 函数为删除设备对象的函数,只有一个参数,即为妖卸载的设备名称。

    4、IoDeleteSymbolicLink() : 此 API 函数为删除符号链接的函数,只有一个参数,即为符号链接名称。

    5、IoCreateDevice():此 API 函数创建设备对象供驱动程序使用。包含七个参数。

    6、RtlInitUnicodeString():此 API 函数初始化一个Unicode字符串,主要用来实例化设备名称或符号链接名称。

    7、IoCreateSymbolicLink():此 API 函数主要是对已实例化的设备名称和符号链接名称进行绑定。参数为符名和设备名

    8、IoGetCurrentIrpStackLocation():此 API 函数向调用者返回一个 IRP 当前堆栈位置的指针,参数为设备对象和 IRP 指针(PIRP)。

    9、IoCompleteRequest():此 API 函数表示调用方已完成所有 I/O 请求处理操作,并将给定的 IRP 传给 I/O 管理器。无返回值。

    10、CTL_CODE() 宏:此宏为系统提供的,在wdm中定义的,用于创建一个唯一的32位系统I/O控制代码,这个控制代码包括4部分组成。

    11、CreateFile(): 此函数创建或打开(文件,文件流,目录,磁盘,控制台缓冲区)等设备,并返回一个用来访问这些对象的句柄 。

    12、DeviceIoControl():  此函数直接发送一个控制代码到指定的设备驱动程序,使相应的设备来执行相应的操作。

    13、CloseHandle():此函数关闭一个打开的对象句柄,该对象句柄可以是线程句柄,也可以是进程、信号量等其他内核对象的句柄 。  

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

    下面请看驱动程序代码:

    首先用 C++ 创建一个空白的“ 控制台应用程序”项目:然后新建一个源文件:Appinformation.cpp

    代码如下:

    #include <iostream>
    #include <tchar.h>
    #include <Windows.h>
    #include "InputBuffer.h"
    using std::cout;
    using std::endl;
    
    int _tmain(int argc, _TCHAR* argv[])
    {    
        /************************************函数说明*********************************************
        * 函数名:CreateFile                                                                     *
        * 类型:Win32 API函数                                                                    *
        * 作用:这是一多功能的函数,可打开或创建如:控制台,通信资源,目录(只读打开),                    *
        * 磁盘驱动器,文件,邮槽,管道等对象,并返回可访问的句柄:。                                     *
        * 参数说明:                                                                             *
        * LPCTSTR lpFileName : 普通文件名或者设备文件名                                            *
        * DWORD dwDesiredAccess : 访问对象的权限,希望以哪种方式打开,建议两者兼备。                    *
        * DWORD dwShareMode : 共享模式,建议两者兼备。                                             *
        * LPSECURITY_ATTRIBUTES : 用来设定一个指向是否由子进程返回的句柄可以被继承                     *
        * DWORD dwCreationDisposition : 表示判断文件存在或不存在,按需求如何创建                      *
        * DWORD dwFlagsAndAttributes : 文件标志或设备属性                                         *
        * HANDLE hTemplateFile :模板文件,给创建的文件提供文件属性和扩展属性,一般为空                   *
        * 返回值: 获取设备的句柄                                                                   *
        *****************************************************************************************/
        HANDLE  DeviceHandle = CreateFile(
            L"\\.\AppSymbolLink",              //\??\设备符号链接名称,注意这里要用 L 宽字符转换
            GENERIC_READ | GENERIC_WRITE,    
            FILE_SHARE_READ | FILE_SHARE_WRITE,   
            NULL,                             
            OPEN_EXISTING,                        //枚举:OPEN_EXISTING 表示文件必须已经存在。
            FILE_ATTRIBUTE_NORMAL,                 //此枚举表示文件没有其他属性集。
            NULL);                                 //模板文件,一般为空
        if (DeviceHandle == INVALID_HANDLE_VALUE){
            printf("获取文件或设备句柄失败!!");
            getchar();                            //读取清除缓冲区字符
            return -1;
        }
    
            printf("获取驱动程序句柄成功 
    ");
            int a=10, b=20, c=30,e=0,f=0,g=0;
            InputBuffer(DeviceHandle, a, b, c);
            for (int i = 0; i < 3; i++)
            {
                switch (i){
                case 0:{
                           e = OtnC[i];
                }
                case 1:{
                           f = OtnC[i];
                }
                case 2:{
                           g = OtnC[i];
                }
                } //End switch (i)
            }  //End for 
    
        printf("a=%d,b=%d,c=%d
    ",e, f, g);    
        CloseHandle(DeviceHandle);            //过程完成后,最好关闭句柄    
        cout << "驱动通讯完成" << endl;
        system("pause");
    }

    好了,按设备的符号链接打开了设备,并得到了它的句柄,有了句柄,我们就能更好的和它进行互动了!!

    如果想和他通信,就需要发送通讯口令了,我们把发送口令的函数它写在头文件:InputBuffer.h中。

    #pragma once
    #include <iostream>
    #include <Windows.h>
    #include "SetCtlCode.h"
    
    int OtnC[3] = { 0 };  //整形全局数组变量
    
    /************************************函数说明****************************************
    * 函数名:InputBuffer                                                               *
    * 类型:自定义无返回值函数                                                             *
    * 作用:这个函数用于给控制代码函数提供参数,也为了其函数调用获取返回值。                       *                                                                                
    * 参数说明:                                                                         *
    * DeviceHandle : 设备句柄,用于指定通信的设备                                          *
    * a : 通讯数据。                                                                    *
    * b : 通讯数据。                                                                    *
    * c : 通讯数据                                                                      *
    * 返回值: 无 ,因为返回值已赋给了全局变量                                                *
    ************************************************************************************/
    
    void InputBuffer(HANDLE DeviceHandle, int a, int b, int c){
        int ItnC[3] = { a, b, c }; ULONG wtWrite;
        int *InBuffer = ItnC;
        int *OutBuffer = OtnC;
    
        /************************************函数说明********************************
        * 函数名:DeviceIoControl                                                   *
        * 类型:Win32 API函数                                                       *
        * 作用:发送控制代码到指定设备驱动程序。                                         *    
        * 参数说明:                                                                *
        * hDevice Long:设备句柄。用于指定通信的设备                                    *
        * dwIoControlCode Long:应用程序调用驱动程序的控制命令,就是IOCTL_XXX IOCTLs。   *
        * lpInBuffer Any:应用程序传递给驱动程序的数据缓冲区地址。                        *
        * nInBufferSize Long:应用程序传递给驱动程序的数据缓冲区大小,字节数。             *
        * lpOutBuffer Any:驱动程序返回给应用程序的数据缓冲区地址。                       *
        * nOutBufferSize Long:驱动程序返回给应用程序的数据缓冲区大小,字节数。            *
        * lpBytesReturned Long:驱动程序实际返回给应用程序的数据字节数地址。              *
        * lpOverlapped OVERLAPPED:针对同步操作,请用ByVal As Long传递零值              *
        * 返回值: 非0成功,0失败                                                      *
        *****************************************************************************/
    
        DeviceIoControl(DeviceHandle, SetCtlCodeA, InBuffer, sizeof(InBuffer)*3, OutBuffer, sizeof(OutBuffer)* 3, &wtWrite, NULL);           
        printf("DeviceIoControl 发送控制代码完成 
    ");
        int e = 0, f = 0, g = 0;
        for (int i = 0; i < 3; i++)
        {
            OtnC[i] = OutBuffer[i];
        }
        return ;
    }

    看到没! DeviceIoControl 函数中的第二个参数就需要提供口令了,这里专业的说法叫,“I/O控制码”(也叫输入输出控制码),我们把这控制代码卸载另一个头文件中:SetCtlCode.h

    #include <winioctl.h>
    /************************************宏说明***********************************
    * 宏名:CTL_CODE                                                             *
    * 类型:Win32 宏命令                                                          *
    * 作用:用于创建一个唯一的32位系统I/O控制代码。                                   *
    * 参数说明:                                                                 *
    * DeviceType:设备类型 ,枚举如(FILE_DEVICE_UNKNOWN)未知设备                   *
    * Function:  设备唯一功能标识码,有效范围:32768-65535                          *
    * Method:    I/O访问内存的使用方式,枚举,一般用METHOD_BUFFERED。                *
    * Access:    设备访问限制,,枚举,一般用FILE_ANY_ACCESS                        *
    * 返回值:    无                                                              *
    *****************************************************************************/
    //可以只写一个
    #define SetCtlCodeA CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
    #define SetCtlCodeB CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ANY_ACCESS)

    好了,到此,一个发送 I/O 请求包的 控制台应用程序就创建并编写完毕,下面来开始我们的驱动编写:

    先创建一个 WDM 工程,然后添加一个头文件,名为:AppDriver.cpp

    然后开入编写入口函数:

    #ifdef _cplusplus
    extern "C"
    {
    #endif
    #include <ntddk.h>
    #include "AddDevice.h"
    #include "SendIRP.h"
    #ifdef _cplusplus
    }
    #endif
    
    #define PAGEDCODE cod_seg("PAGE")             //将以下函数载入到分页内存中执行
    #pragma PAGEDCODE                              //载入到分页内存中执行
    VOID Unload(PDRIVER_OBJECT pDriverObject);  
    extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistrPath)
    {
        CreateMyDevice(pDriverObject);                                      //调用自定义的 CreateMyDevice 来创建设备对象
    
        //注册派遣函数,与IRP 请求包的数据类型一一对应
        pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = Send_Irp; // 发送I/O管理器和其他系统组件、其他内核模式驱动
        pDriverObject->MajorFunction[IRP_MJ_CREATE] = Send_Irp;         // 创建设备,CreateFile会产生此IRP,返回一个句柄给用户应用程序
        pDriverObject->MajorFunction[IRP_MJ_READ] = Send_Irp;         // 读取设备内容,ReadFile会产生此IRP
        pDriverObject->MajorFunction[IRP_MJ_WRITE] = Send_Irp;       // 改写设备内容,WriteFile时会产生此IRP
        pDriverObject->MajorFunction[IRP_MJ_CLOSE] = Send_Irp;       // 关闭保留的通往目标设备对象的句柄,CloseHandle会产生此IRP
        pDriverObject->MajorFunction[IRP_MJ_CLEANUP] = Send_Irp;    // 清除用户模式应用程序的目标设备对象的句柄,CloseHandle会产生此IRP    
        pDriverObject->DriverUnload = Unload;                               //调用卸载函数    
        KdPrint(("AppDriver 驱动加载成功!"));
        return STATUS_SUCCESS;
    }
    
    VOID Unload(PDRIVER_OBJECT pDriverObject)
    {    
        pDriverObject->DeviceObject = AppDevice;    //指向设备对象
        IoDeleteDevice(AppDevice);                  //卸载设备对象
        IoDeleteSymbolicLink(&AppSymbolLinkName);   //卸载符号链接
        KdPrint(("驱动卸载成功!"));
    }

    入口函数 DriverEntry 的任务包含了 初始化系统代码为驱动指令,还包含了对自定义 Unload 函数的调用和 IRP 请求包的派遣。

    先我们利用 IoCreateDevice  API函数来创建设备驱动,我把它写在另一个头文件中:AddDevice.h

    #pragma once            //仅编译一次,用途:常出现在头文件中,因为同一头文件会在许多源文件中多次引用,如果没有指定编译一次,则编译时出现重定义错误。
    #ifdef _cplusplus
    extern "C"
    {
    #endif 
    #include <ntddk.h>
    #ifdef _cplusplus
    }
    #endif 
    
    static PDEVICE_OBJECT AppDevice;                          //设备对象全局变量
    static UNICODE_STRING AppDeviceName, AppSymbolLinkName;   //设备名称和符号链接名称全局变量
    
    //创建设备
    #define INITCODE cod_seg("INIT")                               // 初始化的时候载入内存,然后可以从内存中卸载掉
    #pragma INITCODE
    NTSTATUS CreateMyDevice(PDRIVER_OBJECT pDriverObject){
        NTSTATUS Status;                                                   //返回值局部变量        
        RtlInitUnicodeString(&AppDeviceName, L"\Device\AppDriver");      //初始化设备名称
        RtlInitUnicodeString(&AppSymbolLinkName, L"\??\AppSymbolLink");  //初始化符号链接名称
            
        //调用 IoCreateDevice API 函数创建设备对象
        Status=IoCreateDevice(pDriverObject, 0, &AppDeviceName, FILE_DEVICE_UNKNOWN, NULL, TRUE, &AppDevice);
    
        
        if (!NT_SUCCESS(Status)){                  //判断返回值,看是否创建成功
            switch (Status){                        //如果没有成功,返回枚举错误消息
            case STATUS_INSUFFICIENT_RESOURCES:
                KdPrint(("资源部足!!"));
                break;
            case STATUS_OBJECT_NAME_EXISTS:
                KdPrint(("指定对象名存在!!"));
                break;
            case STATUS_OBJECT_NAME_COLLISION:
                KdPrint(("对象名称冲突!!"));
                break;
            }        
        }
    
        //设备创建成功后,就设置它的缓冲区读写方式,这里为(读写)
        AppDevice->Flags |= DO_BUFFERED_IO; 
    
        //调用 IoCreateSymbolicLink API 函数对设备名称和符号链接名称进行绑定
        Status=IoCreateSymbolicLink(&AppSymbolLinkName, &AppDeviceName);
        
        //判断返回值,看是否绑定成功
        if (NTSTATUS(Status) != STATUS_SUCCESS){              //判断返回值,看是否绑定成功
            IoDeleteDevice(AppDevice);                        //如果未成功,调用IoDeleteDevice 函数删除设备
            KdPrint(("设备因符号链接绑定失败而被删除!!"));
        }
        KdPrint(("设备创建完成,设备已成功加载"));
        return Status;                                        //如果成功就返回成功的宏
    }

    到此,我们就真正创建了一个驱动,没错确实创建了,但像个人样,但却没有灵魂啊,没有灵魂就没有意识,没有意识就是死的!!!

    所以,我们就得给他构造自主的潜微的意识,让它能对别人的呼叫会有所回应!!这里就要用到 IoGetCurrentStackLocation 这个API函数来接受被人发出的信息(IRP 请求包)派遣,从哪里获取呢,这里所有的信息都存放在 Stack 堆栈上,所以此时就需要在堆栈上获得: 我们再次新建一个头文件SendIRP,在这里面编写:

    #ifdef _cplusplus
    extern "C"
    {
    #endif 
    #include <ntddk.h>
    #include "SetCtlCode.h"
    #ifdef _cplusplus
    }
    #endif 
    
    #define INITCODE cod_seg("INIT")                                 // 初始化的时候载入内存,然后可以从内存中卸载掉
    #pragma INITCODE 
    NTSTATUS Send_Irp(PDEVICE_OBJECT pDeviceObject, PIRP pIrp){
          NTSTATUS Status; PIO_STACK_LOCATION Stack;                 //定义返回值和堆栈变量
          Stack=IoGetCurrentIrpStackLocation(pIrp);                  //获取当前 IRP 在堆栈上的位置指针      
          UCHAR Irp_Type = Stack->MajorFunction;                     //获取 IRP 在堆栈中的类型
          KdPrint(("Irp_Type=%d 
    ", Irp_Type));
    
          //对 IRP 的类型进行判断,进行不同的处理
          switch (Irp_Type){
              case IRP_MJ_DEVICE_CONTROL:{
                                           KdPrint(("In to IRP_MJ_DEVICE_CONTROL 
    "));
                                           ULONG CTL_CODE = Stack->Parameters.DeviceIoControl.IoControlCode;
                                           ULONG InBufferLength = Stack->Parameters.DeviceIoControl.InputBufferLength;
                                           ULONG OutBufferLength = Stack->Parameters.DeviceIoControl.OutputBufferLength;
                                           int *OutBuffer = (int *)pIrp->AssociatedIrp.SystemBuffer;
                                           KdPrint(("Get CTL_CODE,CTL_CODE=%d 
    ",CTL_CODE));
                                           KdPrint(("InBufferLength=%d 
    " , InBufferLength));
                                           KdPrint(("OutBufferLength=%d 
    ", OutBufferLength));
                                           KdPrint(("*OutBuffer=%d 
    ", OutBuffer));
                                           switch (CTL_CODE){
                                               case Add_Ctl_Code:{
                                                                     KdPrint(("In to Add_Ctl_Code 
    "));
                                                                     int i, a, b, c;
                                                                     _asm{                      //用汇编语句来(读取)缓冲区指针中的数据
                                                                         mov eax,OutBuffer
                                                                             mov ebx,[eax]
                                                                             mov a,ebx
                                                                             mov ebx, [eax+4]
                                                                             mov b,ebx
                                                                             mov ebx, [eax+8]
                                                                             mov c,ebx
                                                                     }
                                                                     KdPrint(("a=%d,b=%d,c=%d 
    ",a,b,c));
                                                                     i = a + b + c;
                                                                     KdPrint(("i=a + b + c,i=%d 
    ", i));
                                                                      a =a*i, b=b*i, c=c*i;
                                                                     _asm{                      //用汇编语句来(填充)缓冲区指针
                                                                         mov eax,a
                                                                             mov ebx,OutBuffer
                                                                             mov [ebx],eax
                                                                             mov eax, b
                                                                             mov [ebx + 4],eax                                                                                                                                    
                                                                             mov eax,c
                                                                             mov [ebx + 8],eax                                                                         
                                                                     }
                                                                     pIrp->IoStatus.Information = OutBufferLength;
                                                                     pIrp->IoStatus.Status = STATUS_SUCCESS;
                                                                     IoCompleteRequest(pIrp, IO_NO_INCREMENT);
                                                                     KdPrint(("Leave the Add_Ctl_Code 
    ", i));
                                                                     break;
                                               }
                                               case Sub_Ctl_Code:{
                                                                     break;
                                               }
                                           }  //End (switch (CTL_CODE))
                                           break;
              }
              case IRP_MJ_CREATE:{
                                     break;
              }
              case IRP_MJ_READ:{
                                   break;
              }
              case IRP_MJ_WRITE:{
                                    break;
              }
              case IRP_MJ_CLOSE:{
                                    break;
              }
              case IRP_MJ_CLEANUP:{
                                      break;
              }
    
          }  // End (switch (Irp_Type))
    
          return STATUS_SUCCESS;
    
    }  //End  Send_Irp Function

    此断代码主要对在堆栈中获取其它应用程序发送的 I/O 请求包的 IRP 派遣,驱动程序再更具 IRP 的派遣类型就行不同的处理,然后,得到请求数据,并对它进行了重新计算,然后通过汇编语句对计算后的数据进行反馈给堆栈中,让发送I/O 请求包 的应用程序进行接受数。

    接受 或发送 I/O 请求包 都同样需用 I/O 控制代码,才能完成!所以我们再来编写I/O 控制代码,把它写另一个头文件中:SetCtlCode.h

    #define Add_Ctl_Code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
    #define Sub_Ctl_Code CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ANY_ACCESS)

    由于驱动已经包含了 IOCTL 宏,所以这里不必再包含<winioctl.h>头文件了。

    到此整个应用程序和驱动整形就编写完成! 我们可以在虚拟机制中通过 DriverMonitor .exe 和 DbgView工具进行加载驱动并进行查看,然后打开AppInformation.exe 控制台应用程序进行发送 I/O请求包,就能到接受驱动计算过的数据了!

  • 相关阅读:
    一个好玩的代码生成工具
    正式决定了...
    小评几种O/R Mapping工具
    本期期刊主题:ASP.NET技术与JavaScript技巧,包括控件等
    vs.net2005单元测试的私有方法测试
    如何为一个instance配置多个监听,并且实现Client Load Balancing和Client Load Balancing
    编译告警解决记录
    SourceInsight宏开发
    map
    挖掘G13金矿让150M手机内存变成250M
  • 原文地址:https://www.cnblogs.com/lfls128/p/4987388.html
Copyright © 2011-2022 走看看