官术网_书友最值得收藏!

第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)容之前,熱熱身吧。

主站蜘蛛池模板: 罗江县| 广水市| 明水县| 石楼县| 普定县| 维西| 宁阳县| 尼木县| 大同县| 林西县| 浑源县| 乌审旗| 宜章县| 紫阳县| 刚察县| 新宾| 佛学| 高碑店市| 友谊县| 周宁县| 含山县| 洛浦县| 宾川县| 三台县| 丽水市| 马龙县| 祁门县| 德令哈市| 宁强县| 淄博市| 定州市| 仪陇县| 井陉县| 平潭县| 万宁市| 永济市| 吉首市| 巴青县| 财经| 平和县| 南宫市|