应用程序与驱动程序的通讯

应用程序与驱动程序的通信

基本介绍

1.设备与驱动的关系

设备由驱动去创建,访问一个设备,是首先得访问驱动。如果驱动在卸载的时候没有删除符号,r3下也是不能去访问设备的。

2.设备符号链接名与设备名

Ring3不能直接访问设备,需要中间桥梁。这个中间桥梁是Ring0驱动为设备名注册的链接符号(Symbol Link)。

应用程序与驱动程序的通讯

从图中可以看出,R3要访问设备,是通过设备符号名去访问设备的,而R3应用层下的设备符号名和设备管理器中的设备符号名有出入,必须,也只能写成\\.\设备符号名 的方式 ,系统会自动转为\??\设备符号名 ,紧接着通过设备符号名这个中间桥梁可以转化为真正的R0下的设备名。

也就是说,符号链接名在应用层只是简单的符号转化过程,有些类似宏定义,设备符号名主体是相同的。而设备名和设备符号名可以不相同。因为设备符号名仅仅是个中间桥梁。

来使用WinObj.exe 查看一下:

应用程序与驱动程序的通讯

从图上可以看出:设备符号名'\(GLOBAL)??\test 和设备名\Device\MyDevice 可以不一致。

所以有以下说法:

  1. R3下不能直接访问设备,只能用设备符号名去访问设备,但是也不能直接使用\??\设备符号名 去获得设备名。

  2. R3要访问设备,必须使用\\.\设备符号名 ,这个设备符号名由R0给定。

  3. R0下的设备名格式为\Device\自定义设备名

  4. R0下的设备符号名格式为\??\自定义符号名 ,其中自定义符号名自定义设备名 可以不一致。

  5. 可以看出,最终的目的都是为了访问R0下的设备\device\自定义设备名

通信机制

综述

应用程序和驱动程序的通信是通过IRP (i/o request packet) (IO请求包) 来完成的。

//PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
//证明object中含有majorFunction.
//in wdm.h
typedef  _DRIVER_OBJECT  DRIVER_OBJECT;

//in windbg:

lkd> dt _driver_object
nt!_DRIVER_OBJECT
   +0x000 Type             : Int2B
   +0x002 Size             : Int2B
   +0x004 DeviceObject     : Ptr32 _DEVICE_OBJECT
   +0x008 Flags            : Uint4B
   +0x00c DriverStart      : Ptr32 Void
   +0x010 DriverSize       : Uint4B
   +0x014 DriverSection    : Ptr32 Void
   +0x018 DriverExtension  : Ptr32 _DRIVER_EXTENSION
   +0x01c DriverName       : _UNICODE_STRING
   +0x024 HardwareDatabase : Ptr32 _UNICODE_STRING
   +0x028 FastIoDispatch   : Ptr32 _FAST_IO_DISPATCH
   +0x02c DriverInit       : Ptr32     long 
   +0x030 DriverStartIo    : Ptr32     void 
   +0x034 DriverUnload     : Ptr32     void 
   +0x038 MajorFunction    : [28] Ptr32     long   // here

驱动程序通过注册 派遣函数例程 来完成响应,在每一个驱动对象中有一个成员MajorFunction ,这个成员是个数组指针数组,记录了所有已注册例程(Routine,也即函数) 的地址,一旦对应的请求发送给驱动,准确的说应该是发送给由驱动创建的设备,那么此时就会从MajorFunction 中根据请求类型找到对应的处理例程地址,从而处理请求。对于没有注册的例程,MajorFunction中存放了默认的处理函数地址_IopInvalidDeviceRequest

摘自 <windows 驱动开发技术详解> chapter7 派遣函数 p187 pic 7-1
##伪代码
##for other not registered routine :default routine (_IopInvalidDeviceRequest)
foreach othter_not_register index in MajorFunction:
    MajorFunction[index]=&_IopInvalidDeviceRequest

应用层

应用程序通过函数DeviceIoControl() 向设备发送对应的控制码。此类请求被封装成IRP_MJ_CONTROL 类型的请求。因此需要在驱动程序中注册MajorFunction[IRP_MJ_CONTROL 的处理例程。

驱动层

驱动层接收到IRP_MJ_CONTROL 的请求之后会用注册的IRP_MJ_CONTROL例程处理地址 去处理此类请求。注册的函数一般称之为派遣函数
派遣函数参数格式:

最简单的处理过程是:

  • 将参数IRP的状态设置为成功
  • 使用IoCompleteRequest() 结束IRP请求。
  • 该派遣函数返回成功。

相关代码

R3

基本信息

BOOL WINAPI DeviceIoControl(
  _In_        HANDLE       hDevice,     //设备句柄
  _In_        DWORD        dwIoControlCode, //控制请求码,CTL_CODE类型
  _In_opt_    LPVOID       lpInBuffer,
  _In_        DWORD        nInBufferSize,
  _Out_opt_   LPVOID       lpOutBuffer,
  _In_        DWORD        nOutBufferSize,
  _Out_opt_   LPDWORD      lpBytesReturned,
  _Inout_opt_ LPOVERLAPPED lpOverlapped
);
in WinIoCtl.h
#define CTL_CODE( DeviceType, Function, Method, Access ) (                 \
    ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \
)
其中Method 用户可定义范围为: 0x800h-0xFFFh
// that function codes 0-2047 are reserved for Microsoft Corporation, and
// 2048-4095 are reserved for customers.

完整代码

// communicate_client.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <windows.h>
//  device name:
//  "\\.\MyDevice" ,so symbol link is "\??\MyDevice"
#define DEVICE_NAME L"\\\\.\\MyDevice"

#define IOCTL_ON CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_IN_DIRECT,FILE_ANY_ACCESS)
#define IOCTL_OFF CTL_CODE(FILE_DEVICE_UNKNOWN,0x901,METHOD_IN_DIRECT,FILE_ANY_ACCESS)

int _tmain(int argc, _TCHAR* argv[])
{
    //打开句柄,会触发IRP_MJ_CREATE
    //CreateFile可以访问文件,也可以访问设备。正是符号"\\.\"让系统得知要访问的是设备,而不是文件
    HANDLE m_hDevice = CreateFile(DEVICE_NAME,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL    );
    if ( INVALID_HANDLE_VALUE == m_hDevice )
    {
        printf("Can't create this Device.\r\n");
        exit(-1);
    }
    char signal=NULL;
    DWORD dwRet = NULL;
    while(signal =getchar())
    {
        switch (signal)
        {
        case 'h':
             DeviceIoControl(m_hDevice,IOCTL_ON,NULL,0,NULL,0,&dwRet,NULL); //hook it.
            break;
        case 'u':
            DeviceIoControl(m_hDevice,IOCTL_OFF,NULL,0,NULL,0,&dwRet,NULL); //unhook reset
            break;
        case 'q':
        //关闭句柄会触发 IRP_MJ_CLEANUP and IRP_MJ_CLOSE
            CloseHandle(m_hDevice);
            exit(-1);
            break;
        }
    }
    return 0;
}

R0

#include "stdafx.h"
#define DEVNAME L"\\Device\\MyDevice"    
#define LNKNAME L"\\??\\MyDevice"   
#define IOCTL_ON CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_IN_DIRECT,FILE_ANY_ACCESS)
#define IOCTL_OFF CTL_CODE(FILE_DEVICE_UNKNOWN,0x901,METHOD_IN_DIRECT,FILE_ANY_ACCESS)

void communicate_serverUnload(IN PDRIVER_OBJECT DriverObject);


#ifdef __cplusplus
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING  RegistryPath);
#endif

//IRP_MJ_CONTROL 处理例程
NTSTATUS DisPathRoutine(PDEVICE_OBJECT pDevObj,PIRP pIrp)
{
    NTSTATUS Status = STATUS_SUCCESS;
    //得到当前栈
    PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp);
    //获取控制码
    ULONG ulcode = (ULONG) pStack->Parameters.DeviceIoControl.IoControlCode;
    switch(ulcode)
    {
    case IOCTL_ON:
        DbgPrint("recive hook message\r\n");
        break;
    case IOCTL_OFF:
        DbgPrint("recive resest hook message\r\n");
        break;
    }

    //注明已完成IRP
    pIrp->IoStatus.Status = Status;
    pIrp->IoStatus.Information = 0;
    IoCompleteRequest(pIrp,IO_NO_INCREMENT);
    return Status;
}

//IRP_MJ_Create 处理例程,返回成功时,r3才能获得句柄。
NTSTATUS CreateRoutine(PDEVICE_OBJECT pDevice_object,PIRP pIrp)
{
    return STATUS_SUCCESS;

}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING  RegistryPath)
{

    DbgPrint("%p",RegistryPath->Buffer); //结构体指针指向的成员是取值,而不是地址 
    DbgPrint("%S",(ULONG *)RegistryPath->Buffer);
    UNICODE_STRING DevName;
    UNICODE_STRING LnkName;
    RtlInitUnicodeString(&DevName,DEVNAME);
    RtlInitUnicodeString(&LnkName,LNKNAME);
    NTSTATUS status = STATUS_SUCCESS; //STATUS_UNSUCCESSFUL
    PDEVICE_OBJECT pDevObj = NULL;

    DbgPrint("Hello from communicate_server!\n");
    //注册派遣函数例程地址
    pDriverObject->DriverUnload = communicate_serverUnload;
    pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DisPathRoutine;//
    pDriverObject->MajorFunction[IRP_MJ_CREATE] = CreateRoutine;//

    DbgPrint("%S",DevName.Buffer);
    //创建设备
    status =IoCreateDevice(pDriverObject,
                                    0,
                                    &DevName,
                                    FILE_DEVICE_UNKNOWN,
                                    0,
                                    TRUE,
                                    &pDevObj);
    DbgPrint("%08x",status);
    if (!NT_SUCCESS(status))   //昨天写成NT_STATUS(status) 找了一天的错误。。。。  
    {
        DbgPrint("Can't CreateDevice\r\n");

        return status;
    }



    DbgPrint("Have CreateDevice\r\n");

    pDevObj->Flags |= DO_DIRECT_IO;

    //注册符号链接
    status = IoCreateSymbolicLink(&LnkName,&DevName);

    if (!NT_SUCCESS(status))
    {
        DbgPrint("Can't Create SymbolLink");
        IoDeleteDevice(pDevObj);
        return status;
    }
    return status;
}

void communicate_serverUnload(IN PDRIVER_OBJECT DriverObject)
{
    DbgPrint("Goodbye from communicate_server!\n");
    //删除设备,删除符号链接。
    PDEVICE_OBJECT pDev;
    pDev = DriverObject->DeviceObject;
    IoDeleteDevice(pDev);
    UNICODE_STRING linkName;
    RtlInitUnicodeString(&linkName,LNKNAME);
    IoDeleteSymbolicLink(&linkName);
    //DbgPrint("%p",&DriverEntry);
}





代码说明

//  Status values are 32 bit values layed out as follows:
//
//   3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
//   1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
//  +---+-+-------------------------+-------------------------------+
//  |Sev|C|       Facility          |               Code            |
//  +---+-+-------------------------+-------------------------------+
//
//  where
//
//      Sev - is the severity code
//
//          00 - Success
//          01 - Informational
//          10 - Warning
//          11 - Error
//
//      C - is the Customer code flag
//
//      Facility - is the facility code
//
//      Code - is the facility's status code
//

//
// Generic test for success on any status value (non-negative numbers
// indicate success).
//

#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)

从上方可以看出 当`status`值最高位为`0`(Success|Informational)时,表示执行成功,数值表示也就是在`0x00000000h~0x7FFFFFFFh`之间。
若为`1` 时,则为失败(Warning|Error),数值在`0x80000000h~0xFFFFFFFFh`之间。
IRP_MJ_*派遣函数的声明和实现都是以下这个模板。
param1: pDevice_object
param2: PIrp
NTSTATUS IRP_MJ_*(PDEVICE_OBJECT pDevice_object,PIRP pIrp)
{
.....
    return STATUS_SUCCESS;

}

执行结果

\Services\communicate_server    
00000012    51.18585205 Hello from communicate_server!  
00000013    51.18586731 \Device\MyDevice    
00000014    51.18590164 00000000    
00000015    51.18591690 Have CreateDevice   
00000016    69.02235413 recive hook message     
00000017    70.19845581 recive hook message     
00000018    71.43782806 recive resest hook message  
00000019    71.62664032 [1252] DllCanUnloadNow called for VSA7.dll  
00000020    71.62674713 [1252] DllCanUnloadNow returned S_FALSE 
00000021    78.28601837 recive resest hook message  
00000022    88.01497650 Goodbye from communicate_server!    

文章中还有一些关于函数参数的说明,见MSDN文档即可。
今天发现用VISIO2013作图,选择Vtable图形 就可以轻松做表。