WDM驱动程序入门
SourceUrl
WDM驅(qū)動程序入門(1)——HelloWDM
WDM驅(qū)動程序是一種很新的東西,相信很多人都跟我一樣,對它很感興趣,但是又找不到學習的切入點。究其原因,還是因為WDM是一種非常“死板板”的程序,它一運行就是工作在系統(tǒng)的底層RING 0處,提供各種接口給應用程序調(diào)用。也正因為如此,它不像普通的應用程序一樣,可以很快地上手——更多的時候,你是在閱讀它的技術(shù)資料和各種接口信息,你還要非常地熟悉系統(tǒng)底層的工作原理,否則一個不小心,就“藍屏”了,呵呵——話說回來,寫驅(qū)動程序的時候,死機是家常便飯。
因此很多人都對WDM望而生畏了。回想一下,我剛開始學WDM的情形還歷歷在目——看書看了整整3天,但是看完之后好像跟沒看也差不了多少,還是不知道怎么入門,甚至連怎么寫一個“Hello World”都不知道——后來才知道其實WDM是沒有所謂的“Hello World”程序的,唉,真是痛苦啊,這主要還是因為網(wǎng)絡上的WDM資料太少造成的。為了不讓大家重蹈我的覆轍并對WDM有個感性的認識,在此我給出一個最簡單的完整的WDM框架,并附有注釋,姑且可以算是一個入門的“Hello World”吧。
廢話少說,讓我們馬上開始研究,要求讀者已安裝DDK 2000。(在Win98中我還沒有測試過,不清楚是否能正常運行)
/***************************************************************
程序名稱:Hello World for WDM
文件名稱:HelloWDM.h
作者:羅聰
日期:2002-8-16
***************************************************************/
//頭文件,只是聲明一些函數(shù)和變量,比較簡單就不多說了,請讀者自行研究:
#ifdef __cplusplus
extern "C"
{
#endif
#include "ntddk.h"
#ifdef __cplusplus
}
#endif
typedef struct _DEVICE_EXTENSION
{
??? PDEVICE_OBJECT??? fdo;
??? PDEVICE_OBJECT??? NextStackDevice;
??? UNICODE_STRING??? ifSymLinkName;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
?????????????????????????? IN PDEVICE_OBJECT PhysicalDeviceObject);
NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo,
??????????????????????? IN PIRP Irp);
/***************************************************************
程序名稱:Hello?World?for?WDM
文件名稱:HelloWDM.cpp
作者:羅聰
日期:2002-8-16
***************************************************************/?
//一定要的頭文件,聲明了函數(shù)模塊和變量:
#include?"HelloWDM.h"?
/***************************************************************
函數(shù)名稱:DriverEntry()
功能描述:WDM程序入口
***************************************************************/
//extern?"C"是必須的,表示“用C鏈接”。如果你的文件名是HelloWDM.c的話,這句可以省略。
extern?"C"
NTSTATUS?DriverEntry(????IN?PDRIVER_OBJECT?DriverObject,
????????????????????????IN?PUNICODE_STRING?RegistryPath)
{
????//指定“添加設備”消息由函數(shù)“HelloWDMAddDevice()”來處理:
????DriverObject->DriverExtension->AddDevice?=?HelloWDMAddDevice;
????//指定“即插即用”消息由函數(shù)“HelloWDMPnp()”來處理:
????DriverObject->MajorFunction[IRP_MJ_PNP]?=?HelloWDMPnp;?
????//返回一個NTSTATUS值STATUS_SUCCESS。幾乎所有的驅(qū)動程序例程都必須返回一個NTSTATUS值,這些值在NTSTATUS.H?DDK頭文件中有詳細的定義。
????return?STATUS_SUCCESS;
}?
/***************************************************************
函數(shù)名稱:HelloWDMAddDevice()
功能描述:處理“添加設備”消息
***************************************************************/
NTSTATUS?HelloWDMAddDevice(IN?PDRIVER_OBJECT?DriverObject,
???????????????????????????IN?PDEVICE_OBJECT?PhysicalDeviceObject)
{
????//定義一個NTSTATUS類型的返回值:
????NTSTATUS?status;
????//定義一個功能設備對象(Functional?Device?Object):
????PDEVICE_OBJECT?fdo;?
????//創(chuàng)建我們的功能設備對象,并儲存到fdo中:
????status?=?IoCreateDevice(
????????DriverObject,????????????????//驅(qū)動程序?qū)ο?/span>
????????sizeof(DEVICE_EXTENSION),????//要求的設備擴展的大小
????????NULL,????????????????????????//設備名稱,這里為NULL
????????FILE_DEVICE_UNKNOWN,????????//設備的類型,在標準頭文件WDM.H或NTDDK.H中列出的FILE_DEVICE_xxx值之一
????????0,????????????????????????????//各種常量用OR組合在一起,指示可刪除介質(zhì)、只讀等。
????????FALSE,????????????????????????//如果一次只有一個線程可以訪問該設備,為TRUE,否則為FALSE
????????&fdo);????????????????????????//返回的設備對象?
????//NT_SUCCESS宏用于測試IoCreateDevice內(nèi)核是否成功完成。不要忘記檢查對內(nèi)核的所有調(diào)用是否成功。NT_ERROR宏不等同于!NT_SUCCESS,最好使用!NT_SUCCESS,因為除了錯誤外,它還截獲警告信息。
????if(?!NT_SUCCESS(status))
????????return?status;?
????//創(chuàng)建一個設備擴展對象dx,用于存儲指向fdo的指針:
????PDEVICE_EXTENSION?dx?=?(PDEVICE_EXTENSION)fdo->DeviceExtension;
????dx->fdo?=?fdo;?
????//用IoAttachDeviceToDeviceStack函數(shù)把HelloWDM設備掛接到設備棧:
????dx->NextStackDevice?=?IoAttachDeviceToDeviceStack(fdo,?PhysicalDeviceObject);?
????//設置fdo的flags。有兩個“位”是必須改變的,一個是必須清除DO_DEVICE_INITIALIZING標志,如果在DriverEntry例程中調(diào)用IoCreateDevice(),就不需要清除這個標志位。還有一個是必須設置DO_BUFFER_IO標志位:
????fdo->Flags?|=?DO_BUFFERED_IO?|?DO_POWER_PAGABLE;
????fdo->Flags?&=?~DO_DEVICE_INITIALIZING;?
????//返回值:
????return?STATUS_SUCCESS;
}?
/***************************************************************
函數(shù)名稱:HelloWDMPnp()
功能描述:處理“即插即用”消息
***************************************************************/
NTSTATUS?HelloWDMPnp(IN?PDEVICE_OBJECT?fdo,
????????????????????????IN?PIRP?Irp)
{
????//創(chuàng)建一個設備擴展對象dx,用于存儲指向fdo的指針:
????PDEVICE_EXTENSION?dx=(PDEVICE_EXTENSION)fdo->DeviceExtension;?
????//首先要通過函數(shù)IoGetCurrentIrpStackLocation()得到當前的IRP,并由此得到Minor?Function:
????PIO_STACK_LOCATION?IrpStack?=?IoGetCurrentIrpStackLocation(Irp);
????ULONG?MinorFunction?=?IrpStack->MinorFunction;?
????//然后把這個Minor?Function傳遞給下一個設備棧:
????IoSkipCurrentIrpStackLocation(Irp);
????NTSTATUS?status?=?IoCallDriver(?dx->NextStackDevice,?Irp);?
????//處理“即插即用”次功能代碼:
????//當Minor?Function等于IRP_MN_REMOVE_DEVICE時,說明有設備被拔出或卸下,這時要取消資源分配并刪除設備:
????if(?MinorFunction==IRP_MN_REMOVE_DEVICE)
????{
????????//取消設備接口:
????????IoSetDeviceInterfaceState(&dx->ifSymLinkName,?FALSE);
????????RtlFreeUnicodeString(&dx->ifSymLinkName);
????????//調(diào)用IoDetachDevice()把fdo從設備棧中脫開:
????????if?(dx->NextStackDevice)
????????????IoDetachDevice(dx->NextStackDevice);
????????//刪除fdo:
????????IoDeleteDevice(fdo);
????}?
????//返回值:
????return?status;
}?
?
WDM驅(qū)動程序入門(2)——驅(qū)動程序的小秘密
WDM程序編譯出來的并不是我們常見的.exe,而是.sys文件,在未經(jīng)設置編譯環(huán)境之前,是不能直接用VC來編譯的(這就是為什么會有幾百個錯誤了)。這種類型的文件你可以在WINNT\System32\Drivers里面找到很多。其實驅(qū)動程序也是一種PE文件,它同樣由DOS MZ header開頭,也有完整的DOS stub和PE header,同樣擁有Import table和Export table——hoho……那跟普通的PE文件有什么不一樣呢?
其實.sys跟.exe文件一樣,都是一種PE文件來的。不同的是,.sys文件Import的通常是NTOSKRNL.EXE,而.exe文件Import的通常是KERNEL32.DLL和USER32.DLL。
知道了這些有什么用呢?實際上,由于.sys通常不調(diào)用KERNEL32.DLL和USER32.DLL,所以你是不能在設備驅(qū)動程序里面調(diào)用任何C、C++和Win32函數(shù)的,而且也不能用C++關(guān)鍵字new和delete等(可以用malloc和free來代替),而必須使用大量的內(nèi)核函數(shù)。
為了讀者的方便,下面我列出一些常見的驅(qū)動程序可用的內(nèi)核函數(shù):
Ex…??????? 執(zhí)行支持
Hal…??????? 硬件抽象層(僅NT/Windows 2000)
Io…??????? I/O管理器(包括即插即用函數(shù))
Ke…??????? 內(nèi)核
Ks…??????? 內(nèi)核流IRP管理函數(shù)
Mm…??????? 內(nèi)存管理器
Ob…??????? 對象管理器
Po…??????? 電源管理
Ps…??????? 進程結(jié)構(gòu)
Rtl…??????? 運行時庫
Se…??????? 安全引用監(jiān)視
Zw…??????? 其他函數(shù)
寫設備驅(qū)動程序時必須注意的一些問題:
1、內(nèi)核宏
如果查看DDK頭文件,會發(fā)現(xiàn)有幾個內(nèi)核函數(shù)是以宏的方式實現(xiàn)的。這種宏中有幾個宏的定義是相當糟糕的。例如,我們看到RemoveHeadList的定義如下:#define RemoveHeadList(ListHead)
??????? (ListHead)->Flink;
??????? {RemoveEntryList((ListHead)->Flink)}
如果以以下方式調(diào)用RemoveHeadList,則將編譯錯誤的代碼:if(SomethingInList)
??????? Entry = RemoveHeadList(list);
使這個調(diào)用安全的唯一方法是使用花括號:if(SomethingInList)
??? {
??????? Entry = RemoveHeadList(list);
??? }
?
所以我們切勿為了貪圖一時的方便,而使用不太規(guī)范的寫法,最好是在所有的if、for和while等語句中使用花括號。
2、驅(qū)動程序函數(shù)名稱
跟C/C++的main()函數(shù)一樣,設備驅(qū)動程序也有一個必須存在,而且只能以DriverEntry()為名稱的入口函數(shù)。然而,除此之外,我們可以使用任何名字來給其他函數(shù)命名——只要你自己記得就行了,當然,最好符合某些特定的規(guī)范啦,例如匈牙利命名法……
3、安裝時的問題
·在Windows98中驅(qū)動程序可執(zhí)行文件必須是8.3文件名。(別問我為什么,我也不知道,我只能建議你去問比爾該死)
·如果INF文件中含有非法節(jié)的詳細資料,Windows將不使用這個INF文件。
?WDM驅(qū)動程序入門(3)——安裝步驟
DDK分為98 DDK和2000 DDK兩種,它們工作起來是大同小異的,不過有些驅(qū)動程序只能在2000 DDK中使用。由于Win98注定是一種即將被淘汰的操作系統(tǒng)了,所以我也不打算介紹如何在98 DDK中進行編譯,以下的所有內(nèi)容都是針對2000 DDK的。
·準備工作
1、確定你已經(jīng)安裝了Visual C++
2、安裝2000 DDK
3、安裝2000 DDK成功后,在“開始”->“程序”里應該有“Development Kits”->“Windows 2000 DDK”的項目。
(注意一定要先安裝好VC,然后才安裝DDK,這個順序決不能顛倒!!)
4、保證DDKROOT環(huán)境變量設置為Windows 2000 DDK的基目錄,如果不是的話,請在控制面板“系統(tǒng)”屬性的“高級”標簽環(huán)境變量編輯器中設置好這個環(huán)境變量。
·編寫必需的文件
編譯WDM程序的時候,有兩個文件是必須要有的,它們是:
1、makefile
(這個是什么啊?你可能會問。)對于比較年輕的程序員來說,有可能沒有見過這個文件吧。其實在VC這些IDE出現(xiàn)之前,我們都必須使用makefile來確定項目中哪些文件需要重新編譯,現(xiàn)在的IDE都把這個工作自動做好了。(Well……其實這樣也好。)
我們要做的工作很簡單,就是提供這樣一個文件,它的內(nèi)容是:
#
# DO NOT EDIT THIS FILE!!!? Edit .\sources. If you want to add a new source
# file to this component.? This file merely indirects to the real make file
# that is shared by all the driver components of the Windows NT DDK
#
!INCLUDE $(NTMAKEENV)\makefile.def
?
正如它所述,不要編輯這個文件。事實上每個WDM程序所需要的makefile的內(nèi)容都是一樣的,也就是說,我們只需要簡單地copy一個makefile到新的項目中就可以了。(呵呵,是不是很方便呢?)
2、Sources
TARGETNAME=HelloWDM
TARGETTYPE=DRIVER
DRIVERTYPE=WDM
TARGETPATH=OBJ
INCLUDES=$(BASEDIR)\inc;\
???????? $(BASEDIR)\inc\ddk;\
TARGETLIBS=$(BASEDIR)\lib\*\free\usbd.lib\
SOURCES=HelloWDM.cpp\
?
這個文件指定了驅(qū)動程序目標名是HelloWDM.sys,是一個WDM驅(qū)動程序,生成的文件存放在OBJ目錄中。值得注意的是,“=”前后不能有空格,否則編譯的時候會出錯。
·開始編譯
娃哈哈,前面羅羅嗦嗦講了一大堆,現(xiàn)在終于到重點了。WDM程序的編譯過程比較特殊,它不是在VC里面按F7來編譯的(盡管你可以通過設置來達到這一目的),而是通過一個DDK實用工具build來完成。下面我們來講講具體步驟:
1、“Debug”版的生成
首先,我們假設你的源代碼放在D:\HelloWDM里面。請跟著以下步驟:
“開始”->“程序”->“Development Kits”->“Windows 2000 DDK”->“Checked Build Environment”
屏幕將顯示:(有“回車”的那行是需要讀者你親自打進去的)
New or updated MSVC detected.? Updating DDK environment….
Setting environment for using Microsoft Visual C++ tools.
Starting dirs creation…Completed.
D:\NTDDK>cd\HelloWDM??? (回車)
D:\HelloWDM>build??? (回車)
?
如果源代碼沒有錯誤的話,生成的HelloWDM.sys將存放在objchk\i386目錄中。
2、“Release”版的生成
請跟著以下步驟:
“開始”->“程序”->“Development Kits”->“Windows 2000 DDK”->“Free Build Environment”
隨后的步驟跟“Debug”版相同,不同的是生成的HelloWDM.sys將存放在objfre\i386目錄中。
·安裝
如果前面的編譯過程沒有錯誤的話,現(xiàn)在我們應該已經(jīng)得到了一個HelloWDM.sys文件,假設它是放在D:\HelloWDM\objfre\i386中。
我們還要干什么呢?…………對啦,就是安裝它!不然辛辛苦苦編譯出來有什么用?
安裝WDM驅(qū)動程序可以用兩種方法,一種是利用注冊表,還有一種是利用INF文件。我們一般是采用INF文件(這是微軟推薦的)。INF文件可以在 WINNT\INF 目錄中找到很多。為了順利安裝,我在這里先給出 HelloWDM 所需要的 HelloWDM.INF 文件:
?
Code;;?The?Win2K?DDK?documentation?contains?an?excellent?INF?reference.
;---------?Version?Section?---------------------------------------------------
[Version]
Signature="$CHICAGO$"
Provider=LC_Device
DriverVer=8/21/2002,3.0.0.3
;?If?device?fits?one?of?the?standard?classes,?use?the?name?and?GUID?here,
;?otherwise?create?your?own?device?class?and?GUID?as?this?example?shows.
Class=Unknown
ClassGUID={ff646f80-8def-11d2-9449-00105a075f6b}
;---------?SourceDiskNames?and?SourceDiskFiles?Section?-----------------------
;?These?sections?identify?source?disks?and?files?for?installation.?They?are
;?shown?here?as?an?example,?but?commented?out.
[SourceDisksNames]
1?=?"HelloWDM",Disk1,,
[SourceDisksFiles]
HelloWDM.sys?=?1,objfre\i386,
;---------?ClassInstall/ClassInstall32?Section?-------------------------------
;?Not?necessary?if?using?a?standard?class
;?9X?Style
[ClassInstall]
Addreg=Class_AddReg
;?NT?Style
[ClassInstall32]
Addreg=Class_AddReg
[Class_AddReg]
HKR,,,,%DeviceClassName%
HKR,,Icon,,"-5"
;---------?DestinationDirs?Section?-------------------------------------------
[DestinationDirs]
YouMark_Files_Driver?=?10,System32\Drivers
;---------?Manufacturer?and?Models?Sections?----------------------------------
[Manufacturer]
%MfgName%=Mfg0
[Mfg0]
;?PCI?hardware?Ids?use?the?form
;?PCI\VEN_aaaa&DEV_bbbb&SUBSYS_cccccccc&REV_dd
;改成你自己的ID
%DeviceDesc%=YouMark_DDI,?PCI\VEN_9999&DEV_9999
;----------?DDInstall?Sections?-----------------------------------------------
;?---------?Windows?9X?-----------------
;?Experimentation?has?shown?that?DDInstall?root?names?greater?than?19?characters
;?cause?problems?in?Windows?98
[YouMark_DDI]
CopyFiles=YouMark_Files_Driver
AddReg=YouMark_9X_AddReg
[YouMark_9X_AddReg]
HKR,,DevLoader,,*ntkern
HKR,,NTMPDriver,,HelloWDM.sys
HKR,?"Parameters",?"BreakOnEntry",?0x00010001,?0
;?---------?Windows?NT?-----------------
[YouMark_DDI.NT]
CopyFiles=YouMark_Files_Driver
AddReg=YouMark_NT_AddReg
[YouMark_DDI.NT.Services]
Addservice?=?HelloWDM,?0x00000002,?YouMark_AddService
[YouMark_AddService]
DisplayName?=?%SvcDesc%
ServiceType?=?1?;?SERVICE_KERNEL_DRIVER
StartType?=?3?;?SERVICE_DEMAND_START
ErrorControl?=?1?;?SERVICE_ERROR_NORMAL
ServiceBinary?=?%10%\System32\Drivers\HelloWDM.sys
[YouMark_NT_AddReg]
HKLM,?"System\CurrentControlSet\Services\HelloWDM\Parameters",\
"BreakOnEntry",?0x00010001,?0
;?---------?Files?(common)?-------------
[YouMark_Files_Driver]
HelloWDM.sys
;---------?Strings?Section?---------------------------------------------------
[Strings]
ProviderName="Flying?L?Co.,Ltd."
MfgName="LC?Soft"
DeviceDesc="Hello?World?WDM!"
DeviceClassName="LC_Device"
SvcDesc="???"
注意它可以同時在Win98或者Win2000中使用(系統(tǒng)會通過這個INF文件里面的字段名稱,自動選擇適合當前系統(tǒng)的安裝方法的)。由于INF文件的各個字段含義比較復雜,限于篇幅,我在這里就不詳細講解了,請讀者自行參閱有關(guān)的文章或者書籍。
準備好這個 HelloWDM.INF 文件后,讓我們打開控制面板,雙擊“添加/刪除硬件”,選擇“添加/排除設備故障”->“添加新設備”->“否,我想從列表選擇硬件”->“其它設備”->“從磁盤安裝”,選擇 HelloWDM.INF 所在的路徑,然后安裝。
當安裝完成后,系統(tǒng)就會添加上你寫好的驅(qū)動程序了。(可以在“設備管理器”中查看到)。然后重啟電腦,這個驅(qū)動程序就投入使用啦。
轉(zhuǎn)載于:https://www.cnblogs.com/dubingsky/archive/2009/06/29/1513100.html
總結(jié)