天天看點

DeviceIoControl 驅動互動

驅動程式通信的函數,除了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

  1. #define IOCTL_TEST1 CTL_CODE(\  
  2.                         FILE_DEVICE_UNKNOWN, \  
  3.                         0x800, \  
  4.                         METHOD_BUFFERED, \  
  5.                         FILE_ANY_ACCESS)  
  6. #define IOCTL_TEST2 CTL_CODE(\  
  7.                         FILE_DEVICE_UNKNOWN, \  
  8.                        0x801, \  
  9.                         METHOD_IN_DIRECT, \  
  10.                         FILE_ANY_ACCESS)  
  11. #define IOCTL_TEST3 CTL_CODE(\  
  12.                         FILE_DEVICE_UNKNOWN, \  
  13.                         0x802, \  
  14.                         METHOD_NEITHER, \  
  15.                         FILE_ANY_ACCESS)  
  16. 再是IRP_MJ_DEVICE_CONTROL派遣函數:  
  17. NTSTATUSIOCTRLDRIVER_DispatchDeviceControl(  
  18.        IN PDEVICE_OBJECT              DeviceObject,  
  19.        IN PIRP                                pIrp  
  20.        )  
  21. {  
  22.        NTSTATUS status = STATUS_SUCCESS;  
  23.        PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);  
  24.        //得到輸入緩沖區大小  
  25.        ULONG cbin = stack->Parameters.DeviceIoControl.InputBufferLength;  
  26.        //得到輸出緩沖區大小  
  27.        ULONG cbout = stack->Parameters.DeviceIoControl.OutputBufferLength;  
  28.        //得到IOCTL碼  
  29.        ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;  
  30.        ULONG info = 0;  
  31.        switch(code)  
  32.        {  
  33.        case IOCTL_TEST1:  
  34.                 {  
  35.                         KdPrint(("zhui:IOCTL_TEST1\n"));  
  36.                         UCHAR* InputBuffer =(UCHAR*)pIrp->AssociatedIrp.SystemBuffer;  
  37.                         for (ULONGi=0;i<cbin;i++)  
  38.                         {  
  39.                                 KdPrint(("zhui:%X\n",InputBuffer[i]));  
  40.                         }  
  41.                         //操作輸出緩沖區  
  42.                         UCHAR* OutputBuffer =(UCHAR*)pIrp->AssociatedIrp.SystemBuffer;  
  43.                        memset(OutputBuffer,0xAA,cbout);  
  44.                         //設定實際操作輸出緩沖區長度  
  45.                         info = cbout;  
  46.                         break;  
  47.                 }  
  48.        case IOCTL_TEST2:  
  49.                 {  
  50.                         KdPrint(("zhui:IOCTL_TEST2\n"));  
  51.                         UCHAR* InputBuffer =(UCHAR*)pIrp->AssociatedIrp.SystemBuffer;  
  52.                         for (ULONGi=0;i<cbin;i++)  
  53.                         {  
  54.                                KdPrint(("zhui:%X\n",InputBuffer[i]));  
  55.                         }  
  56.                         //pIrp->MdlAddress為DeviceIoControl輸出緩沖區位址相同  
  57.                         KdPrint(("zhui:UserAddress:0X%08X\n",MmGetMdlVirtualAddress(pIrp->MdlAddress)));  
  58.                         UCHAR* OutputBuffer =(UCHAR*)MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,NormalPagePriority);  
  59.                         //InputBuffer被映射到核心模式下的記憶體位址,必定在0X80000000-0XFFFFFFFF之間  
  60.                        memset(OutputBuffer,0xAA,cbout);  
  61.                         //設定實際操作輸出緩沖區長度  
  62.                         info = cbout;  
  63.                         break;  
  64.                 }  
  65.        case IOCTL_TEST3:  
  66.                 {  
  67.                         KdPrint(("zhui:IOCTL_TEST3\n"));  
  68.                         UCHAR* UserInputBuffer= (UCHAR*)stack->Parameters.DeviceIoControl.Type3InputBuffer;  
  69.                         KdPrint(("zhui:UserInputBuffer:0X%0X\n",UserInputBuffer));  
  70.                         //得到使用者模式位址  
  71.                         PVOID UserOutputBuffer= pIrp->UserBuffer;  
  72.                         KdPrint(("zhui:UserOutputBuffer:0X%0X\n",UserOutputBuffer));  
  73.                         __try  
  74.                         {  
  75.                                KdPrint(("zhui:Enter __try block\n"));  
  76.                                 //判斷指針是否可讀  
  77.                                ProbeForRead(UserInputBuffer,cbin,4);  
  78.                                 //顯示輸入緩沖區内容  
  79.                                 for (ULONGi=0;i<cbin;i++)  
  80.                                 {  
  81.                                        KdPrint(("zhui:%X\n",UserInputBuffer[i]));  
  82.                                 }  
  83.                                 //判斷指針是否可寫  
  84.                                ProbeForWrite(UserOutputBuffer,cbout,4);  
  85.                                 //操作輸出緩沖區  
  86.                                memset(UserOutputBuffer,0xAA,cbout);  
  87.                                 //由于在上面引發異常,是以以後語句不會被執行!  
  88.                                 info = cbout;  
  89.                                KdPrint(("zhui:Leave __try block\n"));  
  90.                         }  
  91.                        __except(EXCEPTION_EXECUTE_HANDLER)  
  92.                         {  
  93.                                KdPrint(("zhui:Catch the exception\n"));  
  94.                                KdPrint(("zhui:The program will keep going\n"));  
  95.                                 status =STATUS_UNSUCCESSFUL;  
  96.                         }  
  97.                         info = cbout;  
  98.                         break;  
  99.                 }  
  100.        default:  
  101.                 status =STATUS_INVALID_DEVICE_REQUEST;  
  102.                 break;  
  103.        }  
  104.        pIrp->IoStatus.Status = status;  
  105.        pIrp->IoStatus.Information = info;  
  106.        IoCompleteRequest(pIrp, IO_NO_INCREMENT);  
  107.        return status;  
  108. }  

測試的main函數:

[cpp]  view plain copy

  1. int main()  
  2. {  
  3.        HANDLE hDevice =  
  4.                CreateFile("\\\\.\\HelloDDK",  
  5.                                        GENERIC_READ | GENERIC_WRITE,  
  6.                                         0,              // share mode none  
  7.                                        NULL,   // no security  
  8.                                        OPEN_EXISTING,  
  9.                                        FILE_ATTRIBUTE_NORMAL,  
  10.                                         NULL);         // no template  
  11.        if (hDevice == INVALID_HANDLE_VALUE)  
  12.        {  
  13.                 printf("Failed to obtainfile handle to device: "  
  14.                         "%s with Win32error code: %d\n",  
  15.                        "MyWDMDevice", GetLastError() );  
  16.                 return 1;  
  17.        }  
  18.        UCHAR InputBuffer[10];  
  19.        UCHAR OutputBuffer[10];  
  20.        //将輸入緩沖區全部置成0XBB  
  21.        memset(InputBuffer,0xBB,10);  
  22.        DWORD dwOutput;  
  23.        //輸入緩沖區作為輸入,輸出緩沖區作為輸出  
  24.        BOOL bRet;  
  25.        bRet = DeviceIoControl(hDevice, IOCTL_TEST1, InputBuffer, 10,&OutputBuffer, 10, &dwOutput, NULL);  
  26.        if (bRet)  
  27.        {  
  28.                 printf("Output buffer:%dbytes\n",dwOutput);  
  29.                 for (inti=0;i<(int)dwOutput;i++)  
  30.                 {  
  31.                         printf("%02X",OutputBuffer[i]);  
  32.                 }  
  33.                 printf("\n");  
  34.        }  
  35.        bRet = DeviceIoControl(hDevice, IOCTL_TEST2, InputBuffer, 10,&OutputBuffer, 10, &dwOutput, NULL);  
  36.        if (bRet)  
  37.        {  
  38.                 printf("Output buffer:%dbytes\n",dwOutput);  
  39.                 for (inti=0;i<(int)dwOutput;i++)  
  40.                 {  
  41.                         printf("%02X",OutputBuffer[i]);  
  42.                 }  
  43.                printf("\n");  
  44.        }  
  45.        bRet = DeviceIoControl(hDevice, IOCTL_TEST3, InputBuffer, 10,&OutputBuffer, 10, &dwOutput, NULL);  
  46.        if (bRet)  
  47.        {  
  48.                 printf("Output buffer:%dbytes\n",dwOutput);  
  49.                 for (int i=0;i<(int)dwOutput;i++)  
  50.                 {  
  51.                         printf("%02X",OutputBuffer[i]);  
  52.                 }  
  53.                 printf("\n");  
  54.        }  
  55.        CloseHandle(hDevice);  
  56.        return 0;  
  57. }  

首先是控制碼設定:

[cpp]  view plain copy

  1. #define IOCTL_TEST1 CTL_CODE(\  
  2.             FILE_DEVICE_UNKNOWN, \  
  3.             0x800, \  
  4.             METHOD_BUFFERED, \  
  5.             FILE_ANY_ACCESS)  
  6. #define IOCTL_TEST2 CTL_CODE(\  
  7.             FILE_DEVICE_UNKNOWN, \  
  8.             0x801, \  
  9.             METHOD_IN_DIRECT, \  
  10.             FILE_ANY_ACCESS)  
  11. #define IOCTL_TEST3 CTL_CODE(\  
  12.             FILE_DEVICE_UNKNOWN, \  
  13.             0x802, \  
  14.             METHOD_NEITHER, \  
  15.             FILE_ANY_ACCESS)  

再是IRP_MJ_DEVICE_CONTROL派遣函數:

[cpp]  view plain copy

  1. NTSTATUS IOCTRLDRIVER_DispatchDeviceControl(  
  2.     IN PDEVICE_OBJECT       DeviceObject,  
  3.     IN PIRP                 pIrp  
  4.     )  
  5. {  
  6.     NTSTATUS status = STATUS_SUCCESS;  
  7.     PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);  
  8.     //得到輸入緩沖區大小  
  9.     ULONG cbin = stack->Parameters.DeviceIoControl.InputBufferLength;  
  10.     //得到輸出緩沖區大小  
  11.     ULONG cbout = stack->Parameters.DeviceIoControl.OutputBufferLength;  
  12.     //得到IOCTL碼  
  13.     ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;  
  14.     ULONG info = 0;  
  15.     switch(code)  
  16.     {  
  17.     case IOCTL_TEST1:  
  18.         {  
  19.             KdPrint(("zhui:IOCTL_TEST1\n"));  
  20.             UCHAR* InputBuffer = (UCHAR*)pIrp->AssociatedIrp.SystemBuffer;  
  21.             for (ULONG i=0;i<cbin;i++)  
  22.             {  
  23.                 KdPrint(("zhui:%X\n",InputBuffer[i]));  
  24.             }  
  25.             //操作輸出緩沖區  
  26.             UCHAR* OutputBuffer = (UCHAR*)pIrp->AssociatedIrp.SystemBuffer;  
  27.             memset(OutputBuffer,0xAA,cbout);  
  28.             //設定實際操作輸出緩沖區長度  
  29.             info = cbout;  
  30.             break;  
  31.         }  
  32.     case IOCTL_TEST2:  
  33.         {  
  34.             KdPrint(("zhui:IOCTL_TEST2\n"));  
  35.             UCHAR* InputBuffer = (UCHAR*)pIrp->AssociatedIrp.SystemBuffer;  
  36.             for (ULONG i=0;i<cbin;i++)  
  37.             {  
  38.                 KdPrint(("zhui:%X\n",InputBuffer[i]));  
  39.             }  
  40.             //pIrp->MdlAddress為DeviceIoControl輸出緩沖區位址相同  
  41.             KdPrint(("zhui:User Address:0X%08X\n",MmGetMdlVirtualAddress(pIrp->MdlAddress)));  
  42.             UCHAR* OutputBuffer = (UCHAR*)MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,NormalPagePriority);  
  43.             //InputBuffer被映射到核心模式下的記憶體位址,必定在0X80000000-0XFFFFFFFF之間  
  44.             memset(OutputBuffer,0xAA,cbout);  
  45.             //設定實際操作輸出緩沖區長度  
  46.             info = cbout;  
  47.             break;  
  48.         }  
  49.     case IOCTL_TEST3:  
  50.         {  
  51.             KdPrint(("zhui:IOCTL_TEST3\n"));  
  52.             UCHAR* UserInputBuffer = (UCHAR*)stack->Parameters.DeviceIoControl.Type3InputBuffer;  
  53.             KdPrint(("zhui:UserInputBuffer:0X%0X\n",UserInputBuffer));  
  54.             //得到使用者模式位址  
  55.             PVOID UserOutputBuffer = pIrp->UserBuffer;  
  56.             KdPrint(("zhui:UserOutputBuffer:0X%0X\n",UserOutputBuffer));  
  57.             __try  
  58.             {  
  59.                 KdPrint(("zhui:Enter __try block\n"));  
  60.                 //判斷指針是否可讀  
  61.                 ProbeForRead(UserInputBuffer,cbin,4);  
  62.                 //顯示輸入緩沖區内容  
  63.                 for (ULONG i=0;i<cbin;i++)  
  64.                 {  
  65.                     KdPrint(("zhui:%X\n",UserInputBuffer[i]));  
  66.                 }  
  67.                 //判斷指針是否可寫  
  68.                 ProbeForWrite(UserOutputBuffer,cbout,4);  
  69.                 //操作輸出緩沖區  
  70.                 memset(UserOutputBuffer,0xAA,cbout);  
  71.                 //由于在上面引發異常,是以以後語句不會被執行!  
  72.                 info = cbout;  
  73.                 KdPrint(("zhui:Leave __try block\n"));  
  74.             }  
  75.             __except(EXCEPTION_EXECUTE_HANDLER)  
  76.             {  
  77.                 KdPrint(("zhui:Catch the exception\n"));  
  78.                 KdPrint(("zhui:The program will keep going\n"));  
  79.                 status = STATUS_UNSUCCESSFUL;  
  80.             }  
  81.             info = cbout;  
  82.             break;  
  83.         }  
  84.     default:  
  85.         status = STATUS_INVALID_DEVICE_REQUEST;  
  86.         break;  
  87.     }  
  88.     pIrp->IoStatus.Status = status;  
  89.     pIrp->IoStatus.Information = info;  
  90.     IoCompleteRequest(pIrp, IO_NO_INCREMENT);  
  91.     return status;  
  92. }  

測試的main函數:

[cpp]  view plain copy

  1. int main()  
  2. {  
  3.     HANDLE hDevice =   
  4.         CreateFile("\\\\.\\HelloDDK",  
  5.                     GENERIC_READ | GENERIC_WRITE,  
  6.                     0,      // share mode none  
  7.                     NULL,   // no security  
  8.                     OPEN_EXISTING,  
  9.                     FILE_ATTRIBUTE_NORMAL,  
  10.                     NULL );     // no template  
  11.     if (hDevice == INVALID_HANDLE_VALUE)  
  12.     {  
  13.         printf("Failed to obtain file handle to device: "  
  14.             "%s with Win32 error code: %d\n",  
  15.             "MyWDMDevice", GetLastError() );  
  16.         return 1;  
  17.     }  
  18.     UCHAR InputBuffer[10];  
  19.     UCHAR OutputBuffer[10];  
  20.     //将輸入緩沖區全部置成0XBB  
  21.     memset(InputBuffer,0xBB,10);  
  22.     DWORD dwOutput;  
  23.     //輸入緩沖區作為輸入,輸出緩沖區作為輸出  
  24.     BOOL bRet;  
  25.     bRet = DeviceIoControl(hDevice, IOCTL_TEST1, InputBuffer, 10, &OutputBuffer, 10, &dwOutput, NULL);  
  26.     if (bRet)  
  27.     {  
  28.         printf("Output buffer:%d bytes\n",dwOutput);  
  29.         for (int i=0;i<(int)dwOutput;i++)  
  30.         {  
  31.             printf("%02X ",OutputBuffer[i]);  
  32.         }  
  33.         printf("\n");  
  34.     }  
  35.     bRet = DeviceIoControl(hDevice, IOCTL_TEST2, InputBuffer, 10, &OutputBuffer, 10, &dwOutput, NULL);  
  36.     if (bRet)  
  37.     {  
  38.         printf("Output buffer:%d bytes\n",dwOutput);  
  39.         for (int i=0;i<(int)dwOutput;i++)  
  40.         {  
  41.             printf("%02X ",OutputBuffer[i]);  
  42.         }  
  43.         printf("\n");  
  44.     }  
  45.     bRet = DeviceIoControl(hDevice, IOCTL_TEST3, InputBuffer, 10, &OutputBuffer, 10, &dwOutput, NULL);  
  46.     if (bRet)  
  47.     {  
  48.         printf("Output buffer:%d bytes\n",dwOutput);  
  49.         for (int i=0;i<(int)dwOutput;i++)  
  50.         {  
  51.             printf("%02X ",OutputBuffer[i]);  
  52.         }  
  53.         printf("\n");  
  54.     }  
  55.     CloseHandle(hDevice);  
  56.     return 0;  
  57. }