驅動程序入門篇


驅動程序分為兩類: 一個是 Kernel(內核) 模式驅動,另一個是 Windows (用戶窗口層)模式驅動。

這兩種模式本質是相同,但細節不同。本文介紹的是內核模式驅動和驅動程序的安裝與使用。

驅動程序同普通的 .exe,.dll 一樣,都屬於 PE 文件,而且都有一個入口函數。但 .exe 中,入口函數是 main() / WinMain() 和 Unicode 的 wmain() / wWinmain(),.dll 的入口函數則可有可無,它是 DllMain()。

所以驅動程序也有入口函數,而且是必須的,它是 DriverEntry() 函數。再次提示,它是必須的! 因為 I/O 管理器會首先調用驅動程序的DriverEntry() 函數,它的作用就像 DllMain() --完成一些初始化工作。

雖然我們有時候把 DriverEntry() 比作 main(),但二者在本質上不同,DriverEntry() 的生命周期非常短,其作用僅是將內核文件鏡像加載到系統中時進行驅動初始化,調用結束后驅動程序的其他部分依舊存在,並不隨它而終止。

所以我們一般可把 DriverEntry() 稱為“入口函數”,而不可稱為“主函數”。因此作為內核驅動來說,它沒有一個明確的退出點,這應該是 atexit 無法在內核中實現的原因吧。什么是 atexit() 函數呢?它是注冊終止函數(即main執行結束后調用的函數) !

DriverEntry()一共有 2 個參數:

           1)PDRIVER_OBJECT DriverObject,指向驅動程序對象的指針。我們操作驅動程序,全靠它,它是由 I/O 管理器傳遞進來的;

           2)PUNICODE_STRING RegistryPath,驅動程序的服務主鍵,這個參數的使用並不多,但要注意,在DriverEntry() 返回后,它可能
會消失,所以如果需要使用,記住先要保存下來

DriverEntry() 等內核函數的返回值一般是 NTSTATUS 類型的。使用宏NT_SUCCESS(NTSTATUS status)可檢測返回狀態是否成功。它是一個 ULONG 值(無符號長整型,另一種表示形式:Unsigned long  變量名),具體的定義,請參見 DDK 中的 NTSTATUS.H 頭文件,里邊有詳細的定義。

在DriverEntry中,一般需要做一下幾件事情: 設置驅動卸載例程、 注冊 IRP 的派遣函數、 創建設備對象等!並且驅動卸載例程與 IRP 派遣函數都是對驅動對象設置的。

既然要寫驅動程序就需要確定如何來與驅動程序通信,常用的共享內存,共享事件,IOCTL宏,或者直接 ReadFile() 或 WriteFile() 進行讀寫,在本文里我就采用一種簡單的、但又很常用的 IOCTL 宏,它依賴的 IRP 派遣例程 IRP_MJ_DEVICE_CONTROL,Win32程序使用 DeviceIoControl() 與驅動進行通信,根據不同的 IOCTL 宏,輸出不同的調試信息。

驅動程序與 I/O 管理器通信,使用的是 IRP,即 I/O 請求包。IRP 分為2部分:IRP 首部;IRP堆棧。

在驅動程序中,IRP 派遣例程起着很重要的作用,每個 IRP 派遣例程,幾乎都有對應的 Win32 API 函數。

下面介紹 IRP 派遣例程 :

名稱 描述 調用者
IRP_MJ_CREATE 請求一個句柄 CreateFile
IRP_MJ_CLEANUP 在關閉句柄時取消懸掛的IRP CloseHandle
IRP_MJ_CLOSE 關閉句柄 CloseHandle
IRP_MJ_READ 從設備得到數據 ReadFile
IRP_MJ_WRITE 傳送數據到設備 WriteFile
IRP_MJ_DEVICE_CONTROL  控制操作(利用IOCTL宏) DeviceIoControl
IRP_MJ_INTERNAL_DEVICE_CONTROL 控制操作(只能被內核調用) N/A
IRP_MJ_QUERY_INFORMATION 得到文件的長度 GetFileSize
IRP_MJ_SET_INFORMATION 設置文件的長度 SetFileSize
IRP_MJ_FLUSH_BUFFERS 寫輸出緩沖區或丟棄輸入緩沖區 FlushFileBuffers\FlushConsoleInputBuffer \PurgeComm
IRP_MJ_SHUTDOWN 系統關閉 InitiateSystemShutdown

提到派遣例程,必須理解 IRP(I/O Request Package),即"輸入/輸出請求包"這個重要數據結構的概念。Ring3(用戶層)通過 DeviceIoControl 等函數向驅動發出 I/O 請求后,在內核中由操作系統將 I/O 請求包轉化為 IRP 的數據結構,並"派遣"到對應驅動的派遣函數中,如圖下圖所示。

Ring3(用戶層)應用程序調用kernel32.dll導出的DeviceIoControl函數后,會調用到ntdll.dll導出的NtDeviceIoControlFile函數,進而調用到系統內核模塊提供的服務函數NtDeviceIo ControlFile,該函數會將I/O請求轉化為IRP包,並發送到對應驅動的派遣例程函數中。對於其他I/O相關函數,如CreateFile、ReadFile、WriteFile、GetFileSize、SetFileSize、CloseHandle等也是如此。

                                                            新建位圖圖像

如上圖,從 Ring3 (用戶層)的 I/O 請求到內核的 IRP 結構的請求包

一個 IRP包 該發往驅動的哪個派遣例程函數,是由 IRP 結構中的 MajorFunction (IRP主類型)屬性決定的,MajorFunction 屬性的值是一系列宏,具體可在網上查詢。

IRP 首部信息如下:
                      IO_STATUS_BLOCK IoStatus                 //包含 I/O 請求的狀態
                      PVOID AssociatedIrp.SystemBuffer       //  如果執行緩沖區 I/O,這個指針指向系統緩沖區
                      PMDL MdlAddress                        //  如果直接 I/O,這個指針指向用戶緩沖區的存儲器描述符表
                      PVOID UserBuffer                         // I/O 緩沖區的用戶空間地址


IRP 堆棧信息如下:

                      UCHAR MajorFunction              //(主要類型) 指示 IRP_MJ_XXX派遣例程
                      UCHAR MinorFunction              //(IRP 的子類型) 同上,一般文件系統和 SCSI 驅動程序使用它
                      union Parameters  {   //MajorFunction的聯合類型
                             struct Read                       //IRP_MJ_READ的參數
                             ULONG Length
                             ULONG Key
                             LARGE_INTEGER ByteOffset
                             struct Write                      //IRP_MJ_WRITE的參數
                             ULONG Length
                             ULONG Key
                             LARGE_INTEGER ByteOffset
                             struct DeviceIoControl            //IRP_MJ_DEVICE_CONTROL和IRP_MJ_INTERNAL_DEVICE_CONTROL的參數
                             ULONG OutputBufferLength
                             ULONG InputBufferLength
                             ULONG IoControlCode
                             PVOID Type3InputBuffer
                      } 
                      PDEVICE_OBJECT DeviceObject       //請求的目標設備對象的指針
                      PFILE_OBJECT FileObject           //請求的目標文件對象的指針,如果有的話操作 IRP。

對於不同的 IRP 函數,操作也是不同的:有的只操作 IRP 首部;有的只操作 IRP 堆棧;還有操作 IRP 整體。

下面是一些常用的函數:
IRP整體:                                                                                                                   

名稱 描述 調用者
IoStartPacket 發送IRP到Start I/O例程 Dispatch (派遣)
IoCompleteRequest 表示所有的處理完成 DpcForIsr (連接中斷和注冊)
IoStartNextPacket 發送下一個IRP到Start I/O例程 DpcForIsr
IoCallDriver 發送IRP請求 Dispatch
IoAllocateIrp 請求另外的IRP Dispatch
IoFreeIrp 釋放驅動程序分配的IRP I/O Completion (I/0完成)

IRP堆棧:

名稱 描述 調用者
IoGetCurrentIrpStackLocation 得到調用者堆棧的指針 Dispatch
IoMarkIrpPending 為進一步的處理標記調用者I/O堆棧 Dispatch
IoGetNextIrpStackLocation 得到下一個驅動程序的I/O堆棧的指針 Dispatch
IoSetNextIrpStackLocation 將I/O堆棧指針壓入堆棧 Dispatch

 

/************************************************************************************************
*                                 驅動程序與應用程的通訊示例                                                                                                     *
*************************************************************************************************
* 應用程序類型:ControlAppliaction                                                                                                                             *
* 應用程序名稱:Appinformation                                                                                                                                 *
* 驅  動 名 稱:AppDriver                                                                                                                                           *
* 符號鏈接名稱:AppSymbolLink                                                                                                                                 *
*************************************************************************************************
* 簡述:編寫一個驅動程序與應用程之間的通信訊實例,讓應用程序輸入數據,進而發送 I/O 請求,當                                             *
* 系統接的 I/O 管理器在接收到應用層的設備讀寫請求后,將請求封裝為一個 IRP 請求(包括 IRP 頭部                                            *
* 和 IRP STACK_LOCATINO 數組),然后根據 IRP 的類型發送到相應類型派遣例程,派遣例程再把接                                           *
* 受到的 IRP發送到對應的設備的設備棧的最頂層的那個設備驅動(如果頂層的設備對象的派遣函數結                                              *
* 束了IRP的請求,則這次IO請求結束,如果沒有將 IRP 請求結束,那么操作系統將IRP轉發到設備棧的                                           *
* 下一層設備處理。如果這個設備的派遣函數依然沒結束該IRP請求,則繼續向下層設備進行轉發)。                                               *
* IO請求結束后驅動會把處理后的數據返回應用程序。                                                                                                         *
*************************************************************************************************/

先介紹一下驅動程序和應用程序將要使用到的 Win32 API 函數:

1、DriverEntry(): 驅動程序的入口函數,它將內核文件鏡像加載到系統中時進行驅動初始化。

2、DriverUnload():負責刪除在DriverEntry中創建的設備對象並且將設備對象所關聯的符號鏈接刪除,另外,DriverUnload還負責對一些資源進行回收。

3、IoDeleteDevice():此 API 函數為刪除設備對象的函數,只有一個參數,即為妖卸載的設備名稱。

4、IoDeleteSymbolicLink() : 此 API 函數為刪除符號鏈接的函數,只有一個參數,即為符號鏈接名稱。

5、IoCreateDevice():此 API 函數創建設備對象供驅動程序使用。包含七個參數。

6、RtlInitUnicodeString():此 API 函數初始化一個Unicode字符串,主要用來實例化設備名稱或符號鏈接名稱。

7、IoCreateSymbolicLink():此 API 函數主要是對已實例化的設備名稱和符號鏈接名稱進行綁定。參數為符名和設備名

8、IoGetCurrentIrpStackLocation():此 API 函數向調用者返回一個 IRP 當前堆棧位置的指針,參數為設備對象和 IRP 指針(PIRP)。

9、IoCompleteRequest():此 API 函數表示調用方已完成所有 I/O 請求處理操作,並將給定的 IRP 傳給 I/O 管理器。無返回值。

10、CTL_CODE() 宏:此宏為系統提供的,在wdm中定義的,用於創建一個唯一的32位系統I/O控制代碼,這個控制代碼包括4部分組成。

11、CreateFile(): 此函數創建或打開(文件,文件流,目錄,磁盤,控制台緩沖區)等設備,並返回一個用來訪問這些對象的句柄 。

12、DeviceIoControl():  此函數直接發送一個控制代碼到指定的設備驅動程序,使相應的設備來執行相應的操作。

13、CloseHandle():此函數關閉一個打開的對象句柄,該對象句柄可以是線程句柄,也可以是進程、信號量等其他內核對象的句柄 。  

============================================================================================================

下面請看驅動程序代碼:

首先用 C++ 創建一個空白的“ 控制台應用程序”項目:然后新建一個源文件:Appinformation.cpp

代碼如下:

#include <iostream>
#include <tchar.h>
#include <Windows.h>
#include "InputBuffer.h"
using std::cout;
using std::endl;

int _tmain(int argc, _TCHAR* argv[])
{    
    /************************************函數說明*********************************************
    * 函數名:CreateFile                                                                     *
    * 類型:Win32 API函數                                                                    *
    * 作用:這是一多功能的函數,可打開或創建如:控制台,通信資源,目錄(只讀打開),                    *
    * 磁盤驅動器,文件,郵槽,管道等對象,並返回可訪問的句柄:。                                     *
    * 參數說明:                                                                             *
    * LPCTSTR lpFileName : 普通文件名或者設備文件名                                            *
    * DWORD dwDesiredAccess : 訪問對象的權限,希望以哪種方式打開,建議兩者兼備。                    *
    * DWORD dwShareMode : 共享模式,建議兩者兼備。                                             *
    * LPSECURITY_ATTRIBUTES : 用來設定一個指向是否由子進程返回的句柄可以被繼承                     *
    * DWORD dwCreationDisposition : 表示判斷文件存在或不存在,按需求如何創建                      *
    * DWORD dwFlagsAndAttributes : 文件標志或設備屬性                                         *
    * HANDLE hTemplateFile :模板文件,給創建的文件提供文件屬性和擴展屬性,一般為空                   *
    * 返回值: 獲取設備的句柄                                                                   *
    *****************************************************************************************/
    HANDLE  DeviceHandle = CreateFile(
        L"\\\\.\\AppSymbolLink",              //\\??\\設備符號鏈接名稱,注意這里要用 L 寬字符轉換
        GENERIC_READ | GENERIC_WRITE,    
        FILE_SHARE_READ | FILE_SHARE_WRITE,   
        NULL,                             
        OPEN_EXISTING,                        //枚舉:OPEN_EXISTING 表示文件必須已經存在。
        FILE_ATTRIBUTE_NORMAL,                 //此枚舉表示文件沒有其他屬性集。
        NULL);                                 //模板文件,一般為空
    if (DeviceHandle == INVALID_HANDLE_VALUE){
        printf("獲取文件或設備句柄失敗!!");
        getchar();                            //讀取清除緩沖區字符
        return -1;
    }

        printf("獲取驅動程序句柄成功 \n");
        int a=10, b=20, c=30,e=0,f=0,g=0;
        InputBuffer(DeviceHandle, a, b, c);
        for (int i = 0; i < 3; i++)
        {
            switch (i){
            case 0:{
                       e = OtnC[i];
            }
            case 1:{
                       f = OtnC[i];
            }
            case 2:{
                       g = OtnC[i];
            }
            } //End switch (i)
        }  //End for 

    printf("a=%d,b=%d,c=%d\n",e, f, g);    
    CloseHandle(DeviceHandle);            //過程完成后,最好關閉句柄    
    cout << "驅動通訊完成" << endl;
    system("pause");
}

 

好了,按設備的符號鏈接打開了設備,並得到了它的句柄,有了句柄,我們就能更好的和它進行互動了!!

如果想和他通信,就需要發送通訊口令了,我們把發送口令的函數它寫在頭文件:InputBuffer.h中。

#pragma once
#include <iostream>
#include <Windows.h>
#include "SetCtlCode.h"

int OtnC[3] = { 0 };  //整形全局數組變量

/************************************函數說明****************************************
* 函數名:InputBuffer                                                               *
* 類型:自定義無返回值函數                                                             *
* 作用:這個函數用於給控制代碼函數提供參數,也為了其函數調用獲取返回值。                       *                                                                                
* 參數說明:                                                                         *
* DeviceHandle : 設備句柄,用於指定通信的設備                                          *
* a : 通訊數據。                                                                    *
* b : 通訊數據。                                                                    *
* c : 通訊數據                                                                      *
* 返回值: 無 ,因為返回值已賦給了全局變量                                                *
************************************************************************************/

void InputBuffer(HANDLE DeviceHandle, int a, int b, int c){
    int ItnC[3] = { a, b, c }; ULONG wtWrite;
    int *InBuffer = ItnC;
    int *OutBuffer = OtnC;

    /************************************函數說明********************************
    * 函數名:DeviceIoControl                                                   *
    * 類型:Win32 API函數                                                       *
    * 作用:發送控制代碼到指定設備驅動程序。                                         *    
    * 參數說明:                                                                *
    * hDevice Long:設備句柄。用於指定通信的設備                                    *
    * dwIoControlCode Long:應用程序調用驅動程序的控制命令,就是IOCTL_XXX IOCTLs。   *
    * lpInBuffer Any:應用程序傳遞給驅動程序的數據緩沖區地址。                        *
    * nInBufferSize Long:應用程序傳遞給驅動程序的數據緩沖區大小,字節數。             *
    * lpOutBuffer Any:驅動程序返回給應用程序的數據緩沖區地址。                       *
    * nOutBufferSize Long:驅動程序返回給應用程序的數據緩沖區大小,字節數。            *
    * lpBytesReturned Long:驅動程序實際返回給應用程序的數據字節數地址。              *
    * lpOverlapped OVERLAPPED:針對同步操作,請用ByVal As Long傳遞零值              *
    * 返回值: 非0成功,0失敗                                                      *
    *****************************************************************************/

    DeviceIoControl(DeviceHandle, SetCtlCodeA, InBuffer, sizeof(InBuffer)*3, OutBuffer, sizeof(OutBuffer)* 3, &wtWrite, NULL);           
    printf("DeviceIoControl 發送控制代碼完成 \n");
    int e = 0, f = 0, g = 0;
    for (int i = 0; i < 3; i++)
    {
        OtnC[i] = OutBuffer[i];
    }
    return ;
}

看到沒! DeviceIoControl 函數中的第二個參數就需要提供口令了,這里專業的說法叫,“I/O控制碼”(也叫輸入輸出控制碼),我們把這控制代碼卸載另一個頭文件中:SetCtlCode.h

#include <winioctl.h>
/************************************宏說明***********************************
* 宏名:CTL_CODE                                                             *
* 類型:Win32 宏命令                                                          *
* 作用:用於創建一個唯一的32位系統I/O控制代碼。                                   *
* 參數說明:                                                                 *
* DeviceType:設備類型 ,枚舉如(FILE_DEVICE_UNKNOWN)未知設備                   *
* Function:  設備唯一功能標識碼,有效范圍:32768-65535                          *
* Method:    I/O訪問內存的使用方式,枚舉,一般用METHOD_BUFFERED。                *
* Access:    設備訪問限制,,枚舉,一般用FILE_ANY_ACCESS                        *
* 返回值:    無                                                              *
*****************************************************************************/
//可以只寫一個
#define SetCtlCodeA CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define SetCtlCodeB CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ANY_ACCESS)

好了,到此,一個發送 I/O 請求包的 控制台應用程序就創建並編寫完畢,下面來開始我們的驅動編寫:

先創建一個 WDM 工程,然后添加一個頭文件,名為:AppDriver.cpp

然后開入編寫入口函數:

#ifdef _cplusplus
extern "C"
{
#endif
#include <ntddk.h>
#include "AddDevice.h"
#include "SendIRP.h"
#ifdef _cplusplus
}
#endif

#define PAGEDCODE cod_seg("PAGE")             //將以下函數載入到分頁內存中執行
#pragma PAGEDCODE                              //載入到分頁內存中執行
VOID Unload(PDRIVER_OBJECT pDriverObject);  
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistrPath)
{
    CreateMyDevice(pDriverObject);                                      //調用自定義的 CreateMyDevice 來創建設備對象

    //注冊派遣函數,與IRP 請求包的數據類型一一對應
    pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = Send_Irp; // 發送I/O管理器和其他系統組件、其他內核模式驅動
    pDriverObject->MajorFunction[IRP_MJ_CREATE] = Send_Irp;         // 創建設備,CreateFile會產生此IRP,返回一個句柄給用戶應用程序
    pDriverObject->MajorFunction[IRP_MJ_READ] = Send_Irp;         // 讀取設備內容,ReadFile會產生此IRP
    pDriverObject->MajorFunction[IRP_MJ_WRITE] = Send_Irp;       // 改寫設備內容,WriteFile時會產生此IRP
    pDriverObject->MajorFunction[IRP_MJ_CLOSE] = Send_Irp;       // 關閉保留的通往目標設備對象的句柄,CloseHandle會產生此IRP
    pDriverObject->MajorFunction[IRP_MJ_CLEANUP] = Send_Irp;    // 清除用戶模式應用程序的目標設備對象的句柄,CloseHandle會產生此IRP    
    pDriverObject->DriverUnload = Unload;                               //調用卸載函數    
    KdPrint(("AppDriver 驅動加載成功!"));
    return STATUS_SUCCESS;
}

VOID Unload(PDRIVER_OBJECT pDriverObject)
{    
    pDriverObject->DeviceObject = AppDevice;    //指向設備對象
    IoDeleteDevice(AppDevice);                  //卸載設備對象
    IoDeleteSymbolicLink(&AppSymbolLinkName);   //卸載符號鏈接
    KdPrint(("驅動卸載成功!"));
}

入口函數 DriverEntry 的任務包含了 初始化系統代碼為驅動指令,還包含了對自定義 Unload 函數的調用和 IRP 請求包的派遣。

先我們利用 IoCreateDevice  API函數來創建設備驅動,我把它寫在另一個頭文件中:AddDevice.h

#pragma once            //僅編譯一次,用途:常出現在頭文件中,因為同一頭文件會在許多源文件中多次引用,如果沒有指定編譯一次,則編譯時出現重定義錯誤。
#ifdef _cplusplus
extern "C"
{
#endif 
#include <ntddk.h>
#ifdef _cplusplus
}
#endif 

static PDEVICE_OBJECT AppDevice;                          //設備對象全局變量
static UNICODE_STRING AppDeviceName, AppSymbolLinkName;   //設備名稱和符號鏈接名稱全局變量

//創建設備
#define INITCODE cod_seg("INIT")                               // 初始化的時候載入內存,然后可以從內存中卸載掉
#pragma INITCODE
NTSTATUS CreateMyDevice(PDRIVER_OBJECT pDriverObject){
    NTSTATUS Status;                                                   //返回值局部變量        
    RtlInitUnicodeString(&AppDeviceName, L"\\Device\\AppDriver");      //初始化設備名稱
    RtlInitUnicodeString(&AppSymbolLinkName, L"\\??\\AppSymbolLink");  //初始化符號鏈接名稱
        
    //調用 IoCreateDevice API 函數創建設備對象
    Status=IoCreateDevice(pDriverObject, 0, &AppDeviceName, FILE_DEVICE_UNKNOWN, NULL, TRUE, &AppDevice);

    
    if (!NT_SUCCESS(Status)){                  //判斷返回值,看是否創建成功
        switch (Status){                        //如果沒有成功,返回枚舉錯誤消息
        case STATUS_INSUFFICIENT_RESOURCES:
            KdPrint(("資源部足!!"));
            break;
        case STATUS_OBJECT_NAME_EXISTS:
            KdPrint(("指定對象名存在!!"));
            break;
        case STATUS_OBJECT_NAME_COLLISION:
            KdPrint(("對象名稱沖突!!"));
            break;
        }        
    }

    //設備創建成功后,就設置它的緩沖區讀寫方式,這里為(讀寫)
    AppDevice->Flags |= DO_BUFFERED_IO; 

    //調用 IoCreateSymbolicLink API 函數對設備名稱和符號鏈接名稱進行綁定
    Status=IoCreateSymbolicLink(&AppSymbolLinkName, &AppDeviceName);
    
    //判斷返回值,看是否綁定成功
    if (NTSTATUS(Status) != STATUS_SUCCESS){              //判斷返回值,看是否綁定成功
        IoDeleteDevice(AppDevice);                        //如果未成功,調用IoDeleteDevice 函數刪除設備
        KdPrint(("設備因符號鏈接綁定失敗而被刪除!!"));
    }
    KdPrint(("設備創建完成,設備已成功加載"));
    return Status;                                        //如果成功就返回成功的宏
}

到此,我們就真正創建了一個驅動,沒錯確實創建了,但像個人樣,但卻沒有靈魂啊,沒有靈魂就沒有意識,沒有意識就是死的!!!

所以,我們就得給他構造自主的潛微的意識,讓它能對別人的呼叫會有所回應!!這里就要用到 IoGetCurrentStackLocation 這個API函數來接受被人發出的信息(IRP 請求包)派遣,從哪里獲取呢,這里所有的信息都存放在 Stack 堆棧上,所以此時就需要在堆棧上獲得: 我們再次新建一個頭文件SendIRP,在這里面編寫:

#ifdef _cplusplus
extern "C"
{
#endif 
#include <ntddk.h>
#include "SetCtlCode.h"
#ifdef _cplusplus
}
#endif 

#define INITCODE cod_seg("INIT")                                 // 初始化的時候載入內存,然后可以從內存中卸載掉
#pragma INITCODE 
NTSTATUS Send_Irp(PDEVICE_OBJECT pDeviceObject, PIRP pIrp){
      NTSTATUS Status; PIO_STACK_LOCATION Stack;                 //定義返回值和堆棧變量
      Stack=IoGetCurrentIrpStackLocation(pIrp);                  //獲取當前 IRP 在堆棧上的位置指針      
      UCHAR Irp_Type = Stack->MajorFunction;                     //獲取 IRP 在堆棧中的類型
      KdPrint(("Irp_Type=%d \n", Irp_Type));

      //對 IRP 的類型進行判斷,進行不同的處理
      switch (Irp_Type){
          case IRP_MJ_DEVICE_CONTROL:{
                                       KdPrint(("In to IRP_MJ_DEVICE_CONTROL \n"));
                                       ULONG CTL_CODE = Stack->Parameters.DeviceIoControl.IoControlCode;
                                       ULONG InBufferLength = Stack->Parameters.DeviceIoControl.InputBufferLength;
                                       ULONG OutBufferLength = Stack->Parameters.DeviceIoControl.OutputBufferLength;
                                       int *OutBuffer = (int *)pIrp->AssociatedIrp.SystemBuffer;
                                       KdPrint(("Get CTL_CODE,CTL_CODE=%d \n",CTL_CODE));
                                       KdPrint(("InBufferLength=%d \n" , InBufferLength));
                                       KdPrint(("OutBufferLength=%d \n", OutBufferLength));
                                       KdPrint(("*OutBuffer=%d \n", OutBuffer));
                                       switch (CTL_CODE){
                                           case Add_Ctl_Code:{
                                                                 KdPrint(("In to Add_Ctl_Code \n"));
                                                                 int i, a, b, c;
                                                                 _asm{                      //用匯編語句來(讀取)緩沖區指針中的數據
                                                                     mov eax,OutBuffer
                                                                         mov ebx,[eax]
                                                                         mov a,ebx
                                                                         mov ebx, [eax+4]
                                                                         mov b,ebx
                                                                         mov ebx, [eax+8]
                                                                         mov c,ebx
                                                                 }
                                                                 KdPrint(("a=%d,b=%d,c=%d \n",a,b,c));
                                                                 i = a + b + c;
                                                                 KdPrint(("i=a + b + c,i=%d \n", i));
                                                                  a =a*i, b=b*i, c=c*i;
                                                                 _asm{                      //用匯編語句來(填充)緩沖區指針
                                                                     mov eax,a
                                                                         mov ebx,OutBuffer
                                                                         mov [ebx],eax
                                                                         mov eax, b
                                                                         mov [ebx + 4],eax                                                                                                                                    
                                                                         mov eax,c
                                                                         mov [ebx + 8],eax                                                                         
                                                                 }
                                                                 pIrp->IoStatus.Information = OutBufferLength;
                                                                 pIrp->IoStatus.Status = STATUS_SUCCESS;
                                                                 IoCompleteRequest(pIrp, IO_NO_INCREMENT);
                                                                 KdPrint(("Leave the Add_Ctl_Code \n", i));
                                                                 break;
                                           }
                                           case Sub_Ctl_Code:{
                                                                 break;
                                           }
                                       }  //End (switch (CTL_CODE))
                                       break;
          }
          case IRP_MJ_CREATE:{
                                 break;
          }
          case IRP_MJ_READ:{
                               break;
          }
          case IRP_MJ_WRITE:{
                                break;
          }
          case IRP_MJ_CLOSE:{
                                break;
          }
          case IRP_MJ_CLEANUP:{
                                  break;
          }

      }  // End (switch (Irp_Type))

      return STATUS_SUCCESS;

}  //End  Send_Irp Function

此斷代碼主要對在堆棧中獲取其它應用程序發送的 I/O 請求包的 IRP 派遣,驅動程序再更具 IRP 的派遣類型就行不同的處理,然后,得到請求數據,並對它進行了重新計算,然后通過匯編語句對計算后的數據進行反饋給堆棧中,讓發送I/O 請求包 的應用程序進行接受數。

接受 或發送 I/O 請求包 都同樣需用 I/O 控制代碼,才能完成!所以我們再來編寫I/O 控制代碼,把它寫另一個頭文件中:SetCtlCode.h

#define Add_Ctl_Code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define Sub_Ctl_Code CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ANY_ACCESS)

由於驅動已經包含了 IOCTL 宏,所以這里不必再包含<winioctl.h>頭文件了。

到此整個應用程序和驅動整形就編寫完成! 我們可以在虛擬機制中通過 DriverMonitor .exe 和 DbgView工具進行加載驅動並進行查看,然后打開AppInformation.exe 控制台應用程序進行發送 I/O請求包,就能到接受驅動計算過的數據了!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM