導讀:
版權聲明:轉載時請以超鏈接形式標明文章原始出處和作者信息及本聲明
http://chenshine.blogbus.com/logs/4414354.html
剛 進入計算機相關專業領域時,大家最先用過的調試器大多會是Turbo C。它雖然古老但用過的人卻很多,然而嚴格的講,Turbo C是一個集成開發環境,雖然擁有獨立的編譯器,鏈接器,卻沒有獨立的調試器,這和Visual Studio一樣。如果你做過DOS,早期Windows下的匯編,也許你會對Debug,CodeView等調試工具熟悉,但這些工具太老 了,Debug甚至不能調試32位程序,介紹它們與這篇文章的主旨不符,如果你對它們感興趣,可以去查閱相關資料。本文主要是介紹與調試技術相關的理論知 識以及常用調試器的使用,Windows設備驅動程序與內核的調試等。具體的講解方法是理論結合實踐,并且是站在給新手看的角度,循序漸進的,用一個一個 調試會話向你展示每個重要命令的使用。
目錄
1。調試器
2。調試信息
3。用戶模式程序的調試
4。驅動程序與內核的調試
1 調試器1。1 概覽調 試器,與編譯器,鏈接器一樣,都屬于基礎軟件,它們在制作上都有很大的難度,但盡管如此,現實中還是有不少專業級的調試器,微軟官方的就有 cdb,ntsd,WinDbg,kd等,還有SoftIce,OllyDbg等來自于其它公司的優秀調試器。本文不可能對這幾個調試器一一介紹,一個是 限于篇幅,另一個是上面列舉的這幾個調試器無論是哪一個都需要你花很長的時間去完全掌握(在市面上有很多書籍甚至專門講解某一個調試器的使用,比如 SoftIce)。雖然本文不會講解SoftIce的詳細使用,但我還是要對它進行簡短的介紹,因為它太有名了,甚至比Windows出生的早。
1。2 SoftIceSoftIce 是NuMega公司生產的調試器,產于80年代后期,直到今天為止,這個軟件已經變革過好幾次了,因為處理器的體系結構在更新,操作系統也在更新,所以調 試器也必須相應的更新。它之所以有名,那是與歷史有關的,在Intel推出386 Cpu的時候,NuMega立即讓SoftIce支持了386 Cpu的新特性,同時吸引了大量黑客的使用,使它在黑客界產生了一定的地位(功能強大的調試器可以對軟件的保護機制進行破解,如突破序列號之類的)。在 Windows推出時,所有的調試器都不適用這種新的系統軟件體系結構了,唯一能用的就是微軟自己生產的調試器,卻很拙劣,這時NuMega又對 SoftIce進行了變革,不僅讓它可以在新的系統上很好的工作,甚至還可以調試Windows的內核,這是當時是不敢想象的。也正是因為它很好的性能, 卓越的功能,它成為了黑客們的寵兒,并且培養了幾代的黑客。讀完本文后,你自己應該有能力去探索它,或許你也可以在這幾款調試器里找到適合自己的。
1。3 微軟的調試工具本 文主要介紹的是微軟官方的調試器,并且MS微軟也最積極,調試工具包一直在更新中。在微軟的調試器里,大家可能最為熟悉Visual Studio Debugger,就是你在Visual C++里使用的那個,它并不是一個單獨可運行的程序,而是內嵌在Visual Studio中的,它的功能相對于上面幾個調試器來說要弱很多,這里并不會對它進行介紹,如果你想全方面的了解它的話,你可以去參考MSDN,那里專門有 幾章講解它,是一份很好的文檔化的資源(而且還是中文的)。其它的微軟調試工具cdb,ntsd,WinDbg,kd都在微軟的一個安裝包里,被稱作
Debugging Tools for Windows(10 多M的大小,請通過這個鏈接下載并安裝,一會兒就要用到)。這些調試器在下文中會分別介紹,這里先做一個簡單的區別,cdb與ntsd幾乎是一樣的程序, 唯一的不同是cdb是一個CUI程序,即Console程序,而ntsd是GUI程序,但ntsd并沒有產生窗口,而是又分配了一個Console窗口, 這個Console窗口就相當于是cdb。它與真正的cdb執行完全一樣的功能(官方如是說,然而實際上還有一些個不同)。在命令行中啟動它們時下面兩句 命令有一樣的效果(C:/Progs/Debug是調試工具包的安裝位置):
C:/Progs/Debug/>start cdb
C:/Progs/Debug/>ntsd
cdb 與ntsd是用來調試用戶模式應用程序的。kd調試器也是一個命令行程序,不過正如其名kernel debugger所描述的一樣,它是內核調試器的,是驅動程序開發者,系統Hacker的最愛。WinDbg是一個稱職的GUI程序,它有菜單,有工具 欄,還有多個子窗口,可以分別顯示源代碼,調用棧,命令等,它既可以調試ring 0程序,也可以調試ring 3程序,其實它只是一個殼而已,當調試ring 3級程序時它實際上是用cdb/ntsd,而當調試ring 0級程序時是用kd。WinDbg的到來吸引了很多的人使用,你也將會發現,它確實是一款優秀的調試器。
2 調試信息
調試器之所以能夠工作,完全是依賴于編譯和鏈接程序時所生成的調試信息,當然調試信息是具有一定的格式的。
2。1 格式微 軟的調試信息格式經過了幾代變化,最終形成了Program DataBase這種格式,并且這種格式還在進行版本上的更新,VS.Net所用的新的Program DataBase版本與Visual C++ 6.0所用的老的版本是不兼容的,并且你也可以用編譯器和鏈接器明確指定你想要生成的調試信息格式,這一點下文中有闡述。
2。2 內容關 于調試信息我們還必須知道兩件事,一是調試信息包括哪些內容,二是調試信息儲存在什么地方。其實調試信息所應該包括的內容正是調試信息格式變化的原因,從 COFF格式,到CodeView格式,到Program DataBase格式,調試信息變得越來越豐富了,并且是只多不少。Program DataBase格式的調試信息中主要包括了所有全局函數,static 函數,全局變量,static變量,局部變量,函數形參的名字及其位置,源代碼與可執行文件中指令的映射信息,每個函數與變量的類型,以及FPO信息,編 輯和繼續信息。編輯與繼續信息主要用于在Visual Studio中調試時,可以在調試的同時編輯源代碼,并在接下來的調試中得到體現。Program DataBase格式的調試信息包含了這么多的內容,所以用這種調試信息來調試程序時,你將能夠得到更多,更準確,更深入的反饋。
2。3 存儲位置調 試信息的存儲位置是與其格式相關的,Program DataBase格式的調試信息存儲在一個單獨的文件里,擴展名為pdb。像以前的CodeView格式的調試信息即可存儲在單獨的文件里,又可存儲在編 譯時所生成的obj文件中。知道了這些知識后,我們就可以正確地生成調試信息了。在后面的內核調試中我們還要繼續談到它。
3 用戶模式程序的調試根 據上面的講述,我們可以用cdb或WinDbg來進行ring 3程序的調試,這里先講解cdb。cdb允許你啟動某個被調試程序(以下稱debugee)的一個新的實例來進行調試,即先創建cdb,然后cdb再把你 所指定的程序創建為一個新進程進而對其調試,也允許debugee在已經運行的情況下被cdb attach上。cdb還可以對Crash Dump(程序崩潰時的內存Copy,下文將會說明)進行調試,只需要加上-z選項,后面再加上Crash Dump的文件名即可。這幾種調試方式下面將會一一講述。
3。1 調試前的準備
第 2節中對調試信息進行了理論上的說明,接下來我們來看看在實際中應該如何操作。首先我們的程序必須要經過編譯,鏈接,并且在編譯和鏈接時還要指定一些選項 以正確地生成調試信息。這里所使用的編譯器是cl.exe,鏈接器是link.exe,都是微軟官方的,Visual Studio就是用的這兩個(CTRL+F5就是順序調用cl和link的快捷鍵),如果你安裝了Visual C++或Visual Studio,就會有它們,另外一種選擇是安裝SDK,也能夠得到它們。本文所用的cl.exe和link.exe是Visual C++ 6.0的版本。若要生成調試信息,編譯時我們需要加上的選項應是/Zi,而鏈接時則要加上/debug。在下面的調試中,我們將用C語言來寫程序,所以你 有必要知道用C語言寫出來的程序與用匯編寫出來的有什么不同。首先,每個程序都有一個入口函數,它的地址需要在鏈接時指定,并被鏈接器放到最終可執行文件 的頭部,通常用匯編語言寫的程序,有選擇的,你可以在源代碼中指定入口函數,而用C語言寫的程序則需要在鏈接的時候來指定入口函數,說到這你可能不以為 然,“C語言寫的程序的入口函數不就是main嗎?“。實際上,控制臺C程序的入口函數默認情況下是C運行時的啟動函數:mainCRTStartup。 然后由這個函數調用你寫的main函數,所以可執行文件的頭部存放的入口地址是mainCRTStartup的地址,而不是你寫的main函數地址。 mainCRTStartup主要做了一些為了正確執行C/C++程序的初始化工作。它已經由微軟寫好了,由鏈接器自動鏈接到可執行文件中。如果你在程序 中不使用C語言的庫以及一些ANSI規定的全局變量,只是單純地使用C語言這種語法,那么你也可以不鏈接mainCRTStartup,直接指定你自己寫 的某個函數為入口函數,這也是我們在下文中所使用的方法,具體的做法是在鏈接時加入如下選項:/subsystem:console /entry:你的入口函數名稱。這個入口函數應該是不帶參數的。現在我們來總結一下上面所講的內容,假定你的源程序名為exam.c,你想指定的入口函 數為main,那么應該如下生成可執行程序:
C:/Pro/>cl /Zi exam.c /link /debug /subsystem:console /entry:main
另 外,就算你不鏈接mainCRTStartup(它會調用很多的Win32 API),也不在源程序中調用任何的系統函數。那么系統還是會把一些動態鏈接庫,如kernel32.dll,Ntdll.dll等動態鏈接到你的程序所 對應的進程里,即把它們映射到你的程序對應的進程地址空間里。這是因為在你所指定的入口函數運行之前,還會有一系列的 Kernel32.dll,Ntdll.dll中的函數要運行。即在用戶空間中你指定的入口函數,例如上面的main,根本不是第一個運行的函數。那么那 些函數是做什么的呢,通過大量的反匯編和調試能夠推斷出它們是做一些進程,線程在用戶空間的初始化,設置一些異常楨等。在下面的調試中我們將會用一些手段 來研究它們。這些函數是操作系統的一部分,因些我們必須從官方網站下載它的符號文件,當然不下載也行,那么你將面臨的會是一大堆的警告。說到下載,沒有比 這更簡單的了,你不需要預先的下載,只需要添加一個環境變量_NT_SYMBOL_PATH即可,而真正的下載工作由調試器來做,這個環境變量的值與已存 在的PATH環境變量類似,是由分號分隔的一系列的路徑組成。這些路徑應該包括你的調試信息文件(pdb文件)所在地,如前面的C:/Pro/。如果要下 載系統文件如Kernel32.dll,Ntdll.dll的pdb文件,你僅僅需要再加一個這樣的路徑:SRV*D:/Symbols*http: //msdl.microsoft.com/download/symbols,其中D:/Symbols是可更換的,你可以換成任何一個其它的路徑,這 個D:/Symbls是用來存放從后面的URL路徑所下載下來的系統調試信息文件的。其實你也可以預先下載系統調試信息文件到一個路徑里,然后在 _NT_SYMBOL_PATH里指定那個目錄,但這樣一來有兩個缺點,一是你必須得進行版本的匹配,做這件事簡直太乏味了,二是你一次需要把整個操作系 統的調試信息文件都下載下來,可能會需要1G的空間。而通過前面設置環境變量的方式,調試器會根據需要只下載這次調試會話所需要的調試信息文件,并且它會 自動給你匹配版本。由于我們將要編寫的源代碼都在C:/Pro/文件夾中,生成的pdb文件也在C:/Pro/中,所以我們的 _NT_SYMBOL_PATH環境變量應該如下設置,假設你希望系統pdb文件下載到D:/Symbols:
C:/Pro/>set _NT_SYMBOL_PATH=SRV*D:/Symbols*http://msdl.microsoft.com/download/symbols;C:/Pro/
最后,我們需要在命令行中啟動cdb調試器,但當前目錄通常是源代碼文件夾C:/Pro/,為了避免如下冗余的鍵入:
C:/Pro/>C:/Progs/Debug/cdb example1.exe
應該為cdb,ntsd,WinDbg設置PATH環境變量:
C:/Pro/>set PATH=%PATH%;C:/Progs/Debug/
這里的C:/Progs/Debug是Debugging Tools for Windows的安裝目錄。以后就可以這樣來調試了:
C:/Pro/>cdb example1.exe
3。2 cdb啟動新實例的調試3。2。1 編寫一源程序,啟動cdb首先我編寫了下面的C程序,先用這個程序來介紹一些基本的命令,并且來驗證一下調試信息中是否確切包含了上面所說的那些內容:
[example1.c]int gVar;
static int sgVar;
int Inc(int Param);
static int sDec(int sParam);
void main(void)
{
int lVar;
static int slVar;
lVar = 3;
slVar = 4;
gVar = Inc(lVar);
sgVar = sDec(slVar);
}
int Inc(int Param)
{
return (Param+1);
}
int sDec(int sParam)
{
return (sParam-1);
}
對這個程序用上面所講的方法編譯鏈接:
C:/Pro/>cl /Zi example1.c /link /debug /subsystem:console /entry:main
接下來啟動cdb調試器:
C:/Pro/>cdb example1.exe
Microsoft (R) Windows Debugger
Copyright (c) Microsoft Corporation. All rights reserved.
CommandLine: example1.exe
Symbol search path is: SRV*D:/Symbols*http://msdl.microsoft.com/download/symbols
Executable search path is:
ModLoad: 00400000 00406000 example1.exe
ModLoad: 7c920000 7c9b4000 ntdll.dll
ModLoad: 7c800000 7c91c000 C:/WINDOWS/system32/kernel32.dll
(c94.c24): Break instruction exception - code 80000003 (first chance)
eax=00251eb4 ebx=7ffd4000 ecx=00000001 edx=00000002 esi=00251f48 edi=00251eb4
eip=7c921230 esp=0012fb20 ebp=0012fc94 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ntdll!DbgBreakPoint:
7c921230 cc int 3
0:000>
cdb 輸出了此時主線程的上下文信息(這個例子中也只有這一個線程)以及程序斷點信息后便會進入一個新的提示符:0:000>。以后我們會一直工作在這種 類型的提示符上,下面在這個提示符上輸入一個命令來加載所有的調試信息文件,此時可能會慢一些,因為要下載系統DLL的pdb文件,所以要確保你能上網。
0:000>.reload /f
看到這個以"."號做為前綴的命令,可能你會覺得怪怪的,但實際上還有用"!"號做前綴的呢,用"."號做前綴的命令表示元命令,而用"!“號做前綴的命令表示擴展命令,這里只做一個簡單的區分即可。
其 實這時我們所要調試的程序已經運行起來了,不過停在了某處,主線程處于凍結狀態。這一點和Linux的Gnu調試器GDB不一樣,對于GDB調試器,你先 要設置一個斷點,然后再鍵入運行命令(如果自己不手動設置一個斷點,那么程序將會一直運行直到結束,你根本沒有調試的機會。),這時程序才處于運行狀態。 沒有運行和運行之后處于凍結狀態是兩個完全不同的概念。那么我們這個example1.exe停在了什么地方呢。下面我將介紹一個很重要的命令,用它我們 可以來研究這方面的問題。
3。2。2 查看堆棧及用戶空間的初始化用kb命令可以來查看堆棧,它顯示棧上一些重要的信息。
0:000>kb
ChildEBP RetAddr Args to Child
0012fb1c 7c95edc0 7ffdf000 7ffd4000 00000000 ntdll!DbgBreakPoint
0012fc94 7c941639 0012fd30 7c920000 0012fce0 ntdll!LdrpInitializeProcess+0xffa
0012fd1c 7c92eac7 0012fd30 7c920000 00000000 ntdll!_LdrpInitialize+0x183
00000000 00000000 00000000 00000000 00000000 ntdll!KiUserApcDispatcher+0x7
這個命令所列出來的信息后面還要詳細介紹,這里先關注一下函數的調用關系。從上面的列表可以看到有四個函數,這四個函數都是ntdll模塊里的,從下至上函數依次被調用。要注意這是當前線程的用戶棧里所有的東西,就四個函數,
KiUserApcDispatcher是第一個,上面已經提到過,在你指定的入口函數執行之前還有很多的函數被調用,
KiUserApcDispatcher是第一個被調用的,接下來它再調用
_LdrpInitialize,
_LdrpInitialize調用
LdrpInitializeProcess,再調用
DbgBreakPoint。至于是誰調用的
KiUserApcDispatcher,我現在只能簡單的告訴你是操作系統調度例程調度的結果。深入地探討它就離開本文的主題了。現在我們可以肯定的是程序停在了
DbgBreakPoint里,因為它是棧上最后一個函數。從cdb調試器的輸出可以看到example1.exe停在了
DbgBreakPoint函數里的
int 3語句上,
int 3是一個異常,它將會通知操作系統掛起這個線程,并且通知調試器,這是操作系統對調試的一個支持。其實
DbgBreakPoint函數只有一條語句,那就是
int 3。敏感點的人可能會想到,這樣一來,所有的程序,無論是否被調試,在運行的時候最初都會執行
DbgBreakPoint函數了,這也太傻了吧?情況并非如此,當我們鍵入cdb example1.exe時,cdb在啟動example1.exe的時候會加一個特殊的標記,在這種情況下
LdrpInitializeProcess才會調用
DbgBreakPoint,這又是操作系統對調試的一個支持。
本文轉自
http://chenshine.blogbus.com/logs/4414354.html