大概是一個(gè)多星期以前吧,也說不上是出于什么特殊目的,我開始學(xué)上驅(qū)動(dòng)來了,,這是我第一次寫點(diǎn)心得之類的文章,,我起步的時(shí)候也沒少走彎路,現(xiàn)在,,至少我可以寫一個(gè)基本的驅(qū)動(dòng)框架來了,,也挺不容易,所以我把我學(xué)習(xí)的路子寫出來,,和跟我一樣的新手們交流一下,,正要學(xué)寫驅(qū)動(dòng)的同志們可以借鑒一下。前一陣子,,不知道是怎么跑到兩個(gè)專門寫驅(qū)動(dòng)的群里面了,,在那里面,看到了傳說中的大牛,,也結(jié)識了大牛中的一些中牛,,其中也有幫了我不少的不知道是什么樣的牛。在這里我要特別感謝文件系統(tǒng)驅(qū)動(dòng)群里的“被剃毛的老鷹”,,“哆啦B夢”,,LookSail,qi等,正是因?yàn)樗麄?,我才有了今天的進(jìn)步,,盡管這只是一個(gè)小小的進(jìn)步而已。 一,、生產(chǎn)工具: 首先,,得準(zhǔn)備好微軟的驅(qū)動(dòng)開發(fā)包,我這里用的是WDK(Windows Driver Kit)6001,,因?yàn)樗锩娴木幾g環(huán)境我不會(huì)用,,所以,我選用了集成環(huán)境,,這里我使用的VS2008.這兩個(gè)工具在網(wǎng)上都有下,,至于下載和安裝,都是你自己的事情了,。
二,、開工了: 還記得我們當(dāng)初第一次寫C程序嗎?每一個(gè)C程序都有一個(gè)必須的東西,,即main主函數(shù),,它是程序執(zhí)行的起點(diǎn),后來我們熟悉了,我們會(huì)在編譯器編譯的時(shí)候指定另一個(gè)函數(shù)入口為主函數(shù)起點(diǎn),。這里所要說的驅(qū)動(dòng)程序也是有一個(gè)類似的入口,,一般我們都會(huì)把這個(gè)名字叫DriverEntry,和一般的C程序一樣,,這個(gè)函數(shù)入口也可以設(shè)置成為另外一個(gè)名字,,如果是在命令行的話,給個(gè)參數(shù) -entry: NewEntryName即可,,后面的部分,,我們可以在VS里面設(shè)置這個(gè)入口。 一般的Windows應(yīng)用程序在主入口函數(shù)完成相關(guān)的初始化工作以后,,就開始一個(gè)消息循環(huán),,進(jìn)行消息處理。驅(qū)動(dòng)程序也有點(diǎn)像像了,,驅(qū)動(dòng)程序在主入口函數(shù)中完成相關(guān)的初始化之后便處于睡眠狀態(tài),,開始等待外圍請求。驅(qū)動(dòng)程序的主要消息都被Windows打包成IRP包,,相關(guān)的知識如果你都還不明白的話,,我想你可以參考MSDN或者是百度一下,網(wǎng)絡(luò)應(yīng)該成為你的朋友,。 我們就以DriverEntry為例來說明吧,,這個(gè)函數(shù)原型如下: NTSTATUS DriverEntry(PDEVICE_OBJECT pDrvObj,PUNICODE_STRING pRegPath) 第一個(gè)參數(shù)是驅(qū)動(dòng)模塊對象,由操作系統(tǒng)內(nèi)核分配好傳來,,以我的理解吧,就和我們的Win32應(yīng)用程序的第一個(gè)參數(shù)HINSTANCE一樣,,第二個(gè)參數(shù)是操作系統(tǒng)傳來的驅(qū)動(dòng)在注冊表中路徑,。和以前的main函數(shù)一樣,DriverEntry函數(shù)也是做一些初始化的工作,,下面一起來看看一個(gè)基本的最簡單的驅(qū)動(dòng)程序框架,。 一個(gè)基本的驅(qū)動(dòng)程序框架里面,我們做的最主要的事情就是創(chuàng)建設(shè)備對象,,還有初始化一些必要的IRP例程入口,,如果必段的話,我們還必須創(chuàng)建必要的Dos連接符號名一樣,,就和C盤對應(yīng)于\Harddisk\partition1一樣,,行,不廢話了,,一起寫一個(gè)簡單的DriverEntry吧:
#include <wdm.h>
void Example1Unload(PDRIVER_OBJECT pDrvObj) { UNICODE_STRING usDosDevName;//Dos符號鏈接
DbgPrint("Example1: Driver is being unloaded.\n");
//首先我們要?jiǎng)h除的是一個(gè)Dos鏈接名,,否則,這個(gè)鏈接名便不再可用,,直到系統(tǒng)重啟 //不過,這個(gè)Dos鏈接名應(yīng)該和我們在DriverEntry里創(chuàng)建的一樣,千萬記得了 RtlInitUnicodeString(&usDosDevName,L"\\DosDevices\\Example1"); IoDeleteSymbolicLink(&usDosDevName); //接下來,,我們再刪除驅(qū)動(dòng)程序創(chuàng)建的設(shè)備對象,,在此之后,系統(tǒng)將會(huì)把我們的驅(qū)動(dòng)從內(nèi)核移除 //我們的驅(qū)動(dòng)便是被卸載了 IoDeleteDevice(pDrvObj->DeviceObject); //OK,所有的事情已經(jīng)解決 }
NTSTATUS Example1IrpRoutine(PDRIVER_OBJECT pDev,PIRP pIrp) { //在調(diào)試器中輸出一個(gè)字符串,,你還記得Win32下的TRACE系列宏嗎 DbgPrint("An driver routine is called.\n"); return STATUS_SUCCESS;//簡單地返回成功而已 } //驅(qū)動(dòng)入口函數(shù) NTSTATUS DriverEntry(PDRIVER_OBJECT pDrvObj,PUNICODE_STRING pUsRegPath) { NTSTATUS status = STATUS_UNSUCCESSFUL;//初始化為不成功 UNICODE_STRING usDevName;//我們的設(shè)備名 UNICODE_STRING usDosDevName;//Dos符號鏈接 PDEVICE_OBJECT pDevObj = NULL;//設(shè)備對象 unsigned int nIndex;//一個(gè)計(jì)數(shù)器,循環(huán)的時(shí)候可以用到
DbgPrint("Example1: Driver entry is called.\n"); //__try{ //初始化兩個(gè)Unicodestring RtlInitUnicodeString(&usDevName,L"\\Device\\Example1"); RtlInitUnicodeString(&usDosDevName,L"\\DosDevices\\Example1");
//現(xiàn)在我們創(chuàng)建設(shè)備 status = IoCreateDevice(pDrvObj,0,&usDevName,FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN,FALSE,&pDevObj); //只有成功創(chuàng)建設(shè)備我們才有必要繼續(xù) if(NT_SUCCESS(status)){//測試成功與否一般用這個(gè)宏,而不是讓它直接與STATUS_SUCCESS比 //較,查看一下這個(gè)宏定義就知道了 //成功創(chuàng)建設(shè)備之后,我們要作的一件事就是初使化驅(qū)動(dòng)的IRP例程,,現(xiàn)在,我們的驅(qū)動(dòng)什么 都不干,,所以,,每個(gè)例程也還是什么都不做 //每個(gè)設(shè)備最多有IRP_MJ_MAXIMUM_FUNCTION個(gè)IRP例程,現(xiàn)在,,我們都把它們初始 //化成一個(gè)相同的入口 for(nIndex=0;nIndex<IRP_MJ_MAXIMUM_FUNCTION;++nIndex) pDrvObj->MajorFunction[nIndex] = Example1IrpRoutine; //接下來,,我再安裝一個(gè)卸載例程,從而,,使我們的驅(qū)動(dòng)可以動(dòng)態(tài)的卸載,,這就是傳說中的 //熱插撥 pDrvObj->DriverUnload = Example1Unload;
//把創(chuàng)建的設(shè)備保存起來吧,否則以后便不能引用啦 pDrvObj->DeviceObject = pDevObj;
//好了,創(chuàng)建符號鏈接,正是由于有了符號鏈接,我們可以用C:來訪問第一個(gè)硬盤分區(qū)…… status = IoCreateSymbolicLink(&usDosDevName,&usDevName); if(!NT_SUCCESS(status)){ //在這里定義了如果創(chuàng)建Dos鏈接失敗將做的事情,如果我們不需要一個(gè)Dos符號鏈接, //這一步便不是必須的,更不必檢驗(yàn)了 IoDeleteDevice(pDevObj);//刪除創(chuàng)建的設(shè)備對象 //這個(gè)時(shí)候,status肯定就代表失敗了,如果入口函數(shù)返回一個(gè)失敗的狀態(tài),系統(tǒng)會(huì)自動(dòng)把 //創(chuàng)建的這個(gè)Driver刪除的,我們不用擔(dān)心 } }
//現(xiàn)在,我們測試一下成功與否 //}__except(EXCEPTION_EXECUTE_HANDLER){ //如果出現(xiàn)一般的異常,執(zhí)行流程會(huì)走到這兒來,除了一些SEH都解決不了的異常除外,這個(gè)我們以 //后再討論 //}
return status;// } 至此,一個(gè)簡單的設(shè)備驅(qū)動(dòng)程序框架已經(jīng)完成了,,難道不是嗎,?在這個(gè)驅(qū)動(dòng)里面,我們創(chuàng)建的設(shè)備\Device\Example1,這里,Device是設(shè)備對象的所屬名字空間,,Example1才是它的名字,。事實(shí)上,它只是一個(gè)需擬設(shè)備驅(qū)動(dòng)程序,,并沒有一個(gè)實(shí)際的設(shè)備與之相對應(yīng),。 但是,這里只是介紹了這么一個(gè)框架程序代碼,,還得編譯鏈接呢,!
三、編譯鏈接: 我說過,,我水平有限,,不會(huì)使用WDK(或是DDK)集成的編譯鏈接環(huán)境,所以我選擇使用WDK+VS2008. 1.建立工程,。VS里面沒有為現(xiàn)成的驅(qū)動(dòng)工程向?qū)?,所以,我一般都建立一個(gè)Win32工程(我常會(huì)用Console),,只是,,建立一個(gè)空項(xiàng)目即可,你可別指望向?qū)傻拇a文件在你的驅(qū)動(dòng)里面有用,! 2.添加代碼文件,。添加新項(xiàng),選擇源程序代碼文件,不過,,這里一般用C文件,,至于原因,我不想多解釋,,現(xiàn)在只是一個(gè)簡單的代碼,,所以,我都在一個(gè)文件里面寫完,。就是把我剛才在上面的代碼復(fù)制到你剛新建的源文件里就行了,。 3.設(shè)置編譯選項(xiàng)。還記得安裝了WDK吧,,既然如果,,就要用到它了,再說了,,以前還沒用到過#include <wdm.h>之類的語句吧,?好了,在附加包含路徑設(shè)置成WDK中的inc路徑,,一般如下,, WDKROOT\inc\api WDKROOT\inc\crt WDKROOT\inc\ddk 另外,同于驅(qū)動(dòng)是接近硬件的程序,,它可以執(zhí)行絕大多數(shù)CPU指令,,從而,我們還得為我們的驅(qū)動(dòng)程序指定目標(biāo)CPU平臺(tái),,這里,,我選X86.需要在預(yù)定義里定義添加一個(gè)_X86_的定義,否則會(huì)收到一個(gè)需要指定目標(biāo)平臺(tái)(target architecture)的編譯錯(cuò)誤,。 4.鏈接先項(xiàng)?,F(xiàn)在的設(shè)置的話,生成的文件還是exe后綴呢,,但驅(qū)動(dòng)都是sys后綴,所以,,你得改,,在什么地方改,我不說了,。另外,,得添加一個(gè)輸入庫,就是附加依賴項(xiàng):ntoskrnl.lib這個(gè)時(shí)候,,又得設(shè)置附加庫目錄了,,設(shè)置成 WDKROOT\lib\wxp\i386 別忘了,我們現(xiàn)在的工程還是個(gè)console項(xiàng)目呢,得改,,找到鏈接選項(xiàng)卡,,System(子系統(tǒng))一項(xiàng)選擇Native,還有驅(qū)動(dòng)選/Driver. 5.OK,,現(xiàn)在試著生成一下吧,。如果沒有語法錯(cuò)誤的話,它還是會(huì)有一大堆錯(cuò)誤的,,好,,我當(dāng)初寫的時(shí)候也是這樣,那,,我現(xiàn)在把所有的設(shè)置一股腦告訴你,,你就別走彎路了,遇到其它別的問題再說,,主要精力別放在這兒就行了,。 ①C/C++->Preprocessor: Ignor Standard include path YES ②C/C++->Co ③C/C++->Advanced: Calling convertion __stdcall(/Gz) ③Linker->Input: Ignor All default libraries YES ④linker->Manifest file: Generate manifest No ⑤Linker->Advaced: Entry point DriverEntry; Base address 0x100000; Ramdomized base address Default; Da
四.啟動(dòng)驅(qū)動(dòng)程序。 把生成的example1.sys復(fù)制到一個(gè)地方,,我這里是C:\然后,,使用以下程序加載程序加載驅(qū)動(dòng): int _cdecl main(void) { HANDLE hSCManager; HANDLE hService; SERVICE_STATUS ss;
hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
printf("Load Driver\n");
if(hSCManager) { printf("Create Service\n");
hService = CreateService(hSCManager, "Example1", "Example1 Driver", SERVICE_START | DELETE | SERVICE_STOP, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, "C:\\example1.sys", NULL, NULL, NULL, NULL, NULL);
if(!hService) { hService = OpenService(hSCManager, "Example1", SERVICE_START | DELETE | SERVICE_STOP); }
if(hService) { printf("Start Service\n");
StartService(hService, 0, NULL); printf("Press Enter to close service\r\n"); getchar(); ControlService(hService, SERVICE_CONTROL_STOP, &ss);
DeleteService(hService);
CloseServiceHandle(hService);
}
CloseServiceHandle(hSCManager); }
return 0; } 這段代碼是我直接從其它地方復(fù)制過來的,經(jīng)過我的試驗(yàn),,并不總是好使的,,但是,只要執(zhí)行一次就好辦了,,以后就不再需要它啦,。因?yàn)椋趫?zhí)行一次CreateService之后,,它就會(huì)在注冊表的HKLM\CurrentControlSet\System\Services\下創(chuàng)建一個(gè)子鍵Example1,在以后我們的驅(qū)動(dòng)的例子,,就不再使用上面的加載程序,而是直接修改這個(gè)注冊表鍵了(當(dāng)然,,并不是最終目標(biāo),,在此,只是圖個(gè)方便,,以后,,有一天,會(huì)使用標(biāo)準(zhǔn)安裝文件inf),。Example1子鍵下面有個(gè)ImagePath子項(xiàng)指定了這個(gè)服務(wù)的目標(biāo)路徑,。第一次,如果上面的程序執(zhí)行成功,,再按以下回車就卸載了,。以后,,將使用Dos命令來啟動(dòng)和停止它,啟動(dòng): net start example1,,停止:net stop example.是不是很簡單了,? 五、測試: 現(xiàn)在好了,,既然我們的驅(qū)動(dòng)能加載了,,我們就試一下,寫一個(gè)一般的Win32程序,,使用如下語句: CreateFile("\\\\.\\Example1",GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,NULL,OPEN_EXISTING,0,NULL)試一下,。是不是能成功返回一個(gè)句柄了? 六,、總結(jié): 本文主要記錄了我怎么使用VS2008+WDK創(chuàng)建我的第一個(gè)驅(qū)動(dòng)程序的過程,,這個(gè)程序我保證絕對是我的第一個(gè)程序,上個(gè)周末寫的,。哈哈,。雖然我的介紹沒有楚狂人或是wowocock的那么獨(dú)到,那么吸引人,,它只是紹了構(gòu)建一個(gè)驅(qū)動(dòng)框架的基本方法,。相關(guān)的系統(tǒng)知識得參考MSDN。在下一篇中我將介紹幾種IO例程和相關(guān)的內(nèi)存使用方法,,實(shí)現(xiàn)驅(qū)動(dòng)的不同版本的讀寫例程,。有興趣的,等著吧,,嘿嘿,。 好不容易,記下了我學(xué)習(xí)的過程,,有興趣一起學(xué)習(xí)交流的或是有問題的歡迎與我聯(lián)系,,有錯(cuò)請指正,謝謝,,QQ:178041876
特別聲明:轉(zhuǎn)載請注明出處 |
|