驅動程式通信的函數,除了ReadFile和WriteFile函數還有DeviceIoControl函數,而且DeviceIoControl函數那是相當的彪悍。因為它可以自定義控制碼,你隻要在IRP_MJ_DEVICE_CONTROL對應的派遣函數中讀取控制碼,然後針對控制碼,你就可以實作自定義的功能了。
函數原型:
BOOL WINAPI DeviceIoControl(
__in HANDLEhDevice,
__in DWORDdwIoControlCode,
__in_opt LPVOID lpInBuffer,
__in DWORDnInBufferSize,
__out_opt LPVOID lpOutBuffer,
__in DWORDnOutBufferSize,
__out_opt LPDWORD lpBytesReturned,
__inout_opt LPOVERLAPPED lpOverlapped
);
其中lpBytesReturned的值來自于IRP結構中的pIRP->IoStatus.Information。DeviceIoControl的第二個參數就是控制碼,控制碼是一個32為無符号整型,需要符合DDK的規定。
控制代碼中各資料位字段的含義如下:
◎ DeviceType--裝置類型(31-16bit)指 出了裝置的類型,微軟保留了0-7FFFh的取值,剩下的8000h-0FFFFh 供開發商定義新的核心模式驅動程式。我們可以在\include\w2k\ntddk.inc檔案中找到一組FILE_DEVICE_XXX 符号常量,這些值都是微軟保留的 值,我們可以使用其中的FILE_DEVICE_UNKNOWN。當然你也可以定義另外一個FILE_DEVICE_XXX值
◎ Access--存取代碼(15-14bit)指明應用程式存取裝置的方式,由于這個字段隻有2位,是以隻有4種可能性:
· FILE_ANY_ACCESS (0)--最大的存取權限,就是什麼操作都可以
· FILE_READ_ACCESS (1)--讀權限,裝置将資料傳遞到指定的緩沖區
· FILE_WRITE_ACCESS (2)--寫權限,可以從記憶體中向裝置傳遞資料
· FILE_READ_ACCESS or FILE_WRITE_ACCESS (3)--讀寫權限,裝置和記憶體緩沖區之間可以互相傳遞資料
◎ Function--功能代碼(13-2bit)用來描述要進行的操作,我們可以用800h-0FFFh來定義自己的I/O控制代碼,
0-7FFh之間的值是被微軟保留的,用來定義公用的I/O控制代碼
◎ Method--緩沖模式(0-1bit)表示I/O管理器如何對輸入和輸出的資料進行緩沖,這個字段的長度是2位,是以有
4種可能性:
·METHOD_BUFFERED (0)--對I/O進行緩沖
·METHOD_IN_DIRECT (1)--對輸入不進行緩沖
·METHOD_OUT_DIRECT (2)--對輸出不進行緩沖
·METHOD_NEITHER (3)--都不緩沖
緩沖區模式雖然會損失點性能,但是其安全性好。
下面将分别講述這幾種模式。
緩沖記憶體模式(對應代碼中的IOCTL_TEST1)
首先要将控制碼中的Method設定為METHOD_BUFFERED。
往驅動中Input資料:在Win32 APIDeviceIoControl函數的内部,使用者提供的輸入緩沖區的内容被複制到IRP的pIRP->AssociatedIrp.SystemBuffer的記憶體位址,複制的位元組是有DeviceControl指定的輸入位元組數。從驅動中Output資料:派遣函數可以向pIRP->AssociatedIrp.SystemBuffer寫入資料,被當做是裝置輸出的資料。作業系統會将AssociatedIrp.SystemBuffer的資料再次複制到DeviceIoControl提供的輸出緩沖區,複制的位元組數有pIrp->IoStatus.Information指定,DeviceIoControl也可以通過參數lpBytesReturned得到複制的位元組數。
原理就是這樣了,理論上就可以實作讀和寫的雙向操作了。
直接記憶體模式(對應代碼中的IOCTL_TEST2)
首先将Method設定為METHOD_IN_DIRECT 或METHOD_OUT_DIRECT ,這兩者的不同隻是展現在打開裝置的權限上,當以隻讀權限打開裝置時,METHOD_IN_DIRECT 就可以順利操作,而METHOD_OUT_DIRECT 就會失敗。如果以讀寫權限打開時,兩者都可以執行成功。
往驅動中Input資料:這部分和上面的緩沖記憶體模式一樣,輸入緩沖區的資料複制到pIrp->AssociateIrp.SystemBuffer記憶體位址,複制的位元組數是按照DeviceIoControl指定的。
從驅動中Output資料:作業系統會為DeviceIoControl指定的輸出緩沖區鎖定,然後在核心模式位址下重新映射到一段位址。在派遣函數中可以先擷取DeviceIoControl指定的輸出緩沖區(lpOutBufferb被記錄在pIrp->AssociateIrp.SystemBuffer),然後再通過MmGetSystemAddressForMdlSafe擷取其在核位址中的映射值。
其他記憶體模式(對應代碼中的IOCTL_TEST3)
個人覺得這種方式挺麻煩的而且少被用到,由于它是直接通路使用者模式位址,要求調用DeviceIoControl的線程和派遣函數運作在同一個線程裝置上下文中,自己有個印象就行了。
首先将指定的Method參數設定為METHOD_NEITHER。
往驅動中Input資料:通過I/O堆棧的Parameters.DeviceIoControl.Type3InputBuffer得到DeviceIoControl提供的輸入緩沖區位址,Parameters.DeviceIoControl.InputBufferLength得到其長度。由于不能保證傳遞過來的位址合法,是以需要先要結果ProbeRead函數進行判斷。
從驅動中Output資料:通過pIrp->UserBuffer得到DeviceIoControl函數提供的輸出緩沖區位址,再通過Parameters.DeviceIoControl.OutputBufferLength得到輸出緩沖區大小。同樣的要用ProbeWrite函數先進行判斷。
下面給出一個執行個體代碼,來自于張帆的《Windows驅動開發詳解》
首先是控制碼設定:
[cpp] view plain copy
- #define IOCTL_TEST1 CTL_CODE(\
- FILE_DEVICE_UNKNOWN, \
- 0x800, \
- METHOD_BUFFERED, \
- FILE_ANY_ACCESS)
- #define IOCTL_TEST2 CTL_CODE(\
- FILE_DEVICE_UNKNOWN, \
- 0x801, \
- METHOD_IN_DIRECT, \
- FILE_ANY_ACCESS)
- #define IOCTL_TEST3 CTL_CODE(\
- FILE_DEVICE_UNKNOWN, \
- 0x802, \
- METHOD_NEITHER, \
- FILE_ANY_ACCESS)
- 再是IRP_MJ_DEVICE_CONTROL派遣函數:
- NTSTATUSIOCTRLDRIVER_DispatchDeviceControl(
- IN PDEVICE_OBJECT DeviceObject,
- IN PIRP pIrp
- )
- {
- NTSTATUS status = STATUS_SUCCESS;
- PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
- //得到輸入緩沖區大小
- ULONG cbin = stack->Parameters.DeviceIoControl.InputBufferLength;
- //得到輸出緩沖區大小
- ULONG cbout = stack->Parameters.DeviceIoControl.OutputBufferLength;
- //得到IOCTL碼
- ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
- ULONG info = 0;
- switch(code)
- {
- case IOCTL_TEST1:
- {
- KdPrint(("zhui:IOCTL_TEST1\n"));
- UCHAR* InputBuffer =(UCHAR*)pIrp->AssociatedIrp.SystemBuffer;
- for (ULONGi=0;i<cbin;i++)
- {
- KdPrint(("zhui:%X\n",InputBuffer[i]));
- }
- //操作輸出緩沖區
- UCHAR* OutputBuffer =(UCHAR*)pIrp->AssociatedIrp.SystemBuffer;
- memset(OutputBuffer,0xAA,cbout);
- //設定實際操作輸出緩沖區長度
- info = cbout;
- break;
- }
- case IOCTL_TEST2:
- {
- KdPrint(("zhui:IOCTL_TEST2\n"));
- UCHAR* InputBuffer =(UCHAR*)pIrp->AssociatedIrp.SystemBuffer;
- for (ULONGi=0;i<cbin;i++)
- {
- KdPrint(("zhui:%X\n",InputBuffer[i]));
- }
- //pIrp->MdlAddress為DeviceIoControl輸出緩沖區位址相同
- KdPrint(("zhui:UserAddress:0X%08X\n",MmGetMdlVirtualAddress(pIrp->MdlAddress)));
- UCHAR* OutputBuffer =(UCHAR*)MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,NormalPagePriority);
- //InputBuffer被映射到核心模式下的記憶體位址,必定在0X80000000-0XFFFFFFFF之間
- memset(OutputBuffer,0xAA,cbout);
- //設定實際操作輸出緩沖區長度
- info = cbout;
- break;
- }
- case IOCTL_TEST3:
- {
- KdPrint(("zhui:IOCTL_TEST3\n"));
- UCHAR* UserInputBuffer= (UCHAR*)stack->Parameters.DeviceIoControl.Type3InputBuffer;
- KdPrint(("zhui:UserInputBuffer:0X%0X\n",UserInputBuffer));
- //得到使用者模式位址
- PVOID UserOutputBuffer= pIrp->UserBuffer;
- KdPrint(("zhui:UserOutputBuffer:0X%0X\n",UserOutputBuffer));
- __try
- {
- KdPrint(("zhui:Enter __try block\n"));
- //判斷指針是否可讀
- ProbeForRead(UserInputBuffer,cbin,4);
- //顯示輸入緩沖區内容
- for (ULONGi=0;i<cbin;i++)
- {
- KdPrint(("zhui:%X\n",UserInputBuffer[i]));
- }
- //判斷指針是否可寫
- ProbeForWrite(UserOutputBuffer,cbout,4);
- //操作輸出緩沖區
- memset(UserOutputBuffer,0xAA,cbout);
- //由于在上面引發異常,是以以後語句不會被執行!
- info = cbout;
- KdPrint(("zhui:Leave __try block\n"));
- }
- __except(EXCEPTION_EXECUTE_HANDLER)
- {
- KdPrint(("zhui:Catch the exception\n"));
- KdPrint(("zhui:The program will keep going\n"));
- status =STATUS_UNSUCCESSFUL;
- }
- info = cbout;
- break;
- }
- default:
- status =STATUS_INVALID_DEVICE_REQUEST;
- break;
- }
- pIrp->IoStatus.Status = status;
- pIrp->IoStatus.Information = info;
- IoCompleteRequest(pIrp, IO_NO_INCREMENT);
- return status;
- }
測試的main函數:
[cpp] view plain copy
- int main()
- {
- HANDLE hDevice =
- CreateFile("\\\\.\\HelloDDK",
- GENERIC_READ | GENERIC_WRITE,
- 0, // share mode none
- NULL, // no security
- OPEN_EXISTING,
- FILE_ATTRIBUTE_NORMAL,
- NULL); // no template
- if (hDevice == INVALID_HANDLE_VALUE)
- {
- printf("Failed to obtainfile handle to device: "
- "%s with Win32error code: %d\n",
- "MyWDMDevice", GetLastError() );
- return 1;
- }
- UCHAR InputBuffer[10];
- UCHAR OutputBuffer[10];
- //将輸入緩沖區全部置成0XBB
- memset(InputBuffer,0xBB,10);
- DWORD dwOutput;
- //輸入緩沖區作為輸入,輸出緩沖區作為輸出
- BOOL bRet;
- bRet = DeviceIoControl(hDevice, IOCTL_TEST1, InputBuffer, 10,&OutputBuffer, 10, &dwOutput, NULL);
- if (bRet)
- {
- printf("Output buffer:%dbytes\n",dwOutput);
- for (inti=0;i<(int)dwOutput;i++)
- {
- printf("%02X",OutputBuffer[i]);
- }
- printf("\n");
- }
- bRet = DeviceIoControl(hDevice, IOCTL_TEST2, InputBuffer, 10,&OutputBuffer, 10, &dwOutput, NULL);
- if (bRet)
- {
- printf("Output buffer:%dbytes\n",dwOutput);
- for (inti=0;i<(int)dwOutput;i++)
- {
- printf("%02X",OutputBuffer[i]);
- }
- printf("\n");
- }
- bRet = DeviceIoControl(hDevice, IOCTL_TEST3, InputBuffer, 10,&OutputBuffer, 10, &dwOutput, NULL);
- if (bRet)
- {
- printf("Output buffer:%dbytes\n",dwOutput);
- for (int i=0;i<(int)dwOutput;i++)
- {
- printf("%02X",OutputBuffer[i]);
- }
- printf("\n");
- }
- CloseHandle(hDevice);
- return 0;
- }
首先是控制碼設定:
[cpp] view plain copy
- #define IOCTL_TEST1 CTL_CODE(\
- FILE_DEVICE_UNKNOWN, \
- 0x800, \
- METHOD_BUFFERED, \
- FILE_ANY_ACCESS)
- #define IOCTL_TEST2 CTL_CODE(\
- FILE_DEVICE_UNKNOWN, \
- 0x801, \
- METHOD_IN_DIRECT, \
- FILE_ANY_ACCESS)
- #define IOCTL_TEST3 CTL_CODE(\
- FILE_DEVICE_UNKNOWN, \
- 0x802, \
- METHOD_NEITHER, \
- FILE_ANY_ACCESS)
再是IRP_MJ_DEVICE_CONTROL派遣函數:
[cpp] view plain copy
- NTSTATUS IOCTRLDRIVER_DispatchDeviceControl(
- IN PDEVICE_OBJECT DeviceObject,
- IN PIRP pIrp
- )
- {
- NTSTATUS status = STATUS_SUCCESS;
- PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
- //得到輸入緩沖區大小
- ULONG cbin = stack->Parameters.DeviceIoControl.InputBufferLength;
- //得到輸出緩沖區大小
- ULONG cbout = stack->Parameters.DeviceIoControl.OutputBufferLength;
- //得到IOCTL碼
- ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
- ULONG info = 0;
- switch(code)
- {
- case IOCTL_TEST1:
- {
- KdPrint(("zhui:IOCTL_TEST1\n"));
- UCHAR* InputBuffer = (UCHAR*)pIrp->AssociatedIrp.SystemBuffer;
- for (ULONG i=0;i<cbin;i++)
- {
- KdPrint(("zhui:%X\n",InputBuffer[i]));
- }
- //操作輸出緩沖區
- UCHAR* OutputBuffer = (UCHAR*)pIrp->AssociatedIrp.SystemBuffer;
- memset(OutputBuffer,0xAA,cbout);
- //設定實際操作輸出緩沖區長度
- info = cbout;
- break;
- }
- case IOCTL_TEST2:
- {
- KdPrint(("zhui:IOCTL_TEST2\n"));
- UCHAR* InputBuffer = (UCHAR*)pIrp->AssociatedIrp.SystemBuffer;
- for (ULONG i=0;i<cbin;i++)
- {
- KdPrint(("zhui:%X\n",InputBuffer[i]));
- }
- //pIrp->MdlAddress為DeviceIoControl輸出緩沖區位址相同
- KdPrint(("zhui:User Address:0X%08X\n",MmGetMdlVirtualAddress(pIrp->MdlAddress)));
- UCHAR* OutputBuffer = (UCHAR*)MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,NormalPagePriority);
- //InputBuffer被映射到核心模式下的記憶體位址,必定在0X80000000-0XFFFFFFFF之間
- memset(OutputBuffer,0xAA,cbout);
- //設定實際操作輸出緩沖區長度
- info = cbout;
- break;
- }
- case IOCTL_TEST3:
- {
- KdPrint(("zhui:IOCTL_TEST3\n"));
- UCHAR* UserInputBuffer = (UCHAR*)stack->Parameters.DeviceIoControl.Type3InputBuffer;
- KdPrint(("zhui:UserInputBuffer:0X%0X\n",UserInputBuffer));
- //得到使用者模式位址
- PVOID UserOutputBuffer = pIrp->UserBuffer;
- KdPrint(("zhui:UserOutputBuffer:0X%0X\n",UserOutputBuffer));
- __try
- {
- KdPrint(("zhui:Enter __try block\n"));
- //判斷指針是否可讀
- ProbeForRead(UserInputBuffer,cbin,4);
- //顯示輸入緩沖區内容
- for (ULONG i=0;i<cbin;i++)
- {
- KdPrint(("zhui:%X\n",UserInputBuffer[i]));
- }
- //判斷指針是否可寫
- ProbeForWrite(UserOutputBuffer,cbout,4);
- //操作輸出緩沖區
- memset(UserOutputBuffer,0xAA,cbout);
- //由于在上面引發異常,是以以後語句不會被執行!
- info = cbout;
- KdPrint(("zhui:Leave __try block\n"));
- }
- __except(EXCEPTION_EXECUTE_HANDLER)
- {
- KdPrint(("zhui:Catch the exception\n"));
- KdPrint(("zhui:The program will keep going\n"));
- status = STATUS_UNSUCCESSFUL;
- }
- info = cbout;
- break;
- }
- default:
- status = STATUS_INVALID_DEVICE_REQUEST;
- break;
- }
- pIrp->IoStatus.Status = status;
- pIrp->IoStatus.Information = info;
- IoCompleteRequest(pIrp, IO_NO_INCREMENT);
- return status;
- }
測試的main函數:
[cpp] view plain copy
- int main()
- {
- HANDLE hDevice =
- CreateFile("\\\\.\\HelloDDK",
- GENERIC_READ | GENERIC_WRITE,
- 0, // share mode none
- NULL, // no security
- OPEN_EXISTING,
- FILE_ATTRIBUTE_NORMAL,
- NULL ); // no template
- if (hDevice == INVALID_HANDLE_VALUE)
- {
- printf("Failed to obtain file handle to device: "
- "%s with Win32 error code: %d\n",
- "MyWDMDevice", GetLastError() );
- return 1;
- }
- UCHAR InputBuffer[10];
- UCHAR OutputBuffer[10];
- //将輸入緩沖區全部置成0XBB
- memset(InputBuffer,0xBB,10);
- DWORD dwOutput;
- //輸入緩沖區作為輸入,輸出緩沖區作為輸出
- BOOL bRet;
- bRet = DeviceIoControl(hDevice, IOCTL_TEST1, InputBuffer, 10, &OutputBuffer, 10, &dwOutput, NULL);
- if (bRet)
- {
- printf("Output buffer:%d bytes\n",dwOutput);
- for (int i=0;i<(int)dwOutput;i++)
- {
- printf("%02X ",OutputBuffer[i]);
- }
- printf("\n");
- }
- bRet = DeviceIoControl(hDevice, IOCTL_TEST2, InputBuffer, 10, &OutputBuffer, 10, &dwOutput, NULL);
- if (bRet)
- {
- printf("Output buffer:%d bytes\n",dwOutput);
- for (int i=0;i<(int)dwOutput;i++)
- {
- printf("%02X ",OutputBuffer[i]);
- }
- printf("\n");
- }
- bRet = DeviceIoControl(hDevice, IOCTL_TEST3, InputBuffer, 10, &OutputBuffer, 10, &dwOutput, NULL);
- if (bRet)
- {
- printf("Output buffer:%d bytes\n",dwOutput);
- for (int i=0;i<(int)dwOutput;i++)
- {
- printf("%02X ",OutputBuffer[i]);
- }
- printf("\n");
- }
- CloseHandle(hDevice);
- return 0;
- }