- 竹林蹊徑:深入淺出windows驅(qū)動(dòng)開(kāi)發(fā)
- 張佩 馬勇 董鑒源
- 1543字
- 2019-01-04 12:53:45
第1章 Hello World驅(qū)動(dòng)
1.1 從Hello World開(kāi)始
國(guó)外的計(jì)算機(jī)領(lǐng)域有一個(gè)著名的組織,名字叫“計(jì)算機(jī)器協(xié)會(huì)”,它在互聯(lián)網(wǎng)上專(zhuān)門(mén)維護(hù)了一個(gè)網(wǎng)頁(yè),上面列出了200多種版本的“Hello World”程序,仿佛代碼的羅塞塔石碑。
在使用一門(mén)新的編程語(yǔ)言時(shí),人們習(xí)慣把“Hello World”作為第一個(gè)程序。漸漸地,人們也習(xí)慣用類(lèi)似“Hello World”的程序作為一切程序的第一步,以此喚出程序員心中對(duì)于編程樂(lè)觀的一面。
如本節(jié)的題目一樣,如何編寫(xiě)驅(qū)動(dòng)程序是本節(jié)將要解決的問(wèn)題。
那么,讓我們看一下驅(qū)動(dòng)程序的“Hello World”吧!
代碼示例1-1 Hello World驅(qū)動(dòng)程序
#001 /* #002 ******************************************************* #003 *= = 文件名稱(chēng):Hello World.c #004 *= = 文件描述:驅(qū)動(dòng)程序的Hello World例子 #005 *= = 作 者:竹林蹊徑 #006 *= = 編寫(xiě)時(shí)間:2009-04-23 21:16:00 #007 ******************************************************* #008 */ #009 #010 #include <NTDDK.h> #011 #012 //*==================================================== #013 //*= = 函數(shù)名稱(chēng):DriverEntry #014 //*= = 功能描述:驅(qū)動(dòng)程序入口函數(shù) #015 //*= = 入口參數(shù):PDRIVER_OBJECT, PUNICODE_STRING #016 //*= = 出口參數(shù):NTSTATUS #017 //*==================================================== #018 #019 NTSTATUS #020 DriverEntry ( #021 __in PDRIVER_OBJECT DriverObject, #022 __in PUNICODE_STRING RegistryPath #023 ) #024 { #025 DbgPrint("Hello, Windows Driver!"); #026 return STATUS_SUCCESS; #027 } #028 #029 //*==================================================== #030 //*= = 文件結(jié)束 #031 //*====================================================
本示例代碼可以從本書(shū)的\Chapter01\Hello World目錄下找到。
以上就是驅(qū)動(dòng)程序最簡(jiǎn)單的“Hello World”例子,去掉代碼注釋部分,放眼程序,全部代碼屈指可數(shù)。
由第003行注釋可知,這個(gè)例子的文件名稱(chēng)是Hello World.c,說(shuō)明此文件是由C語(yǔ)言編寫(xiě)而成的。
我們知道,操作系統(tǒng)剛出現(xiàn)時(shí)是由機(jī)器語(yǔ)言和匯編語(yǔ)言編寫(xiě)的,后來(lái)為了可移植性等采用了C語(yǔ)言。早期的一些Windows操作系統(tǒng)也是采用C語(yǔ)言編寫(xiě)的,而在開(kāi)發(fā)基于NT技術(shù)的Windows操作系統(tǒng)時(shí),同時(shí)采用了C和C++這兩種高級(jí)語(yǔ)言。
那么究竟是采用C語(yǔ)言還是C++語(yǔ)言來(lái)開(kāi)發(fā)驅(qū)動(dòng)程序呢?其實(shí)兩者各有優(yōu)缺點(diǎn),讀者可以在讀本書(shū)的時(shí)候仔細(xì)體會(huì)。這里采用C語(yǔ)言編寫(xiě)了第一個(gè)例子,因?yàn)橛肅語(yǔ)言完全可以開(kāi)發(fā)出所有的驅(qū)動(dòng)程序,同時(shí)你會(huì)有似曾相識(shí)的感覺(jué)。但是微軟提供了WDF驅(qū)動(dòng)開(kāi)發(fā)模型,這個(gè)在后面的章節(jié)中會(huì)講到,如果需要使用WDF,C++無(wú)疑是最好的選擇。
實(shí)際上,驅(qū)動(dòng)程序編譯成的二進(jìn)制文件是SYS類(lèi)型文件,和普通的EXE類(lèi)型文件一樣,也是PE格式。PE是Portable Executable File Format的簡(jiǎn)寫(xiě),是微軟Windows平臺(tái)環(huán)境下主流的可執(zhí)行程序標(biāo)準(zhǔn)格式,DLL也是常見(jiàn)的PE格式。所以,使用什么編程語(yǔ)言并不嚴(yán)格限定,如果你喜歡,即使用匯編或Delphi也可以開(kāi)發(fā)驅(qū)動(dòng)程序。比如在VxD驅(qū)動(dòng)編程模型盛行時(shí),很多人還是使用匯編開(kāi)發(fā)設(shè)備驅(qū)動(dòng)程序,并提供了相應(yīng)的開(kāi)發(fā)環(huán)境;更有甚者提供了EXE類(lèi)型程序到SYS類(lèi)型程序的轉(zhuǎn)換工具,這些工具雖然大多沒(méi)有流行起來(lái),但是卻佐證了這種方法的可行性。
不過(guò),微軟提供的內(nèi)核編程接口和示例只有C/C++的,為了方便起見(jiàn),我們約定本書(shū)使用C/C++語(yǔ)言來(lái)開(kāi)發(fā)驅(qū)動(dòng)程序。
開(kāi)發(fā)驅(qū)動(dòng)程序大致和開(kāi)發(fā)普通應(yīng)用程序一樣,幾乎擁有同樣的流程:分析需求、設(shè)計(jì)、編碼、調(diào)試、測(cè)試、發(fā)布、維護(hù)這幾個(gè)主要環(huán)節(jié)。但是在后幾個(gè)環(huán)節(jié)上,驅(qū)動(dòng)程序的開(kāi)發(fā)和普通應(yīng)用程序的開(kāi)發(fā)又有著很大的差別。
下面,我們來(lái)簡(jiǎn)單分析一下代碼。
第010行包含了一個(gè)頭文件NTDDK.h,這個(gè)頭文件是NT驅(qū)動(dòng)必須包含的一個(gè)頭文件,WDM驅(qū)動(dòng)則需要換成WDM.h。
第019~027行,整段代碼只有一個(gè)DriverEntry函數(shù)。這個(gè)函數(shù)是所有驅(qū)動(dòng)程序的入口函數(shù),類(lèi)似于Win32編程下的WinMain函數(shù)或C語(yǔ)言的main函數(shù)。
第021~022行,函數(shù)的兩個(gè)參數(shù),分別代表驅(qū)動(dòng)對(duì)象的指針和注冊(cè)表子鍵的字符串指針。這個(gè)暫且不作詳細(xì)論述,我們將在下面章節(jié)中具體說(shuō)明。其中,__in是一個(gè)宏,代表這個(gè)參數(shù)是入口參數(shù),常見(jiàn)的還有__out,代表出口參數(shù)。
第025~026行,再看函數(shù)里面,我們看到只有兩個(gè)語(yǔ)句,是不是很熟悉呢?
DbgPrint是一個(gè)函數(shù),類(lèi)似于C語(yǔ)言的printf函數(shù),打印一串字符串,打印的內(nèi)容為“Hello, Windows Driver!”。隨后函數(shù)返回一個(gè)值STATUS_SUCCESS,這個(gè)值是個(gè)宏,從字面意思可知它代表成功,語(yǔ)句類(lèi)似于main函數(shù)的“return 0;”。這里需要注意的是,打印的內(nèi)容無(wú)法通過(guò)控制臺(tái)查看,需要借助其他工具才能看到。
那么如何對(duì)這個(gè)程序進(jìn)行編譯呢?
我們需要一個(gè)開(kāi)發(fā)環(huán)境,這個(gè)開(kāi)發(fā)環(huán)境名為WDK,微軟已經(jīng)為我們提供了。關(guān)于開(kāi)發(fā)環(huán)境的詳細(xì)介紹,請(qǐng)參閱本書(shū)第2章。
一切都那么自然!下面對(duì)這個(gè)例子進(jìn)行簡(jiǎn)單的擴(kuò)充。
1.1.1 HelloDRIVER
這里是一個(gè)關(guān)于Hello World驅(qū)動(dòng)程序示例代碼的簡(jiǎn)單擴(kuò)充。如果完全看不懂,那就直接跳到1.1.2節(jié)看代碼解釋吧!
代碼示例1-2驅(qū)動(dòng)程序HelloDRIVER聲明文件
#001 /* #002 ***************************************************************** #003 *= = 文件名稱(chēng):HelloDRIVER.h #004 *= = 文件描述:關(guān)于HelloDRIVER的頭文件 #005 *= = 作 者:竹林蹊徑 #006 *= = 編寫(xiě)時(shí)間:2009-04-23 21:16:00 #007 ***************************************************************** #008 */ #009 #010 #ifndef __HELLODRIVER_H__ #011 #define __HELLODRIVER_H__ #012 #013 //*============================================================== #014 //*= = 頭文件聲明 #015 //*============================================================== #016 #017 #include <NTDDK.h> #018 #019 //*============================================================== #020 //*= = 宏與結(jié)構(gòu)體 #021 //*============================================================== #022 #023 typedef struct _DEVICE_EXTENSION { #024 #025 PDEVICE_OBJECT DeviceObject; // 指回設(shè)備對(duì)象的指針 #026 UNICODE_STRING DeviceName; // 設(shè)備名稱(chēng) #027 UNICODE_STRING SymbolicLink; // 符號(hào)鏈接名 #028 #029 }DEVICE_EXTENSION, *PDEVICE_EXTENSION; #030 #031 //*============================================================== #032 //*= = 函數(shù)聲明 #033 //*============================================================== #034 #035 NTSTATUS #036 DriverEntry( #037 __in PDRIVER_OBJECT DriverObject, #038 __in PUNICODE_STRING RegistryPath #039 ); #040 #041 VOID #042 DriverUnload( #043 __in PDRIVER_OBJECT DriverObject #044 ); #045 #046 NTSTATUS #047 DefaultDispatch( #048 __in PDEVICE_OBJECT DeviceObject, #049 __in PIRP Irp #050 ); #051 #052 #endif // End of __HELLODRIVER_H__ #053 #054 //*============================================================== #055 //*= = 文件結(jié)束 #056 //*==============================================================
代碼示例1-3驅(qū)動(dòng)程序HelloDRIVER定義文件
#001 /* #002 ***************************************************************** #003 *= = 文件名稱(chēng):HelloDRIVER.c #004 *= = 文件描述:驅(qū)動(dòng)程序HelloDRIVER例子 #005 *= = 作 者:竹林蹊徑 #006 *= = 編寫(xiě)時(shí)間:2009-04-23 21:16:00 #007 ***************************************************************** #008 */ #009 #010 #include "HelloDRIVER.h" #011 #012 //*============================================================== #013 //*= = 預(yù)處理定義 #014 //*============================================================== #015 #016 #pragma alloc_text(INIT, DriverEntry) #017 #pragma alloc_text(PAGE, DefaultDispatch) #018 #pragma alloc_text(PAGE, DriverUnload) #019 #020 //*============================================================== #021 //*= = 函數(shù)名稱(chēng):DriverEntry #022 //*= = 功能描述:驅(qū)動(dòng)程序入口函數(shù) #023 //*= = 入口參數(shù):PDRIVER_OBJECT, PUNICODE_STRING #024 //*= = 出口參數(shù):NTSTATUS #025 //*============================================================== #026 #027 NTSTATUS #028 DriverEntry ( #029 __in PDRIVER_OBJECT DriverObject, #030 __in PUNICODE_STRING RegistryPath #031 ) #032 { #033 NTSTATUS status; #034 PDEVICE_OBJECT deviceObject; #035 PDEVICE_EXTENSION deviceExtension; #036 UNICODE_STRING symbolicLink; #037 UNICODE_STRING deviceName; #038 ULONG i; #039 KdPrint(("Enter HelloDRIVER DriverEntry!\n")); #040 #041 UNREFERENCED_PARAMETER(RegistryPath); #042 #043 RtlInitUnicodeString(&deviceName, L"\\Device\\HelloDRIVER"); #044 #045 // 處理派遣例程 #046 for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++) #047 { #048 DriverObject->MajorFunction[i] = DefaultDispatch; #049 } #050 #051 DriverObject->DriverUnload = DriverUnload; #052 DriverObject->MajorFunction[IRP_MJ_CREATE] = DefaultDispatch; #053 DriverObject->MajorFunction[IRP_MJ_CLOSE] = DefaultDispatch; #054 DriverObject->MajorFunction[IRP_MJ_READ] = DefaultDispatch; #055 DriverObject->MajorFunction[IRP_MJ_WRITE] = DefaultDispatch; #056 #057 // 創(chuàng)建設(shè)備 #058 status = IoCreateDevice( DriverObject, #059 sizeof(DEVICE_EXTENSION), #060 &deviceName, #061 FILE_DEVICE_UNKNOWN, #062 0, #063 TRUE, #064 &deviceObject); #065 if(!NT_SUCCESS(status)) #066 { #067 return status; #068 } #069 #070 deviceObject->Flags = DO_BUFFERED_IO; #071 deviceExtension = (PDEVICE_EXTENSION)deviceObject->DeviceExtension; #072 deviceExtension->DeviceObject = deviceObject; #073 deviceExtension->DeviceName = deviceName; #074 #075 RtlInitUnicodeString(&symbolicLink, L"\\??\\HelloDRIVER"); #076 deviceExtension->SymbolicLink = symbolicLink; #077 #078 // 創(chuàng)建符號(hào)鏈接 #079 status = IoCreateSymbolicLink(&symbolicLink, &deviceName); #080 #081 if(!NT_SUCCESS(status)) #082 { #083 IoDeleteDevice(deviceObject); #084 return status; #085 } #086 #087 KdPrint(("End of HelloDRIVER DriverEntry!\n")); #088 return status; #089 } #090 #091 //*============================================================== #092 //*= = 函數(shù)名稱(chēng):DriverUnload #093 //*= = 功能描述:驅(qū)動(dòng)程序卸載函數(shù) #094 //*= = 入口參數(shù):PDRIVER_OBJECT #095 //*= = 出口參數(shù):VOID #096 //*============================================================== #097 #098 VOID #099 DriverUnload( #100 __in PDRIVER_OBJECT DriverObject #101 ) #102 { #103 PDEVICE_OBJECT deviceObject; #104 UNICODE_STRING linkName; #105 KdPrint(("Enter HelloDRIVER DriverUnload!\n")); #106 #107 deviceObject = DriverObject->DeviceObject; #108 #109 while(NULL != deviceObject) #110 { #111 PDEVICE_EXTENSION deviceExtesion = \ #112 (PDEVICE_EXTENSION)deviceObject->DeviceExtension; #113 #114 // 刪除符號(hào)鏈接與設(shè)備 #115 linkName = deviceExtesion->SymbolicLink; #116 IoDeleteSymbolicLink(&linkName); #117 deviceObject = deviceObject->NextDevice; #118 IoDeleteDevice(deviceExtesion->DeviceObject); #119 } #120 #121 KdPrint(("End of HelloDRIVER DriverUnload!\n"));; #122 } #123 #124 //*============================================================== #125 //*= = 函數(shù)名稱(chēng):DefaultDispatch #126 //*= = 功能描述:驅(qū)動(dòng)程序默認(rèn)派遣例程 #127 //*= = 入口參數(shù):PDEVICE_OBJECT, PIRP #128 //*= = 出口參數(shù):NTSTATUS #129 //*============================================================== #130 #131 NTSTATUS #132 DefaultDispatch( #133 __in PDEVICE_OBJECT DeviceObject, #134 __in PIRP Irp #135 ) #136 { #137 NTSTATUS status; #138 KdPrint(("Enter HelloDRIVER DefaultDispatch!\n")); #139 #140 UNREFERENCED_PARAMETER(DeviceObject); #141 status = STATUS_SUCCESS; #142 #143 // 完成IRP請(qǐng)求 #144 Irp->IoStatus.Status = status; #145 Irp->IoStatus.Information = 0; #146 IoCompleteRequest(Irp, IO_NO_INCREMENT); #147 #148 KdPrint(("End of HelloDRIVER DefaultDispatch!\n")); #149 return status; #150 } #151 #152 //*============================================================== #153 //*= = 文件結(jié)束 #154 //*==============================================================
本示例代碼可以從本書(shū)的\Chapter01\HelloDRIVER目錄下找到。
1.1.2 代碼解釋
或許你驚訝于這個(gè)簡(jiǎn)單的擴(kuò)充如此之長(zhǎng),其實(shí)如果細(xì)心看一下,它并不像你想象的那么多。
細(xì)心的讀者一定發(fā)現(xiàn)了,目前所用的兩個(gè)例子都攜帶了完整的程序注釋。注釋對(duì)于編程風(fēng)格來(lái)說(shuō)是很重要的一點(diǎn),很多程序員對(duì)此不以為然,這是很不好的一個(gè)習(xí)慣。不同的編程人員習(xí)慣于不同的編程風(fēng)格,希望讀者能對(duì)此加以重視并形成自己的編程風(fēng)格。良好的編程風(fēng)格不僅有利于程序的可讀性,對(duì)于程序的質(zhì)量也起著不容忽視的作用。
希望對(duì)編碼質(zhì)量進(jìn)一步提高的讀者可以閱讀《編程匠藝——編寫(xiě)卓越代碼》或者《代碼大全》等書(shū)。在之后的代碼示例中,出于編排考慮,將不再貼出完整示例及注釋。
接下來(lái)我們仔細(xì)看一下示例代碼,并對(duì)此加以詳細(xì)解釋。
代碼示例1-2是HelloDRIVER驅(qū)動(dòng)程序的聲明文件。這之后,我們對(duì)注釋部分不再單獨(dú)加以解釋?zhuān)?qǐng)讀者自行閱讀。
第010、011、052行,這是C語(yǔ)言中常見(jiàn)的預(yù)處理,用來(lái)避免頭文件重復(fù)包含導(dǎo)致編譯錯(cuò)誤。另外,也可以使用#pragma once來(lái)避免此錯(cuò)誤,其作用是防止頭文件多次被包含,保證頭文件只被編譯一次,比上個(gè)方法的可移植性稍差。
第017行,包含驅(qū)動(dòng)所需的頭文件。
第023~029行,這是一個(gè)結(jié)構(gòu)體定義,用以描述驅(qū)動(dòng)程序的設(shè)備擴(kuò)展。它保存了我們自定義所需的一些信息,有助于更加方便地編程。
第35~50行,這是相關(guān)的函數(shù)聲明。這些函數(shù)的具體實(shí)現(xiàn)存在于定義文件中,我們?cè)谙旅婕右栽敿?xì)介紹。
代碼示例1-3是HelloDRIVER驅(qū)動(dòng)程序的定義文件。
第010行,包含指定的聲明文件。為每個(gè)定義文件寫(xiě)一個(gè)聲明文件是一個(gè)不錯(cuò)的習(xí)慣。
第016~018行,這是一些預(yù)處理。在驅(qū)動(dòng)開(kāi)發(fā)中,需要為每一個(gè)函數(shù)指定其是分頁(yè)內(nèi)存還是非分頁(yè)內(nèi)存。INIT標(biāo)識(shí)是指此函數(shù)在驅(qū)動(dòng)加載時(shí)使用,是初始化相關(guān)的函數(shù),驅(qū)動(dòng)成功加載以后可以從內(nèi)存卸載。PAGE標(biāo)識(shí)是指此函數(shù)在驅(qū)動(dòng)運(yùn)行時(shí)可以被交換到磁盤(pán)上。如果不指定,編譯器默認(rèn)為非分頁(yè)內(nèi)存。
一般情況下,我們不需要考慮這些問(wèn)題,但是有些特殊情況,代碼是不允許被交換到磁盤(pán)上的,否則將導(dǎo)致操作系統(tǒng)藍(lán)屏或者自動(dòng)重啟。這里需要注意一點(diǎn),那就是函數(shù)聲明必須在這些指定內(nèi)存分配的預(yù)處理之前,否則無(wú)法通過(guò)編譯。
從第027行開(kāi)始,是DriverEntry函數(shù)的具體實(shí)現(xiàn)。1.1節(jié)我們說(shuō)過(guò),DriverEntry是驅(qū)動(dòng)程序的入口函數(shù),它由操作系統(tǒng)內(nèi)核中的I/O管理器調(diào)用。
第033~038行,這是函數(shù)相關(guān)的變量定義。在C語(yǔ)言中,變量不允許被定義在函數(shù)流程處理中,也就是說(shuō),必須定義在函數(shù)體的開(kāi)始處,否則出現(xiàn)編譯錯(cuò)誤。在C++語(yǔ)言中則沒(méi)有這種限制。
第039行,可以看出KdPrint也是一個(gè)字符串打印函數(shù)。其實(shí)這個(gè)函數(shù)和上面的DbgPrint是同一個(gè)函數(shù),是它的宏定義方式,用以打印調(diào)試信息。將其定義為宏的好處在于調(diào)試版本打印出具體信息供開(kāi)發(fā)者參考,而在發(fā)行版本編譯時(shí)完全被移除了,這樣可以減小驅(qū)動(dòng)文件大小并有助于提高程序的運(yùn)行效率。
這里提一下調(diào)試版本和發(fā)行版本。在應(yīng)用程序中調(diào)試版本和發(fā)行版本分別被稱(chēng)為Debug版本和Release版本,而在驅(qū)動(dòng)程序中則被稱(chēng)為Check版本和Free版本。前后兩者除了名字不一樣外,沒(méi)什么實(shí)質(zhì)性的差別。而調(diào)試版本和發(fā)行版本的不同則在于前者包含了大量的調(diào)試信息,沒(méi)有經(jīng)過(guò)優(yōu)化,方便開(kāi)發(fā)者尋找程序缺陷和漏洞。
第041行,UNREFERENCED_PARAMETER是一個(gè)宏,經(jīng)常被用來(lái)指定參數(shù)未被引用,可以避免不必要的警告。
說(shuō)到警告,很多應(yīng)用程序員大都不以為然。但是我們希望你在驅(qū)動(dòng)開(kāi)發(fā)中能改掉這個(gè)習(xí)慣,當(dāng)然沒(méi)有更好。因?yàn)轵?qū)動(dòng)程序的崩潰會(huì)導(dǎo)致操作系統(tǒng)的崩潰,直接造成死機(jī)或藍(lán)屏。除非你十分確定警告不會(huì)對(duì)驅(qū)動(dòng)程序帶來(lái)不穩(wěn)定的因素,否則請(qǐng)修正它,因?yàn)樽龅經(jīng)]有警告是使驅(qū)動(dòng)更加趨于穩(wěn)定的一個(gè)基礎(chǔ)。
第043行,對(duì)一個(gè)Unicode字符串進(jìn)行初始化。Windows內(nèi)核中大量使用Unicode字符串,其具體操作有一系列函數(shù)(詳情請(qǐng)參看MSDN文檔),這一系列函數(shù)屬于Rtl系列,也就是微軟推薦使用的運(yùn)行時(shí)函數(shù)。
第046~049行,一個(gè)循環(huán)體。宏IRP_MJ_MAXIMUM_FUNCTION代表驅(qū)動(dòng)程序最大的派遣函數(shù)指針數(shù)。這里使用一個(gè)默認(rèn)的派遣函數(shù)來(lái)初始化它們,然后緊跟著在下面修改我們不打算使用默認(rèn)的派遣函數(shù)指針。
這些派遣函數(shù)又可以稱(chēng)為回調(diào)函數(shù),由定義實(shí)現(xiàn),提供給操作系統(tǒng)調(diào)用。回調(diào)函數(shù)的意義和應(yīng)用程序中的沒(méi)有差別,只不過(guò)在驅(qū)動(dòng)程序中,這些派遣函數(shù)是我們的主要工作重點(diǎn)。學(xué)習(xí)本書(shū)的主要任務(wù)也會(huì)建立在它們的基礎(chǔ)之上。
對(duì)于普通的驅(qū)動(dòng)程序,可以不考慮對(duì)所有的派遣函數(shù)指針進(jìn)行初始化,但是如果想要實(shí)現(xiàn)一個(gè)過(guò)濾驅(qū)動(dòng)程序,那么請(qǐng)參照以上方式初始化。具體實(shí)現(xiàn)方式,請(qǐng)參閱本書(shū)關(guān)于過(guò)濾驅(qū)動(dòng)程序的章節(jié)。如果沒(méi)有進(jìn)行全部初始化,編譯器會(huì)對(duì)未處理的派遣函數(shù)指針進(jìn)行默認(rèn)處理。
第051行,卸載函數(shù)。這個(gè)派遣函數(shù)必須單獨(dú)提供,并且在操作系統(tǒng)版本不同的情況下,這個(gè)函數(shù)可能需要注意一些不同的東西。如果不打算對(duì)驅(qū)動(dòng)程序進(jìn)行卸載,這個(gè)函數(shù)可以不用提供。
第052~055行,提供給操作系統(tǒng)的創(chuàng)建、關(guān)閉以及讀寫(xiě)的派遣函數(shù)。當(dāng)然,還有更多的派遣函數(shù)需要提供,這里為了簡(jiǎn)單,我們使用DefaultDispatch來(lái)代替。
第058行,使用IoCreateDevice函數(shù)宏創(chuàng)建一個(gè)設(shè)備對(duì)象,其名稱(chēng)為“HelloDRIVER”。HelloDRIVER的設(shè)備類(lèi)型為“FILE_DEVICE_UNKNOWN”,是一種獨(dú)占設(shè)備,在運(yùn)行時(shí)只能被一個(gè)應(yīng)用程序所使用。
第065~068行,判斷設(shè)備是否創(chuàng)建成功,并進(jìn)行必要的失敗處理。驅(qū)動(dòng)程序中這樣的處理對(duì)于驅(qū)動(dòng)程序的健壯性起著不容忽視的作用。
第070行,設(shè)置設(shè)備的標(biāo)識(shí)。有BUFFERED_IO和DO_DIRECT_IO兩種,代表了兩種不同的緩沖區(qū)處理方式。
第071~076行,這里初始化了一個(gè)Unicode字符串,同時(shí)也初始化了聲明文件中定義過(guò)的設(shè)備擴(kuò)展結(jié)構(gòu)體。設(shè)備擴(kuò)展中保存了我們自定義所需的一些信息。
第079~085行,使用IoCreateSymbolicLink函數(shù)宏創(chuàng)建了設(shè)備符號(hào)鏈接,并對(duì)創(chuàng)建結(jié)果判斷以進(jìn)行必要的失敗處理。這個(gè)符號(hào)鏈接名主要用來(lái)與應(yīng)用程序進(jìn)行通信。如果創(chuàng)建失敗,則刪除已經(jīng)創(chuàng)建的設(shè)備對(duì)象。
驅(qū)動(dòng)程序的設(shè)備名稱(chēng)對(duì)應(yīng)用程序是透明的,所以只能用于內(nèi)核程序。這也是為什么要?jiǎng)?chuàng)建設(shè)備符號(hào)鏈接的原因。
從第098行開(kāi)始,是DriverUnload函數(shù)的具體實(shí)現(xiàn),它的功能是刪除設(shè)備對(duì)象和設(shè)備符號(hào)鏈接。如果在DriverEntry函數(shù)中分配了資源,也要在這里釋放。
第107行,由驅(qū)動(dòng)對(duì)象指針參數(shù)得到設(shè)備對(duì)象指針。
第109~119行,遍歷已經(jīng)創(chuàng)建的所有設(shè)備符號(hào)鏈接和設(shè)備對(duì)象,并將其刪除。
從第131行開(kāi)始,是DefaultDispatch函數(shù)的具體實(shí)現(xiàn),它的功能是直接完成了IRP (Input/Output Request Package,輸入輸出請(qǐng)求包)。
第144行,設(shè)置IRP的狀態(tài)為成功。
第145行,因?yàn)榇蛩阒苯油瓿蒊RP,所以操作信息的長(zhǎng)度為空,這里將字節(jié)處理長(zhǎng)度信息設(shè)置為0。
第146行,使用IoCompleteRequest函數(shù)直接完成IRP。
為了遵循一部分讀者開(kāi)發(fā)程序的習(xí)慣,這個(gè)示例只使用了一個(gè)文件。在編譯程序時(shí),聲明文件是默認(rèn)被包含到定義文件中的,所以暫時(shí)忽略不計(jì)。
1.1.3 驅(qū)動(dòng)程序的編譯和安裝
編譯驅(qū)動(dòng)程序需要驅(qū)動(dòng)開(kāi)發(fā)環(huán)境。我們還沒(méi)有介紹WDK的下載與安裝,所以讀者可以先看看這里的編譯、安裝效果,等閱讀完第2章以后,再回過(guò)頭來(lái)自己動(dòng)手實(shí)踐,相信你一定可以非常輕松地完成這個(gè)任務(wù)。
驅(qū)動(dòng)程序的編譯不像開(kāi)發(fā)應(yīng)用程序一樣,可以簡(jiǎn)單地單擊某個(gè)菜單或命令按鈕就能通過(guò)IDE集成環(huán)境完全實(shí)現(xiàn),而是需要使用一種叫做nmake的工具。
打開(kāi)Hello World目錄,可以看到有一個(gè)Makefile文件。文件內(nèi)容如下:
!INCLUDE $(NTMAKEENV)\makefile.def
這個(gè)文件幾乎千篇一律,讀者可以隨便找一個(gè)Windows驅(qū)動(dòng)程序的示例拷貝一個(gè)。微軟建議不要去修改這個(gè)文件,我們最好遵循這個(gè)建議。
另外,讀者還能發(fā)現(xiàn)文件夾里有一個(gè)Sources文件,具體內(nèi)容如下:
TARGETNAME = Hello World TARGETTYPE = DRIVER TARGETPATH = OBJ SOURCES = Hello World.c
這是一個(gè)簡(jiǎn)單的Sources文件例子。第1行用來(lái)指定驅(qū)動(dòng)編譯后的驅(qū)動(dòng)程序文件名稱(chēng);第2行用來(lái)指定生成的程序?yàn)轵?qū)動(dòng)程序;第3行用來(lái)指定編譯后生成文件的存放路徑;最后1行用來(lái)指定要編譯的源碼文件。
大部分驅(qū)動(dòng)程序開(kāi)發(fā)人員都知道Sources文件的這種用法,卻很少用它編譯EXE或者DLL。具體論述參閱本書(shū)第2章。
這里一定不要寫(xiě)聲明文件,否則將出現(xiàn)編譯錯(cuò)誤,錯(cuò)誤信息提示為“編譯目錄出現(xiàn)錯(cuò)誤”,而沒(méi)有指明具體是什么錯(cuò)誤。如果犯下這樣的錯(cuò)誤,一時(shí)間很難想到錯(cuò)誤原因。
如果想要查看編譯輸出的錯(cuò)誤信息和警告信息,可以從工程目錄里尋找ERR類(lèi)型文件與WRN類(lèi)型文件。
打開(kāi)WDK編譯環(huán)境,這個(gè)環(huán)境是以命令行形式提供的,按如下操作進(jìn)行即可。
如圖1-1所示,單擊“開(kāi)始”→“程序”→“Windows Driver Kits”→“WDK6001. 10082”→“Build Environments”→“Windows XP”→“Launch Windows XP x86 Checked Build Environment”,打開(kāi)WDK編譯環(huán)境的命令行,通過(guò)DOS命令切換到工程文件的存放目錄,輸入“build”,然后按回車(chē)鍵,編譯結(jié)果如圖1-2所示。

圖1-1 開(kāi)始菜單中的WDK

圖1-2 Hello World的編譯結(jié)果
編譯鏈接成功的SYS類(lèi)型文件存放于當(dāng)前目錄的\objfre_wxp_x86\i386下。
同理,我們用同樣的辦法編譯HelloDRIVER驅(qū)動(dòng)程序。不過(guò)這里采用Check版本,選擇“Launch Windows XP x86 Checked Build Environment”。文件生成的目錄會(huì)稍微有一點(diǎn)差異,在當(dāng)前目錄的\objchk_wxp_x86\i386下。
接下來(lái)的問(wèn)題是如何加載驅(qū)動(dòng)程序。文件夾中的HelloDRIVER.sys驅(qū)動(dòng)程序是無(wú)法直接加載的,也就是說(shuō),無(wú)法像運(yùn)行EXE程序一樣,直接雙擊運(yùn)行。NT驅(qū)動(dòng)需要有專(zhuān)門(mén)的程序來(lái)加載,這里使用DriverStudio軟件里的Monitor工具。
打開(kāi)Monitor軟件,單擊“File”→“Open Driver”,選擇“HelloDRIVER.sys”,然后依次運(yùn)行Start Driver和Stop Driver,可以看到如圖1-3所示的信息。

圖1-3 HelloDRIVER驅(qū)動(dòng)的加載信息
同樣,我們也可以用這個(gè)工具加載Hello World驅(qū)動(dòng)程序。如果使用Monitor工具,可能需要安裝DriverStudio軟件,或者將其單獨(dú)取出來(lái)。如果讀者嫌麻煩,可以從網(wǎng)上找自己喜歡的驅(qū)動(dòng)加載工具。
這里的驅(qū)動(dòng)程序不需要進(jìn)行調(diào)試和測(cè)試,因?yàn)槲覀兘o出的是已經(jīng)經(jīng)過(guò)測(cè)試與調(diào)試之后運(yùn)行正常的代碼。關(guān)于具體調(diào)試和測(cè)試的知識(shí),我們會(huì)在以后的章節(jié)中詳細(xì)論述。
1.1.4 查看我們的驅(qū)動(dòng)
在前面我們提到,DbgPrint函數(shù)打印的字符串無(wú)法通過(guò)控制臺(tái)查看,需要借助于其他的工具。
通過(guò)圖1-3可以知道,使用Monitor工具可以查看驅(qū)動(dòng)程序中的打印信息。這里簡(jiǎn)單介紹一下另外一個(gè)工具——DebugView軟件,它也可以用來(lái)查看在驅(qū)動(dòng)程序中打印的調(diào)試信息。
打開(kāi)DebugView軟件,然后利用Monitor加載Hello World程序,可以看到DebugView中顯示了如圖1-4所示的信息。

圖1-4 使用DebugView查看打印信息
程序的“Hello, Windows Driver!”赫然在目!
如果想查看自己的設(shè)備驅(qū)動(dòng),可以按照上面的步驟再次加載HelloDRIVER驅(qū)動(dòng)程序,然后重啟計(jì)算機(jī)。
右鍵單擊“我的電腦”→選擇“屬性”→“硬件”選項(xiàng)卡→“設(shè)備管理器”,打開(kāi)“設(shè)備管理器”窗口,選擇“查看”菜單下的“顯示隱藏的設(shè)備”命令,可以看到已經(jīng)加載成功的HelloDRIVER驅(qū)動(dòng)。
筆者的計(jì)算機(jī)中顯示的信息如圖1-5所示。

圖1-5 查看HelloDRIVER設(shè)備
這是通過(guò)操作系統(tǒng)自身看到的,當(dāng)然,也可以通過(guò)其他軟件來(lái)查看,比如WinObj工具,如圖1-6所示。注意:本書(shū)里提到的工具,在第2章都會(huì)進(jìn)行介紹。

圖1-6 用WinObj查看HelloDRIVER設(shè)備
1.2 虛擬環(huán)境
1.2.1 使用虛擬環(huán)境進(jìn)行驅(qū)動(dòng)開(kāi)發(fā)
因?yàn)閮?nèi)核調(diào)試會(huì)凍結(jié)它所運(yùn)行的操作系統(tǒng),并存在各種形式的系統(tǒng)不穩(wěn)定等弊端,在實(shí)際的驅(qū)動(dòng)開(kāi)發(fā)工作中,大多數(shù)開(kāi)發(fā)人員只是在最終階段才會(huì)在實(shí)際的機(jī)器上進(jìn)行開(kāi)發(fā)調(diào)試,其余的大部分時(shí)間是使用虛擬環(huán)境進(jìn)行驅(qū)動(dòng)開(kāi)發(fā)的。
使用虛擬環(huán)境進(jìn)行開(kāi)發(fā),還可以節(jié)省很多資源,例如,對(duì)于沒(méi)有能力購(gòu)買(mǎi)另外一臺(tái)PC的人,或者對(duì)于頻繁出差需要在便攜環(huán)境下進(jìn)行內(nèi)核調(diào)試的人來(lái)說(shuō),都提供了不錯(cuò)的解決方案。
這里通常使用的兩類(lèi)虛擬環(huán)境為VMware虛擬機(jī)和Virtual PC虛擬機(jī)。
1.2.2 使用VMware虛擬機(jī)
VMware Workstation是威睿公司推出的一款商業(yè)軟件產(chǎn)品,是桌面虛擬化解決方案的軟件代表,如圖1-7所示。全球不同規(guī)模的客戶都依賴它降低成本和運(yùn)營(yíng)費(fèi)用,確保業(yè)務(wù)持續(xù)性以及加強(qiáng)安全性等。

圖1-7 WMware 7.0.1版本下的Windows XP操作系統(tǒng)
VMware虛擬機(jī)在7.0版本之后的版本中增加了對(duì)Windows 7的支持。所以這里的設(shè)置以VMware 7.0以及之后的版本為準(zhǔn),但是配置卻與之前的版本完全一樣。
新建虛擬機(jī)之后,單擊菜單項(xiàng)“VM”→“Settings”,會(huì)彈出如圖1-8所示的對(duì)話框。單擊“Add”按鈕,選擇“Serial Port”,再選擇“Use named pipe”,在第一個(gè)編輯框中輸入“\\.\pipe\com_1”,第二項(xiàng)選擇“The end is the server.”,第三項(xiàng)選擇“The other end is a virtual machine.”,然后單擊“OK”按鈕完成操作。

圖1-8 VMware虛擬機(jī)的配置對(duì)話框
注意:一定要選中“Device status”下的復(fù)選框“Connect at power on”。
1.2.3 目標(biāo)機(jī)設(shè)置
虛擬機(jī)里安裝了想要調(diào)試的操作系統(tǒng)版本之后,還需要設(shè)置目標(biāo)機(jī)為可調(diào)試的。在Windows Vista操作系統(tǒng)之后,目標(biāo)機(jī)的可調(diào)試性設(shè)置與之前略有不同,下面一一介紹。
(1)Windows XP/Windows 2003的環(huán)境設(shè)置
打開(kāi)系統(tǒng)盤(pán),顯示受保護(hù)的操作系統(tǒng)文件并且顯示所有的文件和文件夾,找到boot.ini文件,去掉只讀屬性等,打開(kāi)文件可以看到如下內(nèi)容:
[boot loader] timeout=30 default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS [operating systems] multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /noexecute=optin /fastdetect
復(fù)制最后一行文字,拷貝到下一行,并在最后添加“/debug /debug /debugport=com1/baudrate=115200”,顯示如下:
[boot loader] timeout=30 default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS [operating systems] multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /noexecute=optin /fastdetect /noguiboot multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /noexecute=optin /fastdetect /debug /debug /debugport=com1 /baudrate=115200
保存之后再次啟動(dòng),即可發(fā)現(xiàn)引導(dǎo)菜單中多了一項(xiàng)“調(diào)試引導(dǎo)的系統(tǒng)”。
(2)Windows Vista/ Windows 7的環(huán)境設(shè)置
Windows Vista版本之前,Windows操作系統(tǒng)使用boot.ini進(jìn)行引導(dǎo),所以可以通過(guò)修改此文件進(jìn)行系統(tǒng)的可調(diào)試性設(shè)置,但是Windows Vista之后的操作系統(tǒng)就無(wú)法再用法進(jìn)行可調(diào)試性設(shè)置了。
在已經(jīng)出版的《天書(shū)夜讀——從匯編語(yǔ)言到Windows內(nèi)核編程》和《寒江獨(dú)釣——Windows內(nèi)核安全編程》兩本書(shū)中,關(guān)于Windows Vista的可調(diào)試性設(shè)置一直是使用bcdedit命令。這種方式比較煩瑣,但是在系統(tǒng)啟動(dòng)時(shí)可以有選擇性地進(jìn)入,或者為普通啟動(dòng),或者為調(diào)試啟動(dòng),無(wú)須再重新進(jìn)行選項(xiàng)設(shè)置。
對(duì)于這種方式的設(shè)置,讀者可以參考前兩本書(shū),這里不再?gòu)?fù)述。這里講述一種較為簡(jiǎn)單的設(shè)置方式,但是如果需要普通啟動(dòng),則需要重新進(jìn)行選項(xiàng)設(shè)置。
單擊“開(kāi)始”菜單→“所有程序”→“附件”,打開(kāi)“命令提示符”,輸入“msconfig”命令,打開(kāi)“系統(tǒng)配置”對(duì)話框;或者使用“WIN+R”組合鍵打開(kāi)“運(yùn)行”對(duì)話框,輸入“msconfig”命令,打開(kāi)“系統(tǒng)配置”對(duì)話框。選擇“引導(dǎo)”選項(xiàng)卡,單擊“高級(jí)選項(xiàng)”按鈕,如圖1-9所示。

圖1-9 Windows Vista/Windows 7的可調(diào)試性設(shè)置對(duì)話框
選中“調(diào)試”復(fù)選框,然后選擇“調(diào)試端口”等。這里的設(shè)置與虛擬機(jī)的設(shè)置以及調(diào)試器的設(shè)置一定要一致,只有三者的設(shè)置一致,才能正常連接調(diào)試。這里采用的選項(xiàng)與之前的設(shè)置一樣:
-b -k com:port=\\.\pipe\com_1,baud=115200,pipe
將WinDBG連接至虛擬機(jī)進(jìn)行調(diào)試,如圖1-10所示。
運(yùn)行VMware虛擬機(jī)里設(shè)置成功的調(diào)試操作系統(tǒng),然后打開(kāi)我們創(chuàng)建的WinDBG快捷方式進(jìn)行連接。WinDBG調(diào)試器的快捷鍵以及菜單命令與Visual Studio系列軟件一致,包括設(shè)置斷點(diǎn)按F9鍵、運(yùn)行按F5鍵等,讀者可以看一下調(diào)試菜單中的菜單項(xiàng)。
圖1-10是本書(shū)第一個(gè)例子Hello World驅(qū)動(dòng)的調(diào)試截圖,其中斷在打印輸出日志的語(yǔ)句上。
1.2.4 Virtual PC虛擬機(jī)
Virtual PC是微軟為了爭(zhēng)奪虛擬化市場(chǎng)而推出的一款軟件產(chǎn)品,和VMware一樣也十分優(yōu)秀。它也可以像VMware一樣,被用來(lái)進(jìn)行驅(qū)動(dòng)的雙機(jī)調(diào)試。由于這里已經(jīng)詳細(xì)介紹了VMware虛擬機(jī)的使用,所以Virtual PC就不做過(guò)多的介紹了,它的設(shè)置與VMware虛擬機(jī)一樣,并不復(fù)雜,讀者可以自己嘗試一下。
1.3 小結(jié)
本章以一個(gè)簡(jiǎn)單的Hello World驅(qū)動(dòng)程序?yàn)槔v解了驅(qū)動(dòng)編程中最基本的一些要素。本章內(nèi)容較為基礎(chǔ),對(duì)于初學(xué)者是不錯(cuò)的入門(mén)課程;而對(duì)于熟練的讀者,算是讓大家在學(xué)習(xí)更豐富的內(nèi)容之前,熱熱身吧。
- Linux設(shè)備驅(qū)動(dòng)開(kāi)發(fā)詳解:基于最新的Linux4.0內(nèi)核
- Mastering ElasticSearch
- Getting Started with oVirt 3.3
- Linux Mint Essentials
- Kubernetes網(wǎng)絡(luò)權(quán)威指南:基礎(chǔ)、原理與實(shí)踐
- 新手易學(xué):系統(tǒng)安裝與重裝
- Linux集群和自動(dòng)化運(yùn)維
- 嵌入式操作系統(tǒng)(Linux篇)(微課版)
- Linux就該這么學(xué)
- Windows 7案例教程
- NetDevOps入門(mén)與實(shí)踐
- 精解Windows 10
- iOS 10 開(kāi)發(fā)指南
- Linux從入門(mén)到精通(視頻教學(xué)版)
- OpenStack Essentials(Second Edition)