符號文件是一種輔助數據,它包含了對應用程序代碼的一些標注信息,這些信息在調試過程中非常有用。如果沒有輔助數據,那么能獲得的信息就只有應用程序的二進制文件。二進制文件很難調試,因為無法看到代碼中的函數名、數據結構名等。這正是符號文件能顯示的。符號文件的擴展名通常是pdb,調試器能夠很好地解析這種文件格式。
編譯器和鏈接器在創建二進制鏡像文件(諸如exe、dll、sys)時,伴生的后綴名為.dbg、.sym或.pdb的包含鏡像文件編譯、鏈接過程中生成的符號信息的文件稱為符號文件。具體來說,符號信息包括如下內容:
- 全局變量;
- 局部變量;
- 函數;
- 變量、結構體類型定義;
源文件路徑以及每個符號對應於源文件中的行號,這是進行源碼級別調試的基礎。
有這么多的信息包含在符號文件中,使得符號文件通常要比二進制文件(PE格式文件)本身要大很多。只要正確設置了符號路徑,使得調試器能夠將調試目標、符號文件以及源碼文件一一對應起來,才能夠最好地發揮調試器的強大功用。
符號文件有兩種類型:私有符號文件和公有符號文件。
- 私有符號文件:是大多數開發人員在日常工作中使用的符號文件,其中包含了調試會話中需要的所有符號信息。
- 公有符號文件:只是有選擇地包含一些符號信息。
符號信息隸屬於指定的模塊,所以只有調試器需要用到某個模塊時,它的符號信息才有被加載和分析的必要。
要在調試器中使用符號,我們必須首先告訴調試器這些符號文件的位置,也就是設置符號路徑。符號路徑可以是本地文件夾路徑、可訪問的UNC路徑、或者是符號服務器路徑。
符號服務器:在調試過程中,需要涉及成千上萬個符號文件,以及同一個符號文件存在不同平台下的不同符號文件版本的時候。一一手動設置符號路徑肯定是不現實的,於是引入了符號服務器的概念。符號服務器有一套命名規則,使得調試軟件能夠正確找到需要的符號文件。一般來說,符號服務器比較大,都是共用的,放在遠程主機上。為了降低網絡訪問的成本,又引入了符號緩存的概念,即將從服務器上下載到的符號文件,保存在本地緩存中,以后調試器需要符號文件的時候,先從緩存中尋找,找不到的時候再到服務器上下載。
1、設置符號路徑
設置符號路徑的語法如下:
.sympath [+] [路徑]
如要覆蓋原來的路徑設置,使用新路徑即可:
.sympath <新路徑>
要在原有路徑的基礎上添加一個新路徑,可使用:
.sympath+ <新增路徑>
如果不帶參數,那么輸出是當前設置的符號路徑:
0:000> .sympath
Symbol search path is: <empty> //尚未設置符號路徑
假如在調試時,我知道需要的符號文件位於一下文件夾"D:\MyPdb“。
0:000> .sympath D:\MyPdb //覆蓋原有符號路徑
Symbol search path is: D:\MyPdb
Expanded Symbol search path is: d:\mypdb
此時調試器將記錄上面新的符號路徑,但並不會從這個路徑中加載任何符號,要指示調試器加載符號,可以使用元命令reload。這個命令能枚舉出進程地址空間中所有已加載的模塊,並且嘗試找出與各個模塊相關的符號文件。
0:000> .reload Reloading current modules .....
如果調試器在指定目錄無法找到文件,那么它會輸出錯誤提示:
*** ERROR:Symbol file could not be found. Defaulted to export symbols for xxx.dll
當沒有設置本地緩存路徑時,那么調試器將使用調試軟件的安裝路徑下的sym文件夾。
要特別注意的是,使用.sympath改變或新增符號路徑后,符號文件並不會自動更新,應再執行.reload命令以更新之。
延遲加載使得模塊的符號表,只在第一次真正使用的時候才被加載。這加快了程序啟動,不用在一開始耗費大量時間加載全部的符號文件。
使用.symopt +4和.symopt -4 來開啟或關閉延遲加載設置。
在已經啟動了延遲加載的情況下,如想臨時改變策略,立刻將指定模塊的符號加載到調試器中,可以使用ld或者.reload /f命令。
2、符號服務器與符號緩存
設置符號服務器的基本語法是:
SRV*[符號緩存]*服務器地址
語法有SRV引導,符號緩存和服務器地址的前面各有一個星號引導。
此外,我們總是應該把微軟的公用符號庫加入到我們的符號路徑中:
.sympath+ srv*<緩存地址>*http://msdl.microsoft.com/download/symbols
這是一台微軟對外公開的服務器,使用http地址訪問,不是所有人都能牢記這個網址,所以最好的辦法就是使用.symfix命令(自動記憶了上面那個微軟符號服務器地址),語法如下:
.symfix [+] [符號緩存地址]
下面的命令等價於上面的.sympath命令,而不用輸入長長的http地址。
0:010> .symfix c:\windows\symbols
0:010> .sympath
Symbol search path is: srv*
Expanded Symbol search path is: SRV*c:\windows\symbols*http://msdl.microsoft.com/download/symbols
以上設置后,當需要用到符號,Windbg將自動到服務器上去下載,然后保存在c:\windows\symbols。
當然,我們也可以在計算機上面設置,設置方式是:
我的電腦=》高級系統設置=》高級Tab,點擊環境變量,新建一個用戶變量如下:
- 變量名:_NT_SYMBOL_PATH
- 變量值:SRV*D:\PDB*http://msdl.microsoft.com/download/symbols/
3、符號選項
命令格式如下:
- 顯示當前設置:.symopt
- 增加選項:.symopt+ Flags
- 刪除選項:.symopt- Flags
第一個命令沒有任何參數,顯示當前設置。“+”代表添加一個選項,“-”代表去除一個選項。
0:000> .symopt
Symbol options are 0x30237:
0x00000001 - SYMOPT_CASE_INSENSITIVE
0x00000002 - SYMOPT_UNDNAME
0x00000004 - SYMOPT_DEFERRED_LOADS
0x00000010 - SYMOPT_LOAD_LINES
0x00000020 - SYMOPT_OMAP_FIND_NEAREST
0x00000200 - SYMOPT_FAIL_CRITICAL_ERRORS
0x00010000 - SYMOPT_AUTO_PUBLICS
0x00020000 - SYMOPT_NO_IMAGE_SEARCH
可用的符號選項請見下表:
值 |
可讀名稱 |
描述 |
0×1 |
SYMOPT_CASE_INSENSITIVE |
符號名稱不區分大小寫 |
0×2 |
SYMOPT_UNDNAME |
符號名稱未修飾 |
0×4 |
SYMOPT_DEFERRED_LOADS |
延遲加載 |
0×8 |
SYMOPT_NO_CPP |
關閉C++轉換,C++中的::符號將以__顯示 |
0×10 |
SYMOPT_LOAD_LINES |
從源文件中加載行號 |
0×20 |
SYMOPT_OMAP_FIND_ NEAREST |
如果由於編譯器優化導致找不到對應的符號,就以最近的一個符號代替之 |
0×40 |
SYMOPT_LOAD_ANYTHING |
使得符號匹配的時候,匹配原則較松散,不那么嚴格。 |
0×80 |
SYMOPT_IGNORE_CVREC |
忽略鏡像文件頭中的CV記錄 |
0×100 |
SYMOPT_NO_UNQUALIFIED_ LOADS |
只在已加載模塊中搜索符號,如果搜索符號失敗,不會自動加載新模塊。 |
0×200 |
SYMOPT_FAIL_CRITICAL_ ERRORS |
不顯示文件訪問錯誤對話框。 |
0×400 |
SYMOPT_EXACT_SYMBOLS |
進行最嚴格的符號文件檢查,只要有微小的差異,符號文件都不會被加載。 |
0×800 |
SYMOPT_ALLOW_ABSOLUTE_ SYMBOLS |
允許從內存的一個絕對地址處讀取符號信息。 |
0×1000 |
SYMOPT_IGNORE_NT_ SYMPATH |
忽視在環境變量中設置的符號路徑,也忽視被調試進程的執行路徑。也就是說,當搜索符號文件的時候,不會從這些路徑中搜索。 |
0×2000 |
SYMOPT_INCLUDE_32BIT_MODULES |
讓運行在安騰系統上的調試器,也枚舉32位模塊。 |
0×4000 |
SYMOPT_PUBLICS_ONLY |
僅搜索符號文件的公共(PUBLIC)符號表,忽略私有符號表。 |
0×8000 |
SYMOPT_NO_PUBLICS |
不搜索符號文件的公共(PUBLIC)符號表 |
0×10000 |
SYMOPT_AUTO_PUBLICS |
先搜索pdb文件的私有符號表,如果在其中找到對應的符號,就不再搜索公共(PUBLIC)符號表,這可以加快搜索速度。 |
0×20000 |
SYMOPT_NO_IMAGE_SEARCH |
不搜索鏡像拷貝 |
0×40000 |
SYMOPT_SECURE |
安全模式,讓調試器盡量不影響到主機。 |
0×80000 |
SYMOPT_NO_PROMPTS |
不顯示符號代理服務器的認證對話框,將導致某些時候無法訪問符號服務器 |
0×80000000 |
SYMOPT_DEBUG |
顯示符號搜索的詳細過程和信息 |
4、符號加載
1、立刻加載
命令格式如下:
ld 模塊名 [/f 符號文件名]
加載指定模塊的符號。調試器默認采用延遲模式加載符號。ld使得延遲模式被打破,讓指定模塊的符號文件立刻加載到調試器中。此指令可為模塊的符號文件設置自定義的匹配名稱。
ld 123 /f abc
這樣一來,abc.pdb將成為123.exe的符號文件。正常情況下,這是不可能的,只能是abc.pdb對應abc.exe。
2、重新加載
如果對自己正在使用的符號文件感到疑惑,比如源代碼和行號明顯不匹配,最好就是重新加載一下符號文件。此命令語法如下:
- .reload /f /v [模塊名]
.reload命令的作用是刪除指定或所有已加載的符號文件,默認情況下,調試器不會立刻根據符號路徑重新搜索並加載新的符號文件,而是推遲到調試器下一次使用此文件時。
使用/f參數將破事調試器立刻搜索並重新加載新的符號文件。
其它參數解釋如下:
- /v:將搜索過程中的詳細信息都顯示出來。
- /i:不檢查pdb文件的版本信息;
- /l:只顯示模塊信息,內核模式下,和“lm n t”命令類似,但顯示內容比后者更多,因為包含了用戶模塊信息;
- /n:僅重載內核符號,不重載用戶符號;
- /o:強制覆蓋符號庫中的符號文件,即使版本相同;
- /d:用戶層模式下使用Windbg時的默認選項,重載調試器模塊列表中的所有模塊;
- /s:內核模式下使用Windbg時的默認選項,重載系統模塊列表中的所有模塊,另外,如果調試器在用戶模式下運行,要加載內核模塊,也必須使用/s選項,否則調試器將只會在調試器模塊列表中搜索而導致找不到內核模塊;
- /u:卸載指定模塊。如發現當前符號版本不對,使用/u開關先卸載之再重新加載。
3、符號驗證
符號文件出現不匹配的情況,這是有可能的,程序員在后期測試的時候可能會將工程多次編譯,為了維護多個版本而使得自己也被混淆。可以使用下面的命令驗證一個模塊的符號文件:
- !chksym <模塊名> [符號名]
加載選項:!sym
有兩類符號加載選項。第一類是Noisy/Quiet,Noisy選項將打印符號加載的詳細信息,Quiet選項則忽略這些信息。第二類是Prompts/Prompts off,即是否允許執行提示(Prompts)對話框。
一般都是在調用.reload命令之前,執行加載選項命令。
所謂Noisy是吵鬧的意思,調試器在搜索、加載符號的時候,會顯示更多與搜索有關的信息。在安靜模式下,則不會顯示這些信息。不管吵鬧與否,都不會影響到最終的搜索、加載結果。
當從網絡上下載符號文件的時候,可能會碰到網絡服務器要求客戶進行安全認證的情況,如果開啟Prompts選項,則彈出認證對話框,讓用戶輸入認證信息;否則,不彈出對話框,並且不會下載符號文件。
不加任何參數的情況下,顯示當前加載選項設置,下面的清單表明當前的設置為Quite及Prompts模式
0:000> .symopt Symbol options are 0x30237: 0x00000001 - SYMOPT_CASE_INSENSITIVE 0x00000002 - SYMOPT_UNDNAME 0x00000004 - SYMOPT_DEFERRED_LOADS 0x00000010 - SYMOPT_LOAD_LINES 0x00000020 - SYMOPT_OMAP_FIND_NEAREST 0x00000200 - SYMOPT_FAIL_CRITICAL_ERRORS 0x00010000 - SYMOPT_AUTO_PUBLICS 0x00020000 - SYMOPT_NO_IMAGE_SEARCH 0:000> !sym !sym <noisy/quiet - prompts/prompts off> - quiet mode - symbol prompts on
5、符號搜索
符號搜索包括全局搜索與就近搜索兩種。
1、全局搜索
命令“x”被用來進行符號的全局搜索,你可以把它直接就理解為search。格式如下:
- x [參數] [模塊!符號]
如果什么參數都沒有的話,它將列出當前調試環境下的所有局部變量,前提是要在有局部變量存在的情況下,顯示局部變量的另一個命令是dv,后文也會講到。
- x kernel32!a*
上面命令搜索並打印出kernel32模塊中所有a開頭的符號。x命令支持DML,使用/D選項即以DML格式顯示。
0:000> !sym !sym <noisy/quiet - prompts/prompts off> - quiet mode - symbol prompts on 0:000> x kernel32!a* 769836a8 kernel32!AllocContext = <no type information> 769a4286 kernel32!AdjustHijriYears = <no type information> 76986698 kernel32!AddLocalAlternateComputerNameW = <no type information> 76997fab kernel32!AllocateUserPhysicalPages = <no type information>... ... ...太長,省略一部分
如果你不知道ntcreatefile這個函數是在哪個模塊中定義的,可以試着使用下面的命令:
- x *!*NtCreateFile* (注:亦請參照!dlls –c命令)
同名函數在多個系統模塊中並定義,這可能出乎你的意料,但卻給你帶來真正的知識。
此外,x命令有多個可選參數。建議總是帶上/t和/v,可顯示更多符號、類型信息。
- /f:將只顯示函數符號;並且會顯示函數的詳細定義。
- /d:顯示更多的變量類型相關信息。
2、就近搜索
如果知道了符號的大概地址,但不能確定確切的符號名稱,該怎么處理呢?就近查找命令“ln”能發揮作用,ln是List Nearest的縮寫。它的作用是:(根據給定的地址)列出附近一定范圍內的所有符號。
6、源碼命令
如果含有源碼信息,可使得調試過程能夠以源碼模式逐行進行。和源碼相關的命令包括下面幾個:
源碼路徑:
和符號路徑類似,要設置源碼路徑,使用如下語法格式:
- .srcpath[+] [路徑1;路徑2]
不含任何參數的情況下,顯示當前設置的源碼路徑。
下面命令將覆蓋原設置,設置新的源碼搜索路徑
- .srcpath<路徑信息>
使用"+"可以將新的路徑添加到原設置中,而不會把原設置覆蓋掉:
- .srcpath+ <路徑信息>
7、源碼選項
這里列出三個源碼選項。
1、noisy
- 狀態:.srcnoisy
- 開啟:.srcnoisy 1
- 關閉:.srcnoisy 0
開始“吵鬧的源碼”選項后,在源碼加載、卸載、甚至單步的時候,都會顯示豐富的源碼信息。
2、lines
行號選項,即在符號文件加載過程中,是否將行號也一並加載進來。因為Windbg支持源碼級調試,所以它在Windbg中是默認開啟(Enable)的。
- .lines [-d|-e|-t]
參數d是disable的意思;e是enable的意思;t表示切換的意思,即自動在disable和enable兩者之間切換。
3、代碼行選項
包括行號和內容,語法如下:
- 打開:l+ [選項]
- 關閉:l- [選項]
命令|是line的縮寫,和上面的.lines命令不同的是,.lines是加載時選項,l是調試時選項。建議使用"l+*"指令,打開所有的行選項,效果會很不錯。這樣在單步調試的時候,每一步的代碼和行號都會顯示出來。顯得很醒目!
值得注意的是,進入源碼模式和進入匯編模式的命令分別為:
- 源碼模式:l+t
- 匯編模式:l-t
運行這兩個命令和在Windbg的Debug菜單下點擊source mode選項其效果是一樣的。