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

基本介绍

1.设备与驱动的关系

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

摘自http://www.cnblogs.com/mydomain/archive/2010/10/20/1857070.html
驱动程序和系统其他组件之间的交互是通过给设备发送或者接受发给设备的请求来交互的。换句话说,一个没有任何设备的驱动是不能按规范方式和系统交互的。当然也不会收到任何IRP,分发函数也失去了意义。

如果驱动程序要和应用程序之间通信,则需要生成设备。此外还必须为设备生成应用程序可以访问的符号链接。

也就是说,应用程序与驱动的交互是通过设备来完成的,设备成了中间桥梁。下文中的符号链接又成了设备名的中间桥梁。记住,核心中介是设备。其他都是为他们的通信提供的机制。

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

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

这里写图片描述
ps:中间的图解释换做r0(r3)下设备管理器中的设备符号链接更好

从图中可以看出,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图形 就可以轻松做表。

发布了63 篇原创文章 · 获赞 6 · 访问量 16万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览