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

来源:互联网 发布:软件开发工作 编辑:程序博客网 时间:2024/05/10 20:43

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

#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

0 0
原创粉丝点击