转载自:http://mzf2008.blog.163.com/blog/static/35599786201011162265581/
1.DeviceIoControl与驱动交互
除了用ReadFile(读设备)和WriteFile(写设备)以外,应用程序还可以通过另外一个WIN32 API函数DeviceIoControl操作设备。DeviceIoControl内部会产生一个IRP_MJ_DEVICE_CONTROL类型的IRP,然后操作系统会将这个IRP转发到派遣函数中。
我们可以用DeviceIoControl定义除读写以外的其他操作,它可以让应用程序和驱动程序进行通信。例如,要对一个设备进行初始化操作,程序员自定义一种I/O控制码,然后用DeviceIoControl将这个控制码和请求一起传递给驱动程序。在派遣函数中,分别对不同的I/O控制码进行处理。
DeviceIoControl的声明如下:
BOOL DeviceIoControl(
HANDLE hDevice, // 已经打开的设备
DWORD dwIoControlCode, // 控制码
LPVOID lpInBuffer, //输入缓冲区
DWORD nInBufferSize, //输入缓冲区大小
LPVOID lpOutBuffer, // 输出缓冲区
DWORD nOutBufferSize, // 输出缓冲区大小
LPDWORD lpBytesReturned, // 实际返回字节数
LPOVERLAPPED lpOverlapped //是否异步操作
);
其中,lpBytesReturned对应派遣函数中的IRP结构中的pIrp->IoStatus.Information。
dwIoControlCode是I/O控制码,控制码也称IOCTL值,是一个32位的无符号整形。IOCTL需要符合DDK的规定。
DDK提供了一个宏CTL_CODE,方便我们定义IOCTL值,其定义如下:
CTL_CODE(DeviceType, Function, Method, Access)
DeviceType:设备对象的类型,这个设备应和创建设备(IoCreateDevice)时的类型相匹配。一般形式如FILE_DEVICE_xxxx的宏。
Function:这是驱动程序定义的IOCTL码。其中:0X0000-0X7FFF为微软保留。0X8000到0XFFFF由程序员自己定义。
Method:这个是操作模式。可以是以下四种模式的一种:
(1) METHOD_BUFFERED:使用缓冲区方式操作
(2) METHOD_IN_DIRECT:使用直接写方式操作
(3) METHOD_OUT_DIRECT:使用直接读方式操作
(4) METHOD_NEITHER:使用其他方式操作
Access:访问权限,一般为FILE_ANY_ACCESS
2.缓冲内存模式IOCTL
使用这种模式时,在Win32 API 函数DeviceIoControl的内部,用户提供的缓冲区的内容会被复制到IRP中的pIrp->AssociatedIrp.SystemBuffer内存地址,复制的字节数是由DeviceIoControl指定的输入字节数。
派遣函数可以读取pIrp->AssociatedIrp.SystemBuffer的内存地址,从而获得应用程序提供的输入缓冲区。另外,派遣函数还可以写入pIrp->AssociatedIrp.SystemBuffer的内存地址,这被当做设备输出的数据。操作系统会将这个地址的数据再次复制到DeviceIoControl提供的输出缓冲区中。复制的字节数有pIrp->IoStatus.Information指定。而DeviceIoControl可以通过它的第七个参数得到这个操作字节数。
派遣函数先通过IoGetCurrentStackLocation函数得到当前I/O堆栈。派遣函数通过stack->Parameters.DeviceIoControl.InputBufferLength得到输入缓冲区的大小,通过stack->Parameters.DeviceIoControl.OutputBufferLength得到输出缓冲区的大小。最后通过stack->Parameters.DeviceIoControl.IoControlCode得到IOCTL。在派遣函数中通过switch语句分别处理不同的IOCTL。
示例代码:
定义IOCTL:
#define CTL_TEST1 CTL_CODE(\
FILE_DEVICE_UNKNOWN, \
0X800, \
METHOD_BUFFERED, \
FILE_ANY_ACCESS)
Win32测试程序:
#include <windows.h>
#include <winioctl.h>
#include <stdio.h>
#include "..\NT_Driver\ioctl.h"
int main(void)
{
HANDLE hDevice;
//打开设备
hDevice = CreateFile("\\\\.\\HelloDDK", GENERIC_READ| GENERIC_WRITE,
0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hDevice == INVALID_HANDLE_VALUE)
{
printf("CreateFile Error : %d\n", GetLastError());
return -1;
}
BOOL bRet;
CHAR inBuffer[10];
memset(inBuffer, 'B', sizeof(inBuffer));
CHAR outBuffer[10];
DWORD dwReturn;
bRet = DeviceIoControl(hDevice, CTL_TEST1, inBuffer, sizeof(inBuffer),
&outBuffer, sizeof(outBuffer), &dwReturn, NULL);
if (bRet)
{
for (int i=0; i<(int)dwReturn; i++)
{
printf("%c ", outBuffer[i]);
}
printf("\n");
return 0;
}
else
return -1;
}
驱动层:
NTSTATUS FuckDeviceControl(IN PDEVICE_OBJECT pDeviceObj, IN PIRP pIrp)
{
KdPrint(("进入IRP_MJ_DEVICE_CONTROL处理函数!\n"));
NTSTATUS status = STATUS_SUCCESS;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
ULONG inLength = stack->Parameters.DeviceIoControl.InputBufferLength;
ULONG outLength = stack->Parameters.DeviceIoControl.OutputBufferLength;
ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
switch(code)
{
case CTL_TEST1:{
KdPrint(("CTL_TEST1\n"));
CHAR* inBuffer = (CHAR*)pIrp->AssociatedIrp.SystemBuffer;
for (int i=0; i<(int)inLength; i++)
{
KdPrint(("%c ", inBuffer[i]));
}
CHAR* outBuffer =(CHAR*) pIrp->AssociatedIrp.SystemBuffer;
RtlFillMemory(outBuffer, outLength, 'A');
break;
}
default:
status = STATUS_INVALID_VARIANT;
}
pIrp->IoStatus.Information = outLength;
pIrp->IoStatus.Status = status;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
KdPrint(("离开IR_MJ_DEVICE_CONTROL处理函数!\n"));
return status;
}
3. 直接内存模式IOCTL
当使用这种模式时,在用CTL_CODE宏定义这种IOTL时,应该制定Method参数为METHOD_OUT_DIRECT或者METHOD_IN_DIRECT。直接模式的IOCTL同样可以避免驱动程序访问用户模式的内存地址。
METHOD_IN_DIRECT: if the caller of DeviceIoControl or IoBuildDeviceIoControlRequest will pass data to the driver.
METHOD_OUT_DIRECT: if the caller of DeviceIoControl or IoBuildDeviceIoControlRequest will receive data from the driver.
在调用DeviceIoControl时,输入缓冲区的内容被复制到IRP中的pIrp->AssociatedIrp.SystemBuffer内存地址,复制的字节数由DeviceIoControl指定。这个步骤和缓冲区模式的IOCTL的处理时一样的。
但是当对于DeviceIoControl指定的输出缓冲区的处理,直接模式的IOCTL和缓冲区模式的IOCTL却是以不同方式处理的。操作系统会将DeviceIoControl指定的输出缓冲区锁定,然后在内核模式下重新映射一段地址。
派遣函数中的IRP结构中的pIrp->MdlAddress记录DeivceIoControl指定的输出缓冲区。派遣函数应该使用MmGetSystemAddressForMdlSafe将这段内存映射到内核模式下的内存地址。
示例代码:
定义IOCTL:
#define IOCTL_TEST2 CTL_CODE(\
FILE_DEVICE_UNKNOWN, \
0x801, \
METHOD_IN_DIRECT, \
FILE_ANY_ACCESS)
驱动层:
NTSTATUS FuckDeviceControl(IN PDEVICE_OBJECT pDeviceObj, IN PIRP pIrp)
{
KdPrint(("进入IRP_MJ_DEVICE_CONTROL处理函数!\n"));
NTSTATUS status = STATUS_SUCCESS;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
ULONG inLength = stack->Parameters.DeviceIoControl.InputBufferLength;
ULONG outLength = stack->Parameters.DeviceIoControl.OutputBufferLength;
ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
switch(code)
{
case CTL_TEST2:
{
KdPrint(("CTL_TEST2!\n"));
CHAR* inBuffer = (CHAR*)pIrp->AssociatedIrp.SystemBuffer;
for (int i=0; i<(int)inLength; i++)
{
KdPrint(("%c ", inBuffer[i]));
}
//调用MmGetSystemAddressForMdlSafe获得内核模式下的映射地址
CHAR* outBuffer = (CHAR*)MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,
NormalPagePriority);
KdPrint(("0x%08X\n", outBuffer));
RtlFillMemory(outBuffer, outLength, 'A');
break;
}
default:
status = STATUS_INVALID_VARIANT;
}
pIrp->IoStatus.Information = outLength;
pIrp->IoStatus.Status = status;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
KdPrint(("离开IR_MJ_DEVICE_CONTROL处理函数!\n"));
return status;
}
4.其他内存模式IOCTL
在这个模式中,在用CTL_CODE定义IOCTL时,应该指定Method为METHOD_NEITHER。这种方式IOCTL很少被用到,因为它直接访问用户模式地址。使用用户模式地址必须保证调用DeviceIoControl的线程与派遣函数运行在同一个线程上下文中。
对于DeviceIoControl提供的输入缓冲区的地址,派遣函数可以通过I/O堆栈的stack->Parameters.DeviceIoControl.Type3InputBuffer获得。同时,派遣函数可以通过pIrp->UserBuffer获得DeviceIoControl的输出缓冲区。
由于驱动程序的派遣函数不能保证传递进来的用户模式地址是合法的。所以最好对传入的用户模式地址进行可读写判断。使用ProbeForRead和ProbeForWrite函数。
示例代码:
定义IOCTL:
#define IOCTL_TEST2 CTL_CODE(\
FILE_DEVICE_UNKNOWN, \
0x801, \
METHOD_NERTHER, \
FILE_ANY_ACCESS)
驱动层:
NTSTATUS FuckDeviceControl(IN PDEVICE_OBJECT pDeviceObj, IN PIRP pIrp)
{
KdPrint(("进入IRP_MJ_DEVICE_CONTROL处理函数!\n"));
NTSTATUS status = STATUS_SUCCESS;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
ULONG inLength = stack->Parameters.DeviceIoControl.InputBufferLength;
ULONG outLength = stack->Parameters.DeviceIoControl.OutputBufferLength;
ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
switch(code)
{
case CTL_TEST3:
{
KdPrint(("CTL_TEST3!\n"));
CHAR* inBuffer = (CHAR*)stack->Parameters.DeviceIoControl.Type3InputBuffer;
KdPrint(("inBuffer address:0x%08X", inBuffer));
CHAR* outBuffer = (CHAR*)pIrp->UserBuffer;
KdPrint(("outBuffer address:0x%08X", outBuffer));
__try
{
KdPrint(("进入try块!\n"));
ProbeForRead(inBuffer, inLength, 4);
for (ULONG i =0; i<inLength; i++)
{
KdPrint(("%c", inBuffer[i]));
}
ProbeForWrite(outBuffer, outLength, 4);
RtlFillMemory(outBuffer, outLength, 'C');
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
KdPrint(("进入except块!\n"));
status = STATUS_UNSUCCESSFUL;
}
break;
}
default:
status = STATUS_INVALID_VARIANT;
}
pIrp->IoStatus.Information = outLength;
pIrp->IoStatus.Status = status;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
KdPrint(("离开IR_MJ_DEVICE_CONTROL处理函数!\n"));
return STATUS_SUCCESS;
}