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

第1章 Hello World驅動

1.1 從Hello World開始

國外的計算機領域有一個著名的組織,名字叫“計算機器協會”,它在互聯網上專門維護了一個網頁,上面列出了200多種版本的“Hello World”程序,仿佛代碼的羅塞塔石碑。

在使用一門新的編程語言時,人們習慣把“Hello World”作為第一個程序。漸漸地,人們也習慣用類似“Hello World”的程序作為一切程序的第一步,以此喚出程序員心中對于編程樂觀的一面。

如本節的題目一樣,如何編寫驅動程序是本節將要解決的問題。

那么,讓我們看一下驅動程序的“Hello World”吧!

代碼示例1-1 Hello World驅動程序

      #001 /*
      #002 *******************************************************
      #003 *= = 文件名稱:Hello World.c
      #004 *= = 文件描述:驅動程序的Hello World例子
      #005 *= = 作    者:竹林蹊徑
      #006 *= = 編寫時間:2009-04-23 21:16:00
      #007 *******************************************************
      #008 */
      #009
      #010 #include <NTDDK.h>
      #011
      #012 //*====================================================
      #013 //*= = 函數名稱:DriverEntry
      #014 //*= = 功能描述:驅動程序入口函數
      #015 //*= = 入口參數:PDRIVER_OBJECT, PUNICODE_STRING
      #016 //*= = 出口參數: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 //*= = 文件結束
      #031 //*====================================================

本示例代碼可以從本書的\Chapter01\Hello World目錄下找到。

以上就是驅動程序最簡單的“Hello World”例子,去掉代碼注釋部分,放眼程序,全部代碼屈指可數。

由第003行注釋可知,這個例子的文件名稱是Hello World.c,說明此文件是由C語言編寫而成的。

我們知道,操作系統剛出現時是由機器語言和匯編語言編寫的,后來為了可移植性等采用了C語言。早期的一些Windows操作系統也是采用C語言編寫的,而在開發基于NT技術的Windows操作系統時,同時采用了C和C++這兩種高級語言。

那么究竟是采用C語言還是C++語言來開發驅動程序呢?其實兩者各有優缺點,讀者可以在讀本書的時候仔細體會。這里采用C語言編寫了第一個例子,因為用C語言完全可以開發出所有的驅動程序,同時你會有似曾相識的感覺。但是微軟提供了WDF驅動開發模型,這個在后面的章節中會講到,如果需要使用WDF,C++無疑是最好的選擇。

實際上,驅動程序編譯成的二進制文件是SYS類型文件,和普通的EXE類型文件一樣,也是PE格式。PE是Portable Executable File Format的簡寫,是微軟Windows平臺環境下主流的可執行程序標準格式,DLL也是常見的PE格式。所以,使用什么編程語言并不嚴格限定,如果你喜歡,即使用匯編或Delphi也可以開發驅動程序。比如在VxD驅動編程模型盛行時,很多人還是使用匯編開發設備驅動程序,并提供了相應的開發環境;更有甚者提供了EXE類型程序到SYS類型程序的轉換工具,這些工具雖然大多沒有流行起來,但是卻佐證了這種方法的可行性。

不過,微軟提供的內核編程接口和示例只有C/C++的,為了方便起見,我們約定本書使用C/C++語言來開發驅動程序。

開發驅動程序大致和開發普通應用程序一樣,幾乎擁有同樣的流程:分析需求、設計、編碼、調試、測試、發布、維護這幾個主要環節。但是在后幾個環節上,驅動程序的開發和普通應用程序的開發又有著很大的差別。

下面,我們來簡單分析一下代碼。

第010行包含了一個頭文件NTDDK.h,這個頭文件是NT驅動必須包含的一個頭文件,WDM驅動則需要換成WDM.h。

第019~027行,整段代碼只有一個DriverEntry函數。這個函數是所有驅動程序的入口函數,類似于Win32編程下的WinMain函數或C語言的main函數。

第021~022行,函數的兩個參數,分別代表驅動對象的指針和注冊表子鍵的字符串指針。這個暫且不作詳細論述,我們將在下面章節中具體說明。其中,__in是一個宏,代表這個參數是入口參數,常見的還有__out,代表出口參數。

第025~026行,再看函數里面,我們看到只有兩個語句,是不是很熟悉呢?

DbgPrint是一個函數,類似于C語言的printf函數,打印一串字符串,打印的內容為“Hello, Windows Driver!”。隨后函數返回一個值STATUS_SUCCESS,這個值是個宏,從字面意思可知它代表成功,語句類似于main函數的“return 0;”。這里需要注意的是,打印的內容無法通過控制臺查看,需要借助其他工具才能看到。

那么如何對這個程序進行編譯呢?

我們需要一個開發環境,這個開發環境名為WDK,微軟已經為我們提供了。關于開發環境的詳細介紹,請參閱本書第2章。

一切都那么自然!下面對這個例子進行簡單的擴充。

1.1.1 HelloDRIVER

這里是一個關于Hello World驅動程序示例代碼的簡單擴充。如果完全看不懂,那就直接跳到1.1.2節看代碼解釋吧!

代碼示例1-2驅動程序HelloDRIVER聲明文件

      #001 /*
      #002 *****************************************************************
      #003 *= = 文件名稱:HelloDRIVER.h
      #004 *= = 文件描述:關于HelloDRIVER的頭文件
      #005 *= = 作    者:竹林蹊徑
      #006 *= = 編寫時間: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 //*= = 宏與結構體
      #021 //*==============================================================
      #022
      #023 typedef struct _DEVICE_EXTENSION {
      #024
      #025     PDEVICE_OBJECT DeviceObject;    // 指回設備對象的指針
      #026     UNICODE_STRING DeviceName;      // 設備名稱
      #027     UNICODE_STRING SymbolicLink;    // 符號鏈接名
      #028
      #029 }DEVICE_EXTENSION, *PDEVICE_EXTENSION;
      #030
      #031 //*==============================================================
      #032 //*= = 函數聲明
      #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 //*= = 文件結束
      #056 //*==============================================================

代碼示例1-3驅動程序HelloDRIVER定義文件

      #001 /*
      #002 *****************************************************************
      #003 *= = 文件名稱:HelloDRIVER.c
      #004 *= = 文件描述:驅動程序HelloDRIVER例子
      #005 *= = 作    者:竹林蹊徑
      #006 *= = 編寫時間:2009-04-23 21:16:00
      #007 *****************************************************************
      #008 */
      #009
      #010 #include "HelloDRIVER.h"
      #011
      #012 //*==============================================================
      #013 //*= = 預處理定義
      #014 //*==============================================================
      #015
      #016 #pragma alloc_text(INIT, DriverEntry)
      #017 #pragma alloc_text(PAGE, DefaultDispatch)
      #018 #pragma alloc_text(PAGE, DriverUnload)
      #019
      #020 //*==============================================================
      #021 //*= = 函數名稱:DriverEntry
      #022 //*= = 功能描述:驅動程序入口函數
      #023 //*= = 入口參數:PDRIVER_OBJECT, PUNICODE_STRING
      #024 //*= = 出口參數: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     // 創建設備
      #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     // 創建符號鏈接
      #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 //*= = 函數名稱:DriverUnload
      #093 //*= = 功能描述:驅動程序卸載函數
      #094 //*= = 入口參數:PDRIVER_OBJECT
      #095 //*= = 出口參數: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         // 刪除符號鏈接與設備
      #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 //*= = 函數名稱:DefaultDispatch
      #126 //*= = 功能描述:驅動程序默認派遣例程
      #127 //*= = 入口參數:PDEVICE_OBJECT, PIRP
      #128 //*= = 出口參數: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請求
      #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 //*= = 文件結束
      #154 //*==============================================================

本示例代碼可以從本書的\Chapter01\HelloDRIVER目錄下找到。

1.1.2 代碼解釋

或許你驚訝于這個簡單的擴充如此之長,其實如果細心看一下,它并不像你想象的那么多。

細心的讀者一定發現了,目前所用的兩個例子都攜帶了完整的程序注釋。注釋對于編程風格來說是很重要的一點,很多程序員對此不以為然,這是很不好的一個習慣。不同的編程人員習慣于不同的編程風格,希望讀者能對此加以重視并形成自己的編程風格。良好的編程風格不僅有利于程序的可讀性,對于程序的質量也起著不容忽視的作用。

希望對編碼質量進一步提高的讀者可以閱讀《編程匠藝——編寫卓越代碼》或者《代碼大全》等書。在之后的代碼示例中,出于編排考慮,將不再貼出完整示例及注釋。

接下來我們仔細看一下示例代碼,并對此加以詳細解釋。

代碼示例1-2是HelloDRIVER驅動程序的聲明文件。這之后,我們對注釋部分不再單獨加以解釋,請讀者自行閱讀。

第010、011、052行,這是C語言中常見的預處理,用來避免頭文件重復包含導致編譯錯誤。另外,也可以使用#pragma once來避免此錯誤,其作用是防止頭文件多次被包含,保證頭文件只被編譯一次,比上個方法的可移植性稍差。

第017行,包含驅動所需的頭文件。

第023~029行,這是一個結構體定義,用以描述驅動程序的設備擴展。它保存了我們自定義所需的一些信息,有助于更加方便地編程。

第35~50行,這是相關的函數聲明。這些函數的具體實現存在于定義文件中,我們在下面加以詳細介紹。

代碼示例1-3是HelloDRIVER驅動程序的定義文件。

第010行,包含指定的聲明文件。為每個定義文件寫一個聲明文件是一個不錯的習慣。

第016~018行,這是一些預處理。在驅動開發中,需要為每一個函數指定其是分頁內存還是非分頁內存。INIT標識是指此函數在驅動加載時使用,是初始化相關的函數,驅動成功加載以后可以從內存卸載。PAGE標識是指此函數在驅動運行時可以被交換到磁盤上。如果不指定,編譯器默認為非分頁內存。

一般情況下,我們不需要考慮這些問題,但是有些特殊情況,代碼是不允許被交換到磁盤上的,否則將導致操作系統藍屏或者自動重啟。這里需要注意一點,那就是函數聲明必須在這些指定內存分配的預處理之前,否則無法通過編譯。

從第027行開始,是DriverEntry函數的具體實現。1.1節我們說過,DriverEntry是驅動程序的入口函數,它由操作系統內核中的I/O管理器調用。

第033~038行,這是函數相關的變量定義。在C語言中,變量不允許被定義在函數流程處理中,也就是說,必須定義在函數體的開始處,否則出現編譯錯誤。在C++語言中則沒有這種限制。

第039行,可以看出KdPrint也是一個字符串打印函數。其實這個函數和上面的DbgPrint是同一個函數,是它的宏定義方式,用以打印調試信息。將其定義為宏的好處在于調試版本打印出具體信息供開發者參考,而在發行版本編譯時完全被移除了,這樣可以減小驅動文件大小并有助于提高程序的運行效率。

這里提一下調試版本和發行版本。在應用程序中調試版本和發行版本分別被稱為Debug版本和Release版本,而在驅動程序中則被稱為Check版本和Free版本。前后兩者除了名字不一樣外,沒什么實質性的差別。而調試版本和發行版本的不同則在于前者包含了大量的調試信息,沒有經過優化,方便開發者尋找程序缺陷和漏洞。

第041行,UNREFERENCED_PARAMETER是一個宏,經常被用來指定參數未被引用,可以避免不必要的警告。

說到警告,很多應用程序員大都不以為然。但是我們希望你在驅動開發中能改掉這個習慣,當然沒有更好。因為驅動程序的崩潰會導致操作系統的崩潰,直接造成死機或藍屏。除非你十分確定警告不會對驅動程序帶來不穩定的因素,否則請修正它,因為做到沒有警告是使驅動更加趨于穩定的一個基礎。

第043行,對一個Unicode字符串進行初始化。Windows內核中大量使用Unicode字符串,其具體操作有一系列函數(詳情請參看MSDN文檔),這一系列函數屬于Rtl系列,也就是微軟推薦使用的運行時函數。

第046~049行,一個循環體。宏IRP_MJ_MAXIMUM_FUNCTION代表驅動程序最大的派遣函數指針數。這里使用一個默認的派遣函數來初始化它們,然后緊跟著在下面修改我們不打算使用默認的派遣函數指針。

這些派遣函數又可以稱為回調函數,由定義實現,提供給操作系統調用。回調函數的意義和應用程序中的沒有差別,只不過在驅動程序中,這些派遣函數是我們的主要工作重點。學習本書的主要任務也會建立在它們的基礎之上。

對于普通的驅動程序,可以不考慮對所有的派遣函數指針進行初始化,但是如果想要實現一個過濾驅動程序,那么請參照以上方式初始化。具體實現方式,請參閱本書關于過濾驅動程序的章節。如果沒有進行全部初始化,編譯器會對未處理的派遣函數指針進行默認處理。

第051行,卸載函數。這個派遣函數必須單獨提供,并且在操作系統版本不同的情況下,這個函數可能需要注意一些不同的東西。如果不打算對驅動程序進行卸載,這個函數可以不用提供。

第052~055行,提供給操作系統的創建、關閉以及讀寫的派遣函數。當然,還有更多的派遣函數需要提供,這里為了簡單,我們使用DefaultDispatch來代替。

第058行,使用IoCreateDevice函數宏創建一個設備對象,其名稱為“HelloDRIVER”。HelloDRIVER的設備類型為“FILE_DEVICE_UNKNOWN”,是一種獨占設備,在運行時只能被一個應用程序所使用。

第065~068行,判斷設備是否創建成功,并進行必要的失敗處理。驅動程序中這樣的處理對于驅動程序的健壯性起著不容忽視的作用。

第070行,設置設備的標識。有BUFFERED_IO和DO_DIRECT_IO兩種,代表了兩種不同的緩沖區處理方式。

第071~076行,這里初始化了一個Unicode字符串,同時也初始化了聲明文件中定義過的設備擴展結構體。設備擴展中保存了我們自定義所需的一些信息。

第079~085行,使用IoCreateSymbolicLink函數宏創建了設備符號鏈接,并對創建結果判斷以進行必要的失敗處理。這個符號鏈接名主要用來與應用程序進行通信。如果創建失敗,則刪除已經創建的設備對象。

驅動程序的設備名稱對應用程序是透明的,所以只能用于內核程序。這也是為什么要創建設備符號鏈接的原因。

從第098行開始,是DriverUnload函數的具體實現,它的功能是刪除設備對象和設備符號鏈接。如果在DriverEntry函數中分配了資源,也要在這里釋放。

第107行,由驅動對象指針參數得到設備對象指針。

第109~119行,遍歷已經創建的所有設備符號鏈接和設備對象,并將其刪除。

從第131行開始,是DefaultDispatch函數的具體實現,它的功能是直接完成了IRP (Input/Output Request Package,輸入輸出請求包)。

第144行,設置IRP的狀態為成功。

第145行,因為打算直接完成IRP,所以操作信息的長度為空,這里將字節處理長度信息設置為0。

第146行,使用IoCompleteRequest函數直接完成IRP。

為了遵循一部分讀者開發程序的習慣,這個示例只使用了一個文件。在編譯程序時,聲明文件是默認被包含到定義文件中的,所以暫時忽略不計。

1.1.3 驅動程序的編譯和安裝

編譯驅動程序需要驅動開發環境。我們還沒有介紹WDK的下載與安裝,所以讀者可以先看看這里的編譯、安裝效果,等閱讀完第2章以后,再回過頭來自己動手實踐,相信你一定可以非常輕松地完成這個任務。

驅動程序的編譯不像開發應用程序一樣,可以簡單地單擊某個菜單或命令按鈕就能通過IDE集成環境完全實現,而是需要使用一種叫做nmake的工具。

打開Hello World目錄,可以看到有一個Makefile文件。文件內容如下:

      !INCLUDE $(NTMAKEENV)\makefile.def

這個文件幾乎千篇一律,讀者可以隨便找一個Windows驅動程序的示例拷貝一個。微軟建議不要去修改這個文件,我們最好遵循這個建議。

另外,讀者還能發現文件夾里有一個Sources文件,具體內容如下:

      TARGETNAME = Hello World
      TARGETTYPE = DRIVER
      TARGETPATH = OBJ
      SOURCES = Hello World.c

這是一個簡單的Sources文件例子。第1行用來指定驅動編譯后的驅動程序文件名稱;第2行用來指定生成的程序為驅動程序;第3行用來指定編譯后生成文件的存放路徑;最后1行用來指定要編譯的源碼文件。

大部分驅動程序開發人員都知道Sources文件的這種用法,卻很少用它編譯EXE或者DLL。具體論述參閱本書第2章。

這里一定不要寫聲明文件,否則將出現編譯錯誤,錯誤信息提示為“編譯目錄出現錯誤”,而沒有指明具體是什么錯誤。如果犯下這樣的錯誤,一時間很難想到錯誤原因。

如果想要查看編譯輸出的錯誤信息和警告信息,可以從工程目錄里尋找ERR類型文件與WRN類型文件。

打開WDK編譯環境,這個環境是以命令行形式提供的,按如下操作進行即可。

如圖1-1所示,單擊“開始”→“程序”→“Windows Driver Kits”→“WDK6001. 10082”→“Build Environments”→“Windows XP”→“Launch Windows XP x86 Checked Build Environment”,打開WDK編譯環境的命令行,通過DOS命令切換到工程文件的存放目錄,輸入“build”,然后按回車鍵,編譯結果如圖1-2所示。

圖1-1 開始菜單中的WDK

圖1-2 Hello World的編譯結果

編譯鏈接成功的SYS類型文件存放于當前目錄的\objfre_wxp_x86\i386下。

同理,我們用同樣的辦法編譯HelloDRIVER驅動程序。不過這里采用Check版本,選擇“Launch Windows XP x86 Checked Build Environment”。文件生成的目錄會稍微有一點差異,在當前目錄的\objchk_wxp_x86\i386下。

接下來的問題是如何加載驅動程序。文件夾中的HelloDRIVER.sys驅動程序是無法直接加載的,也就是說,無法像運行EXE程序一樣,直接雙擊運行。NT驅動需要有專門的程序來加載,這里使用DriverStudio軟件里的Monitor工具。

打開Monitor軟件,單擊“File”→“Open Driver”,選擇“HelloDRIVER.sys”,然后依次運行Start Driver和Stop Driver,可以看到如圖1-3所示的信息。

圖1-3 HelloDRIVER驅動的加載信息

同樣,我們也可以用這個工具加載Hello World驅動程序。如果使用Monitor工具,可能需要安裝DriverStudio軟件,或者將其單獨取出來。如果讀者嫌麻煩,可以從網上找自己喜歡的驅動加載工具。

這里的驅動程序不需要進行調試和測試,因為我們給出的是已經經過測試與調試之后運行正常的代碼。關于具體調試和測試的知識,我們會在以后的章節中詳細論述。

1.1.4 查看我們的驅動

在前面我們提到,DbgPrint函數打印的字符串無法通過控制臺查看,需要借助于其他的工具。

通過圖1-3可以知道,使用Monitor工具可以查看驅動程序中的打印信息。這里簡單介紹一下另外一個工具——DebugView軟件,它也可以用來查看在驅動程序中打印的調試信息。

打開DebugView軟件,然后利用Monitor加載Hello World程序,可以看到DebugView中顯示了如圖1-4所示的信息。

圖1-4 使用DebugView查看打印信息

程序的“Hello, Windows Driver!”赫然在目!

如果想查看自己的設備驅動,可以按照上面的步驟再次加載HelloDRIVER驅動程序,然后重啟計算機。

右鍵單擊“我的電腦”→選擇“屬性”→“硬件”選項卡→“設備管理器”,打開“設備管理器”窗口,選擇“查看”菜單下的“顯示隱藏的設備”命令,可以看到已經加載成功的HelloDRIVER驅動。

筆者的計算機中顯示的信息如圖1-5所示。

圖1-5 查看HelloDRIVER設備

這是通過操作系統自身看到的,當然,也可以通過其他軟件來查看,比如WinObj工具,如圖1-6所示。注意:本書里提到的工具,在第2章都會進行介紹。

圖1-6 用WinObj查看HelloDRIVER設備

1.2 虛擬環境

1.2.1 使用虛擬環境進行驅動開發

因為內核調試會凍結它所運行的操作系統,并存在各種形式的系統不穩定等弊端,在實際的驅動開發工作中,大多數開發人員只是在最終階段才會在實際的機器上進行開發調試,其余的大部分時間是使用虛擬環境進行驅動開發的。

使用虛擬環境進行開發,還可以節省很多資源,例如,對于沒有能力購買另外一臺PC的人,或者對于頻繁出差需要在便攜環境下進行內核調試的人來說,都提供了不錯的解決方案。

這里通常使用的兩類虛擬環境為VMware虛擬機和Virtual PC虛擬機。

1.2.2 使用VMware虛擬機

VMware Workstation是威睿公司推出的一款商業軟件產品,是桌面虛擬化解決方案的軟件代表,如圖1-7所示。全球不同規模的客戶都依賴它降低成本和運營費用,確保業務持續性以及加強安全性等。

圖1-7 WMware 7.0.1版本下的Windows XP操作系統

VMware虛擬機在7.0版本之后的版本中增加了對Windows 7的支持。所以這里的設置以VMware 7.0以及之后的版本為準,但是配置卻與之前的版本完全一樣。

新建虛擬機之后,單擊菜單項“VM”→“Settings”,會彈出如圖1-8所示的對話框。單擊“Add”按鈕,選擇“Serial Port”,再選擇“Use named pipe”,在第一個編輯框中輸入“\\.\pipe\com_1”,第二項選擇“The end is the server.”,第三項選擇“The other end is a virtual machine.”,然后單擊“OK”按鈕完成操作。

圖1-8 VMware虛擬機的配置對話框

注意:一定要選中“Device status”下的復選框“Connect at power on”。

1.2.3 目標機設置

虛擬機里安裝了想要調試的操作系統版本之后,還需要設置目標機為可調試的。在Windows Vista操作系統之后,目標機的可調試性設置與之前略有不同,下面一一介紹。

(1)Windows XP/Windows 2003的環境設置

打開系統盤,顯示受保護的操作系統文件并且顯示所有的文件和文件夾,找到boot.ini文件,去掉只讀屬性等,打開文件可以看到如下內容:

      [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

復制最后一行文字,拷貝到下一行,并在最后添加“/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

保存之后再次啟動,即可發現引導菜單中多了一項“調試引導的系統”。

(2)Windows Vista/ Windows 7的環境設置

Windows Vista版本之前,Windows操作系統使用boot.ini進行引導,所以可以通過修改此文件進行系統的可調試性設置,但是Windows Vista之后的操作系統就無法再用法進行可調試性設置了。

在已經出版的《天書夜讀——從匯編語言到Windows內核編程》和《寒江獨釣——Windows內核安全編程》兩本書中,關于Windows Vista的可調試性設置一直是使用bcdedit命令。這種方式比較煩瑣,但是在系統啟動時可以有選擇性地進入,或者為普通啟動,或者為調試啟動,無須再重新進行選項設置。

對于這種方式的設置,讀者可以參考前兩本書,這里不再復述。這里講述一種較為簡單的設置方式,但是如果需要普通啟動,則需要重新進行選項設置。

單擊“開始”菜單→“所有程序”→“附件”,打開“命令提示符”,輸入“msconfig”命令,打開“系統配置”對話框;或者使用“WIN+R”組合鍵打開“運行”對話框,輸入“msconfig”命令,打開“系統配置”對話框。選擇“引導”選項卡,單擊“高級選項”按鈕,如圖1-9所示。

圖1-9 Windows Vista/Windows 7的可調試性設置對話框

選中“調試”復選框,然后選擇“調試端口”等。這里的設置與虛擬機的設置以及調試器的設置一定要一致,只有三者的設置一致,才能正常連接調試。這里采用的選項與之前的設置一樣:

      -b -k com:port=\\.\pipe\com_1,baud=115200,pipe

將WinDBG連接至虛擬機進行調試,如圖1-10所示。

運行VMware虛擬機里設置成功的調試操作系統,然后打開我們創建的WinDBG快捷方式進行連接。WinDBG調試器的快捷鍵以及菜單命令與Visual Studio系列軟件一致,包括設置斷點按F9鍵、運行按F5鍵等,讀者可以看一下調試菜單中的菜單項。

圖1-10是本書第一個例子Hello World驅動的調試截圖,其中斷在打印輸出日志的語句上。

1.2.4 Virtual PC虛擬機

Virtual PC是微軟為了爭奪虛擬化市場而推出的一款軟件產品,和VMware一樣也十分優秀。它也可以像VMware一樣,被用來進行驅動的雙機調試。由于這里已經詳細介紹了VMware虛擬機的使用,所以Virtual PC就不做過多的介紹了,它的設置與VMware虛擬機一樣,并不復雜,讀者可以自己嘗試一下。

1.3 小結

本章以一個簡單的Hello World驅動程序為例,講解了驅動編程中最基本的一些要素。本章內容較為基礎,對于初學者是不錯的入門課程;而對于熟練的讀者,算是讓大家在學習更豐富的內容之前,熱熱身吧。

主站蜘蛛池模板: 武功县| 启东市| 平潭县| 洪雅县| 宣武区| 乡宁县| 砀山县| 南靖县| 阳曲县| 日照市| 商都县| 永胜县| 兴安县| 科尔| 兴安县| 江津市| 错那县| 杭州市| 崇仁县| 黔西| 大宁县| 斗六市| 南雄市| 德格县| 昭觉县| 怀柔区| 博罗县| 精河县| 政和县| 栾川县| 贵南县| 青铜峡市| 拜泉县| 彭州市| 苍溪县| 上思县| 龙岩市| 旬邑县| 方山县| 濮阳市| 荔浦县|