一、導言
在四個月漫長的征戰後,終於在國慶節的今天完成了基礎篇系列。本文寫作的初衷很簡單,就是給平靜的池水中加入一點波瀾,如果大家在閱讀後感受到一點生氣,激起探索未知的熱情,筆者也會感到由衷的喜悅。
本系列文章為業余編程愛好者而寫,僅僅作為初學者的一個借鑒,真正的精華存在於參考資料*中。知識的積累將經歷從薄到厚,再從厚到薄的反復過程,為了打下牢固的基礎,請讀者務必在閱讀本文的基礎上花費必要的時間完成參考資料。
二、基本架構
為了保證性能,匯編與c成為操作系統編寫的首選語言,Windows家族的前輩都不例外,唯獨Vista那龐大的身軀,讓人不禁疑惑微軟究竟如何才能誕下比恐龍還大的怪物。與此相反,WinPE作為維護型操作系統可以被一個32M的U盤所容納。不管外觀上的諸多差異,功能上的強弱區別,我們所關心的是它們的共性——即操作系統的內核。與操作系統密不可分的文件系統,就成為我們研究內核的一條途徑。
Windows采用了基於對象模型(object-based model)的設計方式,各功能劃分為不同的組件,兩幅常見的架構圖如下:
我們把入口選定為Win32子系統(Win32 Subsystem),它是我們接觸最多,也最熟悉的一個子系統。硬件抽象層(HAL)及其以下部分目前不在我們關注的焦點內,我們將精力集中在系統執行層和核心層,它們具體的功能請參看資料2的第一章。
內核模式下除了屈指可數的幾個函數,一切都將是全新的——全新的思想、概念、模型、結構、函數,跨越的幅度不亞於從c語言編程遷移到c++語言,用c語言的思維來學習c++必然會產生阻礙。令人欣慰的是,這裡沒有特色之流的術語,絕大部分內容都符合人的記憶規律。你將很快掌握設備對象、IRP、IO堆棧等基礎結構,同時你還發現以前很難記憶的PEB、TEB、_ETHREAD等一系列無詳細文檔甚至無文檔結構都已有跡可尋,如果你足夠勤奮,甚至會在一年左右的時間後就可以閱讀天書般的防火牆源碼。
讓我們先來完成必要的准備工作吧。
三、調試環境的構建
在用戶模式下編程,除了編譯器幾乎可以不需要其他輔助工具。想查看輸出?直接ShowMessage即可,內核模式下,如果不希望摔得筋折骨斷後才有所醒悟,你該在入門前就選好幾件寶物——注意它既不是聞西同志的西瓜刀,更不是單車鏈。
Microsoft Visual Studio 200x + Windows IFS Kit and DDK xxx + Compuware DriverStudio 3.x + VAssistX xxx + VMware
如果記憶力超強的讀者,也可以選擇C++Builder、Delphi + 插件的形式,有些站點正在致力於推廣這方面的技術,對於新手而言,還是用原裝貨為上策。Windows IFS Kit and DDK是收費軟件,電驢上有試用版,依照資料6的說明完成安裝,注意選上xp和2000部分;Compuware公司已經改行不做DriverStudio,幸好出家前支持了vs2005,安裝完成後,根據資料5打上VisualStudio 2005 Integration fix補丁;VAssistX有試用限制,不巧有人發現不修改代碼而使用trial-reset_32清Armadillo殼,可以恢復試用時間,安全而可靠。VassistX增強了編譯器的語法提示、代碼搜索功能,大大提高了千行規模子程序的閱讀與書寫效率。VMware虛擬機用於減少重啟的煩惱,提高調試的效率。
經過一陣忙碌後,一個已經設置好的編譯器出現了:
VMware虛擬機下的Debugging Tools for Windows(即WinDbg)
讀者也可使用VirtualPC虛擬機。在虛擬機上安裝雙系統(Windows 2000,2003,xp,pe等),可方便刪除造成系統啟動崩潰的“不良”驅動程序。為了方便,我們把虛擬機上的操作系統稱為遠程機。首先我們在本地機上安裝WinDbg,運行後進行如下設置:
設置符號文件搜索目錄,菜單File-Symbol File Path…-填入SRV*c:\symbols*http://msdl.microsoft.com/download/symbols串,它表示使用微軟的文件符號服務器。如果你的電腦未聯網,則只能通過其他方式獲取微軟站點上的符號文件,采用本地符號目錄,調試時往往會出現一些版本不匹配的問題。符號文件非常重要,如果WinDbg找不到合適的符號文件,將無法解析代碼裡的數據結構。文件符號服務器上的符號文件用於解析操作系統文件(dll, exe等),而你的源代碼將編譯產生驅動程序的符號文件。
菜單File-Open Executable…-隨意選擇一個可執行文件進行本地調試,WinDbg將搜索是否已經存在必須的符號文件,如果無,則通過互聯網連接文件符號服務器下載必要的符號文件,保存的目錄是c:\symbols。之所以進行這一步,是幫助初學者在連接遠程機出現長時間的延時時,確定不是因為下載的原因造成的。
運行虛擬機,打開遠程機系統根目錄下的boot.ini文件(可能是隱藏文件),為操作系統復制一新行,並在後添加/debug /debugport=com1 /baudrate=115200,指明連接的方式(串口連接)和速率(115200比特率)。如圖是安裝了兩操作系統的boot.ini文件:
重新運行遠程機,啟動界面如:
為虛擬機添加虛擬串口:關閉虛擬機,點擊配置選項Eidt virtual machine settings,選擇命名管道方式配置虛擬串口com1:
測試虛擬串口:運行遠程機,選擇debug模式進入,此時將比正常啟動多出近30秒的黑屏時間,如果你的硬盤馬力強勁,你會聽到運轉的沙沙聲^_^,接下來的情況將和正常啟動時相同。在本地機上運行WinDbg,點擊菜單File-Kenerl Debug…,首次連接時按左下圖進行配置,確定後WinDbg將開始嘗試遠程連接。為了加快連接速度,不妨多按幾次重新同步的快捷鍵Ctrl-Alt-R,如果上述設置無誤,則虛擬串口標志將不停閃動(中下圖最右),連接成功後,WinDbg出現類似信息(右圖):
調試器連接了遠程機後,就獲得了遠程機的控制權,遠程機則處於停機狀態,此時可以查看內核的情況,如果希望它繼續運行,只需要在調試器的命令行窗口輸入g命令,則調試器歸還控制權,遠程機將繼續運行。
在http://www.osronline.com/index.cfm下載您中意的工具。
特別值得一提的幾個小工具是:Dbgview,Driver Installer、IrpTracker、DriverMonitor和EzDriverInstaller,後兩個工具為Driver Studio附帶。
練習WinDbg的使用,嘗試用(二)- 二)的方式單步跟蹤一個簡單的可執行程序,學習如何設置斷點。
最後一步是個難點,請根據資料3努力的完成這個工作。
四、驅動程序的編譯
sources、makefiles和dirs文件
使用DriverStudio的一個目的是,借用vs強大的IDE編寫代碼,而調用DDK的編譯器進行編譯。早期的vs編譯器也支持驅動代碼的編譯,但隨著DDK的發展,現在它已經完全脫離了vs,兩者不再保證編譯代碼的一致性。為了保證正確性,我們需要使用DDK的編譯器來完成編譯的工作。
基於效率的原因,我們還需要學習使用sources文件,它支持眾多的編譯指令,可以實現復雜的編譯配置。驅動編程中,makfiles文件是一個無關大局的配角,一般無須改動,盡管它是必須的。如果源文件非常復雜,分布於多個目錄,包含了多個工程,則可編寫dirs文件。編譯器通過dirs文件,找到各目錄下的sources文件,逐一完成編譯。下面我們將介紹sources文件的基本寫法,更多指令的詳細用法可查閱資料6或Msdn。
sources是一個無後綴名的文本文件,示例如下:
TARGETNAME=pnpevent
TARGETPATH=obj
TARGETTYPE=DRIVER
USE_PDB=1
INCLUDES=..\..\..\generic
#TARGETLIBS=..\..\..\generic\obj$(BUILD_ALT_DIR)\$(CPU)\generic.lib \
TARGETLIBS=..\..\..\generic\obj$(BUILD_ALT_DIR)\i386\generic.lib \
SOURCES= DriverEntry.cpp \
stddcls.cpp \
control.cpp \
readwrite.cpp \
driver.rc
#號表示注釋,而*號表示目標平台類型,編譯時會被Build工具替換成相應的串,如替換成“I386”。
TARGETNAME指明驅動程序名,TARGETPATH指明生成的路徑(如果沒有其他指令,編譯器生成的是以obj打頭的路徑,如objchk_wxp_x86\i386)。TARGETTYPE指明代碼類型,可創建普通驅動sys、內核模式的dll、庫文件lib、用戶模式程序exe、用戶模式的dll。TARGETLIBS指明需要引用的外部lib庫文件。
INCLUDES指明需要引用的頭文件目錄,SOURCES指明本次需要編譯的文件,文件名後的\表示下行仍為源文件。
還可以使用C_DEFINES和USER_C_FLAGS來定義宏。對於若干年前的驅動代碼,編譯器可能會提示無法識別某些廢棄的編譯指令,視情況手工刪除或更改成等價指令即可。
編譯DriverStudio的VdwLibs2005.sln工程
該工程位於Compuware\DriverStudio\DriverWorks\source目錄下,vs2005編譯器加強了語法的規范性,不修改源文件是無法完成編譯的。為此,請參考資料6,按照圖三、(一)的方法直接調用DDK進行編譯。
閱讀資料1第三章的錯誤處理小節,編譯附帶的源代碼Chap3\SEHTEST,學會使用DriverMonitor來加載驅動、Dbgview來查看該代碼的輸出信息。
五、代碼書寫規范與編譯警告的處理
書寫規范可參考本站的《華為編程規范和范例》,強調一點,驅動編程中使用了眾多的宏,為避免宏擴展帶來的潛在問題,使用if,while,for等時,其下的子句都須以{}擴之。縮進不規范的代碼可通過Alt+F8修正。
if (x)
return y; // 不推薦
if (x)
{ // 標准格式
return y;
} // 標准格式
調高編譯器的告警級別,使任何警告都被當作錯誤而停止編譯,這種嚴謹的態度有助於減少潛在的錯誤。建議在sources文件裡至少設定W3級別:
MSC_WARNING_LEVEL=-W3
六、學習的誤區
缺乏必要的計算機科學技術的理論基礎,是非專業的編程愛好者遇到的一個最大的問題。在這個問題的處理上,如果采取回避的態度,相信很快就將潛力用盡,再也沒有上升的空間。有的人編了幾年程序,卻還在害怕閱讀其他語言書寫的代碼,是這個問題的一個表現。如果看到了這個問題,並希望解決它,也需要采取正確的方法。貪多求全,希望先把基礎理論一口吞下再來實踐編程的思想也是錯誤的,因為理論僅僅是實踐的一個抽象,兩者必然存在差別,大腦也需要在實踐中建立起可靠的影像,而不是“大概,也許,基本上”這樣的一種客套話,這也是能力的體現。
我們的建議是,根據自身的實際情況,在每一個發展階段采取先學習理論再實踐,或先實踐再學習相應理論的方式,螺旋般的向上,速度雖然稍慢,卻能為自己積累下發展的潛力。一個實際的例子是,計算機系的學生在入校後的前兩年,計算機的實踐水平往往比不上別的院系的學生,一旦他們發現實踐上的很多問題可以通過已掌握的基礎理論來解釋之後,兩者的前進速度就有了質的差別。高手的一個特點是哪怕只給他透露了一個關鍵的單詞,你所保留的秘密很可能就被完全破解了,而對於菜鳥而言,即使把金山搬到門口,也還是身在福中不知福。
在基礎篇中,我們推薦了大量的示例代碼,這些代碼可以幫助讀者更好的理解基礎的理論。
完成了基礎篇,您仍然處於文件系統驅動編程的邊緣,對它的理解甚至比不上采用了速成大法學習的其他人,您的收獲則是奇怪為什麼自己先前寫了如此多的垃圾代碼?您將大大擴寬自己的眼界,看到控件之外的許多更有意義的知識。
唯一沒辦法幫助大家的事情,就是外文閱讀水平了。我們只能鼓勵還僅僅掌握著1.5國語言的朋友,人手一冊金山詞霸,首頁google在線翻譯,每日半篇RFC,不出兩月,拿下Msdn。當然如果你能在Msn上結交到海外的留學生,他們對您的幫助將會更大。
編程能力以2萬行作為分界點,還達不到這個要求的朋友,請努力吧。
華老師有句名言,“天才在於勤奮,聰明在於積累”,技巧性的東西固然可以幫助您提高效率,但始終無法代替基礎,如果您想在科技領域真正有所成就,就請遵循這句話。
七、結語
基礎篇將僅僅講述一些與WDM(Windows Driver Model,KMDF相當於WDM的封裝類,Vista已自帶)驅動編程有所關聯的基礎知識,限於能力,並不追求系統性和完整性。資料1作為大師級的著作,是筆者推薦的主要學習資料,本文中大部分驅動示例來源於它的配套代碼。
留給初學者的一個問題是,究竟VMware的虛擬串口支持多高的通信速率呢?
本文作為下場之前的熱身,參考完成時間為一至兩個星期。