自己写的驱动用CreateFile打开时错误码返回1的问题

就像题目上说的,今天在写一个例子代码时遇到了这个问题,下面是当时驱动层和应用层的代码:

#include <ntddk.h>
#define BASE_CODE 0x800
#define CREATE_THREAD_COMMAND CTL_CODE(FILE_DEVICE_UNKNOWN, BASE_CODE + 1, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define DEVICE_NAME L"\\Device\\ThreadDevice"
#define LINK_NAME L"\\??\\ControlDevice"
VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
    UNICODE_STRING uLinkName;
    PDEVICE_OBJECT pDev = pDriverObject->DeviceObject;
    RtlInitUnicodeString(&uLinkName, LINK_NAME);
    IoDeleteSymbolicLink(&uLinkName);

    IoDeleteDevice(pDev);
    DbgPrint("Goodbye world!\n");
}

VOID MyProcessThread(PVOID pContext)
{
    //获取当前发送IRP请求的线程名
    PEPROCESS pCurrProcess = IoGetCurrentProcess();
    PTSTR pProcessName = (PTSTR)((CHAR*)pCurrProcess + 0x174);
//  UNREFERENCED_PARAMETER(pContext);
    DbgPrint("MyProcessThread Current Process %s\n", pProcessName);
    PsTerminateSystemThread(0);
}

VOID SystemThread(PVOID pContext)
{
    //获取系统进程名
    PEPROCESS pCurrProcess = IoGetCurrentProcess();
    PTSTR pProcessName = (PTSTR)((CHAR*)pCurrProcess + 0x174);
//  UNREFERENCED_PARAMETER(pContext);
    DbgPrint("MyProcessThread Current Process %s\n", pProcessName);
    PsTerminateSystemThread(0);
}

VOID CreateThread_Test()
{
    HANDLE hSysThread = NULL;
    HANDLE hMyProcThread = NULL;
    NTSTATUS status;
    //创建系统进程
    status = PsCreateSystemThread(&hSysThread, 0, NULL, NULL, NULL, SystemThread, NULL);
    //创建用户进程
    status = PsCreateSystemThread(&hMyProcThread, 0, NULL, NtCurrentProcess(), NULL, MyProcessThread, NULL);
}

NTSTATUS IoControlDispatch(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
)
{
    NTSTATUS status = STATUS_SUCCESS;
    PIO_STACK_LOCATION pIrps = IoGetCurrentIrpStackLocation(Irp);
    if(CREATE_THREAD_COMMAND == pIrps->Parameters.DeviceIoControl.IoControlCode)
    {
        //创建线程
        CreateThread_Test();
    }
    Irp->IoStatus.Status = status;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(
IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING pRegistryPath)
{
    UNICODE_STRING uDeviceName;
    UNICODE_STRING uSymbolicName;
    PDEVICE_OBJECT pDevObj;
    NTSTATUS status;
    DbgPrint("Hello, world\n");
    pDriverObject->DriverUnload = DriverUnload;

    //初始化设备名称和链接名称
    RtlInitUnicodeString(&uDeviceName, DEVICE_NAME);
    RtlInitUnicodeString(&uSymbolicName, LINK_NAME);
    //创建设备对像
    status = IoCreateDevice(pDriverObject, 0, &uDeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDevObj);
    if(!NT_SUCCESS(status))
    {
        DbgPrint("Create Device Error!\n");
        return status;
    }

    status = IoCreateSymbolicLink(&uSymbolicName, &uDeviceName);
    if(!NT_SUCCESS(status))
    {
        DbgPrint("Create SymbolicLink Error!\n");
        IoDeleteDevice(pDevObj);
        return status;
    }
    //设置IRP_MJ_DEVICE_CONTROL分发派遣函数
    pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IoControlDispatch;
    return STATUS_SUCCESS;
}
#include <stdio.h>
#include <Windows.h>
#include <tchar.h>
#define BASE_CODE 0x800
#define CREATE_THREAD_COMMAND CTL_CODE(FILE_DEVICE_UNKNOWN, BASE_CODE + 1, METHOD_BUFFERED, FILE_ANY_ACCESS)

int _tmain(int argc, TCHAR *argv[])
{
    BOOL bRet = FALSE;
    HANDLE hDevice = CreateFile(_T("\\\\.\\ControlDevice"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (INVALID_HANDLE_VALUE == hDevice)
    {
        printf("Create File Error %d \n", GetLastError());
        _tsystem(_T("PAUSE"));
        return 0;
    }

    printf("Create File Success hDevice = %08x\n", hDevice);
    bRet = DeviceIoControl(hDevice, CREATE_THREAD_COMMAND, NULL, 0, NULL, 0, NULL, NULL);
    if (!bRet)
    {
        printf("Control Device Error, code : %d\n", GetLastError());
    }
    _tsystem(_T("PAUSE"));
    return 0;
}

这些代码非常简单,就是直接在应用层通过CreateFile打开,然后下发一个控制命令,驱动层接收到这个命令,创建两个线程,一个获取当前下发命令的应用程序的进程名,一个获取系统进程的进程名。
这段代码当时主要是有两个问题,第一个就是CreateFile打开时错误,并返回错误码1,乍看好像没有什么问题,其实这个问题我估计还是自己对应用层如何调用驱动层不太熟。首先驱动程序跟应用层的窗口程序类似,都是靠事件驱动的,在窗口程序中把这种事件叫做消息,窗口程序的主要功能是处理各种消息,由系统根据消息负责调用消息处理函数,驱动同样是这样。驱动中的设备对象就好像窗口一样,应用层下发的事件都是针对设备对象的。应用层针对不同设备对象下发的请求通过I/O管理器进行封装,变为一个个的IRP,根据不同的设备对象所属的驱动程序的不同,系统会自动调用我们事先准备好的处理程序,在程序中主要做这样几件事:
1. 对下发的事件进行适当的处理
2. 决定下发这个IRP或者结束这个IRP
3. 决定如何向I/O管理器和本层驱动程序返回值
I/O管理器会根据返回的值来决定如何给上层返回一个值,就拿CreateFile来说,这个API在调用时会经过I/O管理器生成一个IRP_MJ_CREATE类型的IRP,系统根据函数所针对的设备(这个设备可以通过第一个参数知道)找到对应的驱动,然后调用驱动中对应的处理函数,然后将这个处理函数中返回的值返回给I/O管理器,I/O管理器根据这个值决定如何返回值给应用层的API。说道这,这个问题的答案基本上已经出来了,这个问题的原因就是这段代码没有给定IRP_MJ_CREATE的处理函数,I/O管理器并没有收到一个成功的返回,所以它给应用层返回一个错误,我们加上一个Create的处理函数,并在DriverEntry中指定就好了。

NTSTATUS CreateDispatch(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
)
{
    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

//然后需要在DriverEntry中加上这句话,相当于在系统中注册了一个Create事件的处理函数
pDriverObject->MajorFunction[IRP_MJ_CREATE] = CreateDispatch;

这里有两个NTSTATUS的值,通过return返回的是给驱动程序的,而通过Irp->IoStatus.Status返回的是给I/O管理器的,上面说的I/O管理器没有收到成功,说的也是这个值没有给STATUS_SUCCESS
做完这些工作,这个问题就这样解决了,但是接着执行后面的代码,发现程序崩溃了,会弹出一个内存读写错误的提示框,这个时候可以肯定是应用层的问题,因为如果是内核层出现内存读写错误,系统肯定蓝屏了。当时我推测可能是句柄为NULL,或者DeviceIoControl中哪个缓冲区不能为NULL,为了知道是哪的问题,我在调用DeviceIoControl之前加了一条输出语句,我发现这条语句输出的句柄值是正常的,那就肯定是DeviceIoControl的问题,我先试着吧所有的输入输出缓冲区都给定了一个值,通过排查最后发现是倒数第二个参数不能为NULL,这个参数表示的是驱动层实际返回的缓冲区的大小。最后通过查看驱动层的代码,我终于知道这个值为什么不能为NULL。这就要说到DeviceIoControl与驱动通信的方式,DeviceIoControl的定义如下:

BOOL DeviceIoControl(
  HANDLE hDevice, 
  DWORD dwIoControlCode, 
  LPVOID lpInBuffer, 
  DWORD nInBufferSize, 
  LPVOID lpOutBuffer, 
  DWORD nOutBufferSize, 
  LPDWORD lpBytesReturned, 
  LPOVERLAPPED lpOverlapped
);

DeviceIoControl通过dwIoControlCode向驱动下发控制码,这个控制码在驱动中可以通过IO_STACK_LOCATION 结构中的Parameters.DeviceIoControl.IoControlCode值获得。另外函数根据lpInBuffer和nInBufferSize来给驱动传递数据,通过参数lpOutBuffer和nOutBufferSize来接受来自驱动上传的数据,另外还有一个是驱动真实上传数据的大小,大小都是以字节为单位的。那么这个真实大小是怎么来的呢?答案就是通过Irp->IoStatus.Information这个值,I/O管理器取这个值,将它填充到lpBytesReturned所指向的内存中,既然我们在驱动中指定了这个值为0,自然要给它在应用层分配相应的缓冲区了,前面的由于给的是NULL,I/O管理器不可能将这个值填入NULL缓冲区,所以自然会弹出这个内存读写的错误。
最后来总结下:
1. 如果我们要打开对应的驱动中的设备对象,在驱动层需要提供IRP_MJ_CREATE的处理函数,将返回给I/O管理器的值填入到IRP的IoStatus这个结构中。类似的,如果我们通过类似WriteFile、ReadFile的函数发送的相应的IRP,即使我们不需要对这个IRP进行特殊的处理,我们也需要为这些操作提供一个默认的处理函数,至少要向I/0管理器返回一个成功,这样应用层的函数才能调用成功。
2. DeviceIoControl函数,如果不需要跟驱动层进行交互,那么他的输入输出缓冲区是可以给NULL的,但是由于I/O管理器会像它返回驱动层实际返回的数据的大小,所以这个真实大小的缓冲区一定不能为NULL

posted @ 2017-10-24 20:55  masimaro  阅读(2015)  评论(0编辑  收藏  举报