要解釋“驅動對象”,就得先從 DriverEntry() 說起:
做過C語言開發的都知道程序是從 main() 函數開始執行。在進行 Windows 驅動程序開發的時候沒有 main() 函數作為函數入口,取而代之的是 DriverEntry().
DriverEntry() 的原型如下:
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath) { }
前面的 extern “C”大概的意思就是調用C編譯器對函數進行編譯,實現C++和C的混合編程。
DriverEntry
描述:初始化驅動程序,定位和申請硬件資源,創建內核對象
參數:
pDriverObject
從I/O管理器中傳來的驅動對象
pRegistryPath
驅動程序在注冊表中的路徑
返回值: 返回初始化驅動狀態
什么是 DRIVER_OBJECT 驅動對象,他的作用是什么?
每一個 驅動對象 代表着一個已經裝載到內核模式下的驅動,以下例程的輸入參數之一就都包含驅動對象:
DriverEntry, AddDevice, Reinitialize(可選例程),Unload(可選例程)
它們都包含指向驅動對象的指針。
驅動對象是一個半透明對象,驅動編寫者必須熟悉它的某些成員對象,以實現驅動的初始化功能和卸載功能(如果該驅動能夠卸載)。以下列出的是驅動對象中能被驅動訪問的成員:
可訪問成員
PDEVICE_OBJECT DeviceObject
指向一個由驅動創建的設備對象,當驅動程序調用IoCreateDevice成功時,該成員會自動更新。驅動程序可以利用該成員以及DEVICE_OBJECT對象中的NextDevice成員來實現對由該驅動創建的所有設備列表中設備的遍歷。
PDRIVER_EXTENSION DriverExtension
驅動擴展對象指針,該對象唯一能訪問的成員是DriverExtension-> AddDevice,對應的是驅動DriverEntry例程中的AddDevice例程。
PUNICODE_STRING HardwareDatabase
指向\Registry\Machine\Hardware,該路徑指向的是注冊表中包含該硬件的配置信息。
PFAST_IO_DISPATCH FastIoDispatch
指向快速I/O入口地址,該成員之用於FSD(文件系統驅動)已經網絡傳輸驅動。
PDRIVER_INITIALIZE DriverInit
DriverEntry例程的入口點,由I\O管理器設置。
PDRIVER_STARTIO DriverStarIo
驅動程序中StartIo例程的入口地址(如果有的話),當驅動初始化時,DriverEntry例程負責設置它,如果驅動程序沒有StartIo,該成員為NULL。
PDRIVER_UNLOAD DriverUnload
驅動程序中Unload例程的入口地址(如果有的話),當驅動初始化時,DriverEntry例程負責設置它,如果驅動程序沒有StartIo,該成員為NULL。
PDRIVER_DISPATCHMajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]
派遣例程表,該表包含了驅動中DispatchXxx routines等所有派遣例程的入口地址。該數組的索引值為IRP_MJ_Xxx,該值代表每一個IRP的主功能函數代碼(IRP major function code), 任何驅動都必須為IRP_MJ_Xxx請求設置入口地址。每一個DispatchXxx例程的定義如下所示:
NTSTATUS(*PDRIVER_DISPATCH) (IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp);
備注:
每一個內核模式的驅動的初始化例程(驅動入口)都必須命名為 DriverEntry,這樣系統才可以自動將驅動加載進來。
如果例程的名字為其他,則驅動編寫者必須為鏈接器指定對應的初始化例程的名字,否則,系統的加載器或者I/O管理器將無法找到驅動的傳遞地址。其他標准驅動的名字可以由驅動編寫者自行決定。
一個驅動必須在驅動對象中設置 DispatchXxx 的入口地址,在驅動加載之后,該驅動對象將傳遞給 DriverEntry。一個設備的驅動必須為每一個 IRP_MJ_XXX 設置一個或者多個DispatchXxx 的入口地址,對同一類型的設備來說,該 IRP_MJ_XXX 都將被處理。一個上層的驅動必須為所有的 IRP_MJ_XXX 設置一個或者多個 DispatchXxx 入口點,這些IRP_MJ_XXX 將被發送到下一級的設備驅動,否則,驅動無法給沒有設置 DispatchXxx 例程發送任何 IRP_MJ_XXX。
DriverEntry 例程設置了的驅動的 AddDevice, StartIO 以及 Unload 等函數的入口地址。
當驅動加載時,設備驅動可以利用 HardWareDatabase 字符串來從注冊表中獲取硬件的配置信息。該字符串對驅動來說只讀。驅動對象中沒有列出的成員是無法訪問的。
什么是 DEVICE_OBJECT 驅動對象,它的作用是什么?
DEVICE_OBJECT 結構被操作系統用來表示為一個”設備對象 ”,這個設備對象可以代表邏輯的、虛擬的或者是物理設備。每一個設備對象都會有一個指針來指向下一個設備對象(如果有的話),形成一個設備鏈。設備鏈的第一個設備是在 DRIVER_OBJECT 驅動對象結構體中指明的。
驅動的 API 函數:
1. RtlInitUnicodeString
作用: 初始化設備名稱指針。
VOID RtlInitUnicodeString
(
IN OUT UNICODE_STRING DestinationString,
IN PCWSTR SourceString
);
參數:
DestinationString
需要初始化的指針 UNICODE_STRING
SourceString
指向一個以空結尾的 Unicode 字符串常量,用這個字符串來初始化 DestinationString
如例:
代碼1:
UNICODE_STRING US1; RtlInitUnicodeString(&US1,L"DDDD");
會動態分配一塊指向“DDDD”的內存指針,賦值給US1.Buffer;
代碼2:
wchar_t tmpstr[260]={0}; UNICODE_STRING US1; RtlInitUnicodeString(&US1,tmpstr);
這時 US1.Buffer 直接指向 tmpstr, 如果修改了 US1,也會同時修改 tmpstr。
另外此時 US1.MaximumLength=2;
要重新設定 MaximumLength=260*2,才能正常使用。
以前總是對符號鏈接不太明白,今天看到了一篇文章,講的很好,記錄一下。
Windows下的設備是以"\Device\[設備名]”形式命名的。例如磁盤分區的c盤,d盤的設備名就是 "\Device\HarddiskVolume1”,"\Device\HarddiskVolume2”, 當然也可以不指定設備名稱。
如果 IoCreateDevice 中沒有指定設備名稱,那么I/O管理器會自動分配一個數字作為設備的名稱。
例如"\Device\00000001"。
\Device\[設備名],不容易記憶,通常符號鏈接可以理解為設備的別名,更重要的是設備名,只能被內核模式下的其他驅動所識別,而別名可以被用戶模式下的應用程序識別。
例如c盤,就是名為"c:"的符號鏈接,其真正設備對象是"\Device\HarddiskVolume1”,所以在寫驅動時候,一般我們創建符號鏈接,即使驅動中沒有用到,這也算是一個好的習慣吧。
驅動中符號鏈接名是這樣寫的 :
L"\\??\\HelloDDK" --->\??\HelloDDK
L"\\DosDevices\\HelloDDK" --->\DosDevices\HelloDDK
在應用程序中,符號鏈接名:
L"\\\\.\\HelloDDK" -->\\.\HelloDDK
winobj 和 DeviceTree 可以用來查看這些信息。
2. IoCreateDevice
作用: 此函數用於創建常規的設備對象.
NTSTATUS IoCreateDevice(
IN PDRIVER_OBJECT DriverObject,
IN ULONG DeviceExtensionSize,
IN PUNICODE_STRING DeviceNameOPTIONAL,
IN DEVICE_TYPE DeviceType,
IN ULONG DeviceCharacteristics,
IN BOOLEAN Exclusive,
OUT PDEVICE_OBJECT *DeviceObject );
參數:
DriverObject
一個指向調用該函數的驅動程序對象.每一個驅動程序在它的DriverEntry過程里接收一個指向它的驅動程序對象. WDM功能和過濾驅動程序也在他們的AddDevice過程接受一個驅動程序對象的指針.
DeviceExtensionSize
DeviceName
(可選的參數)指向一個以零結尾的包含 Unicode 字符串的緩沖區,那是這個設備的名稱,該字符串必須是一個完整的設備路徑名. WDM功能驅動程序和過濾驅動程序它們設備對象沒有名字.
注意:如果設備名未提供(即這個參數是NULL),IoCreateDevice創建的設備對象將不會有一個DACL與之相關聯.
DeviceType
指定一個由一個系統定義的FILE_DEVICE_XXX常量,表明了這個設備的類型 (如FILE_DEVICE_DISK,FILE_DEVICE_KEYBOARD等),或供應商定義的一種新型設備的類型.
DeviceCharacteristics
指定一個或多個系統定義的常量,連接在一起,提供有關驅動程序的設備其他信息.對於可能的設備特征信息, 見DEVICE_OBJECT結構體.
Exclusive
如果指定設備是獨占的,大部分驅動程序設置這個值為FALSE,如果是獨占的話設置為TRUE,非獨占設置為FALSE.
DeviceObject
一個指向 DEVICE_OBJECT 結構體指針的指針,這是一個指針的指針,指向的指針用來接收DEVICE_OBJECT結構體的指針.
返回值:
IoCreateDevice 函數成功時返回 STATUS_SUCCESS,失敗時返回適當的 NTSTATUS 錯誤代碼.這些錯誤代碼是:
STATUS_INSUFFICIENT_RESOURCES //資源不足
STATUS_OBJECT_NAME_EXISTS //指定對象名存在
STATUS_OBJECT_NAME_COLLISION //對象名有沖突
調用要求: 包含文件( wdm.h,ntddk.h)
3. IoCreateSymbolicLink
作用: 此函數用於將設備與符號連接進行綁定。
NTSTATUS IoCreateSymbolicLink(
IN PUNICODE_STRING SymbolicLinkName,
IN PUNICODE_STRING DeviceName );
參數:
SymbolicLinkName
Unicode 字符串指針,是一個用戶態可見的名稱。
DeviceName
Unicode 字符串指針,是驅動程序創建的設備對象名稱。
返回值:
如果符號鏈接創建成功 返回 STATUS_SUCCESS
4. IoDeleteDevice
作用: 此函數用於刪除已建立的設備
VOID IoDeleteDevice(
IN PDEVICE_OBJECT DeviceObject );
參數:
DeviceObject
PDEVICE_OBJECT類型的指針,指向需要刪除的設備對象
無返回值
5. IoDeleteSymbolicLink
作用: 此函數用於從系統中刪除一個符號鏈接。
NTSTATUS IoDeleteSymbolicLink(
IN PUNICODE_STRING SymbolicLinkName );
參數:
SymbolicLinkName
PUNICODE_STRING類型的指針,指向一個緩沖 Unicode 字符串的用戶可見鏈接符號名稱
返回值:
如果符號鏈接刪除成功 返回 STATUS_SUCCESS
添加驅動設備例程
用工具查看驅動及驅動設備
請看下面的實例:
首先創建一個頭文件()
#pragma once #ifdef _cplusplus extern "C" { #endif #include <ntddk.h> //引用內庫的 ntddk.h 頭文件,它提供函數、類、結構和各種接口 #ifdef _cplusplus } #endif static PDEVICE_OBJECT MyDevice; //定義一個全局變量來存儲設備 static UNICODE_STRING SymbolicLinkName; //定義一個全局變量來存鏈接符 #define INITCODE cod_seg("INIT") //注意,是INITCODE cod_seg ,名稱寫錯了會藍屏 #pragma INITCODE NTSTATUS CreateMyDevice(PDRIVER_OBJECT pDriverObject) { NTSTATUS Status; //定義一個局部變量來存儲返回值 UNICODE_STRING DeviceName; // 定義一個局部變量來存儲設備名稱 RtlInitUnicodeString(&DeviceName, L"\\Device\\Myddk.Device"); //實例化設備名稱變量,並給它賦值 RtlInitUnicodeString(&SymbolicLinkName, L\\Device\\DeviceSymbolLink); //實例化設備鏈接符名稱變量,並給它賦值 //用 IoCreateDevice API 函數創建設備 Status = IoCreateDevice(pDriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, TRUE, &MyDevice); if (!NT_SUCCESS(Status)) { if (Status==STATUS_INSUFFICIENT_RESOURCES) { KdPrint(("資源不足")); } if (Status == STATUS_OBJECT_NAME_EXISTS) { KdPrint(("指定對象名稱存在")); } if (Status == STATUS_OBJECT_NAME_COLLISION) { KdPrint(("對象名沖突")); } KdPrint(("創建設備失敗!!")); return Status; } MyDevice->Flags |= DO_BUFFERED_IO; //設置讀寫方式,這里為緩沖區讀寫方式 //給創建的設備名稱綁定鏈接符 Status = IoCreateSymbolicLink(&SymbolicLinkName, &DeviceName); if (!NT_SUCCESS(Status)) { IoDeleteDevice(MyDevice); KdPrint(("設備鏈接符綁定失敗,設備已刪除!!")); return Status; } KdPrint(("設備創建完成,設備已成功加載")); return Status; }
#ifdef _cplusplus extern "C" { #endif #include "myddk.h" #ifdef _cplusplus } #endif #define PAGEDCODE cod_seg("PAGE") //注意,是PAGEDCODE cod_seg ,名稱寫錯了會藍屏 #pragma PAGEDCODE VOID Unload(PDRIVER_OBJECT pDriverObject); extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pUnicodeString) { CreateMyDevice(pDriverObject); //調用頭文件里的創建設備的函數,來進行創建設備 //用 DriverUnload 宏來進行卸載,然后把卸載的過程賦給它,其實DriverUnload已經可以卸載驅動了, //這里賦值的作用主要是讓它能打印出消息 pDriverObject->DriverUnload = Unload; KdPrint(("我的第一個驅動")); return STATUS_SUCCESS; } //創建一個卸載驅動設備的過程 VOID Unload(PDRIVER_OBJECT pDriverObject) { //卸載驅動對象,什么對象呢,DeviceObject設備對象,設備對象叫什么呢?叫 MyDevice pDriverObject->DeviceObject = MyDevice; //用 IoDeleteDevice API函數卸載設備,參數為指定要卸載的設備對象, //此參數是已在 myddk.h頭文件中實例化的設備對象 IoDeleteDevice(MyDevice); //用 IoDeleteSymbolicLink API函數卸載鏈接符,參數為指定要卸載的鏈接符對象, //此參數為已在 myddk.h頭文件中實例化的 SymbolicLinkName鏈接符對象 IoDeleteSymbolicLink(&SymbolicLinkName); KdPrint(("驅動和設備移除成功!!")); }
然后,在源文件中,來加載與卸載這個設備!
#ifdef _cplusplus extern "C" { #endif #include "myddk.h" #ifdef _cplusplus } #endif #define PAGEDCODE cod_seg("PAGE") //注意,是PAGEDCODE cod_seg ,名稱寫錯了會藍屏 #pragma PAGEDCODE VOID Unload(PDRIVER_OBJECT pDriverObject); extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pUnicodeString) { CreateMyDevice(pDriverObject); //調用頭文件里的創建設備的函數,來進行創建設備 //用 DriverUnload 宏來進行卸載,然后把卸載的過程賦給它,其實DriverUnload已經可以卸載驅動了, //這里賦值的作用主要是讓它能打印出消息 pDriverObject->DriverUnload = Unload; KdPrint(("我的第一個驅動")); return STATUS_SUCCESS; } //創建一個卸載驅動設備的過程 VOID Unload(PDRIVER_OBJECT pDriverObject) { KdPrint(("驅動和設備移除成功!!")); }
然后,我們用 DriverMonitor 工具來把生成的驅動加載到系統中,再用 Winobj 工具來查看設備,看是否創建了我們定義的名稱為“Myddk.Device”的設備。
如圖:
驅動加載成功!
在 Winobj 工具中的 Device 路徑項中可以查看到,我們的設備 Myddk.Device 以創建成功。設備名稱為 Myddk.Device;設備類型為 Device;可是設備的鏈接符沒有??這是為什么,鏈接符也定義初始化也綁定了,如果綁定不成功,會刪除設備啊!!
呵呵呵,我也不明白…這問題暫擱着,我問繼續往下看。
現在我們用 DriverMonitor 工具卸載驅動看看!!
卸載成功!
什么情況??怎么設備還在呢??
但是驅動卻不在了!!
哦,想起來了,我剛剛確實卸載的只是驅動,並沒卸載什么設備…,暈,咋這么古板呢!!
好吧!看來我們還得用代碼向系統內存發送卸載設備的指令!!
見下面一小段代碼:
//創建一個卸載驅動設備的過程 VOID Unload(PDRIVER_OBJECT pDriverObject) { //卸載驅動對象,什么對象呢,DeviceObject設備對象,設備對象叫什么呢?叫 MyDevice pDriverObject->DeviceObject = MyDevice; //用 IoDeleteDevice API函數卸載設備,參數為指定要卸載的設備對象, //此參數是已在 myddk.h頭文件中實例化的設備對象 IoDeleteDevice(MyDevice); //用 IoDeleteSymbolicLink API函數卸載鏈接符,參數為指定要卸載的鏈接符對象, //此參數為已在 myddk.h頭文件中實例化的 SymbolicLinkName鏈接符對象 IoDeleteSymbolicLink(&SymbolicLinkName); KdPrint(("驅動和設備移除成功!!")); }
再次測試…
果不其然,添加卸載設備的API后順利卸載。






