大多數開發人員都意識到PDB文件有助於您進行調試,但僅此而已。如果你不知道PDB文件是怎么回事,不要覺得很糟糕,因為雖然有文檔在那里,但它分散在周圍,而且大部分是為編譯器和調試器編寫器准備的。雖然編寫編譯器和調試器非常酷和有趣,但這可能不是你的工作。
我想做的是把每個在微軟操作系統上進行開發的人都必須知道的PDB文件放在一個地方。這些信息也適用於本機開發人員和托管開發人員,不過我將提到一個特定於托管開發人員的技巧。我將從討論PDB文件存儲和內容開始。由於調試器使用PDB文件,我將詳細討論調試器如何為二進制文件找到正確的PDB文件。最后,我將討論調試器在調試時如何查找源文件,並向您展示一個與調試器如何查找源代碼相關的常用技巧。
在我們開始之前,我需要定義兩個重要的術語。在開發計算機上執行的生成是私有生成。在生成計算機上完成的生成是公共生成。這是一個重要的區別,因為調試在本地生成的二進制文件很容易,總是公共生成導致問題。
所有開發人員需要知道的最重要的事情是:PDB文件和源代碼一樣重要!很多公司沒有人能找到在生產服務器上運行的構建的PDB文件。如果沒有匹配的PDB文件,您的調試挑戰幾乎是不可能的。通過大量的努力,可以在沒有正確的PDB文件的情況下找到問題,但是如果您首先擁有正確的PDB文件,它將為您節省大量的資金。
正如Visual Studio的開發經理約翰坎寧安(John Cunningham)在2008年的PDC會議上所說的,“熱愛、保持和保護您的pdb”。至少,每個開發商店都必須設置一個符號服務器。您還可以在Windows調試工具幫助文件中閱讀Symbol Server文檔本身。請查看這些資源以了解有關詳細信息的更多信息。簡而言之,符號服務器存儲所有公共構建的pdb和二進制文件。這樣,無論哪個構建有人報告崩潰或問題,您都有與調試器可以訪問的公共構建完全匹配的PDB文件。Visual Studio和WinDBG都知道如何訪問符號服務器,如果二進制文件來自公共構建,調試器將自動獲取匹配的PDB文件。
大多數人在將PDB文件放入Symbol服務器之前還需要做一個准備步驟。這一步是在您的公共PDB文件上運行源服務器工具,這被稱為源索引。索引嵌入了版本控制命令,以拉動在特定公共構建中使用的確切源文件。因此,在調試公共生成時,您永遠不必擔心找到該生成的源文件。如果您是一個一人或兩人的團隊,則有時可以不使用源服務器步驟。
我聽到過一些團隊對設置符號服務器的抱怨,他們的軟件太大太復雜。我不得不承認,當我聽到別人說“我的團隊功能失調”時,你的軟件絕不可能比微軟做的任何事情都更大、更復雜。它們將所有產品的每個版本的索引和存儲到一個符號服務器中。這意味着從Windows到Office,再到SQL,再到游戲,所有東西都存儲在一個中心位置。我的猜測是,在雷德蒙德的34號樓只不過是存儲所有這些文件的SAN驅動器,而大樓里的每個人都支持這些SAN。能夠在微軟內部調試任何東西都是非常令人驚奇的,而且您不必擔心符號或源代碼(前提是您對源代碼樹擁有適當的權限)。
在關鍵基礎設施討論結束后,讓我來看看PDB中的內容以及調試器如何找到它們。PDB文件的實際文件格式是一個嚴格保密的秘密,但是微軟提供了api來為調試器返回數據。本機C++ PDB文件包含相當多的信息:
- 公共、私有和靜態函數地址
- 全局變量名稱和地址
- 參數和局部變量的名稱和偏移在堆棧上的位置
- 由類、結構和數據定義組成的類型數據
- 幀指針省略(FPO)數據,這是在x86上進行本機堆棧遍歷的關鍵
- 源文件名及其行
一個.NET PDB只包含兩部分信息:源文件名及其行和本地變量名。所有其他信息都已在.NET元數據中,因此無需在PDB文件中重復相同的信息。
將模塊加載到進程地址空間時,調試器使用兩條信息來查找匹配的PDB文件。第一個顯然是文件名。如果加載ZZZ.DLL,調試器將查找ZZZ.PDB。最重要的部分是調試器如何知道這是與此二進制文件完全匹配的PDB文件。這是通過嵌入在PDB文件和二進制文件中的GUID完成的。如果GUID不匹配,您肯定不會在源代碼級別調試模塊。
.NET編譯器和本機的鏈接器將此GUID放入二進制和PDB中。既然編譯的行為創建了這個GUID,那么請停下來考慮一下。如果您有昨天的版本,但沒有保存PDB文件,您是否可以再次調試二進制文件?不!這就是為什么保存每次生成的PDB文件如此重要的原因。因為我知道你在想,所以我會繼續回答你腦海中已經形成的問題:不,沒有辦法更改GUID。
但是,可以查看二進制文件中的GUID值。使用Visual Studio附帶的命令行工具DUMPBIN,可以列出可移植可執行文件(PE)的所有部分。要運行DUMPBIN,請從程序菜單中打開Visual Studio 2008命令提示符,因為需要設置PATH環境變量才能找到DUMPBIN EXE。順便說一下,如果你對DUMPBIN向你展示的信息感興趣的話,我強烈推薦Matt Pietrek在2002年2月和2002年3月的MSDN雜志上關於PE文件的權威文章。
DUMPBIN有很多命令行選項,但是顯示構建GUID的選項是/HEADERS。Pietrek文章將解釋輸出,但對我們來說重要的一點是調試目錄輸出:
Debug Directories
Time Type Size RVA Pointer
——– —— ——– ——– ——–
4A03CA66 cv 4A 000025C4 7C4 Format: RSDS,
{4B46C704-B6DE-44B2-B8F5-A200A7E541B0}, 1,
C:junkstuffHelloWorldobjDebugHelloWorld.pdb
根據調試器如何確定正確匹配的PDB文件的知識,我想討論調試器在哪里查找PDB文件。調試時,通過查看Visual Studio模塊窗口的符號文件列,可以看到所有這些命令都是自己加載的。首先搜索的是加載二進制文件的目錄。如果PDB文件不存在,調試器將查找嵌入PE文件中的調試目錄中的硬編碼生成目錄。如果查看上面的輸出,就會看到完整的路徑C:JUNKSTUFFHELLOWORLDOBJDEBUGHELLOWORD.PDB。(用於生成.NET應用程序的MSBUILD任務實際上生成到OBJ<CONFIG>目錄,並僅在成功生成時將輸出復制到調試或發布目錄。)如果PDB文件不在前兩個位置,並且在計算機上為設置了符號服務器,則調試器將在符號服務器緩存目錄中查找。最后,如果調試器在Symbol Server緩存目錄中找不到PDB文件,它將在Symbol Server本身中查找。此搜索順序就是為什么本地生成和公共生成部件從不沖突的原因。
調試器搜索PDB文件的方式對幾乎所有要開發的應用程序都很好。PDB文件加載更有趣的地方是那些需要您將程序集放入全局程序集緩存(GAC)中的.NET應用程序。我特別關注的是你的SharePoint和你對web部件的殘忍,但是還有其他的。對於本地計算機上的私有構建,使用起來很容易,因為調試器將在構建目錄中找到PDB文件,如我上面所述。當您需要在另一台計算機上調試或測試私有構建時,就會出現問題。
在另一台機器上,我看到許多開發人員在使用GACUTIL將程序集放入GAC后所做的工作是打開一個命令窗口,在C:windowssembly中搜索程序集在磁盤上的物理位置。盡管將來可能會發生更改,但為任何CPU編譯的程序集實際上都位於如下目錄中:C:WindowsassemblyGAC_MSILExample1.0.0.0__682bc775ff82796a
示例是程序集的名稱,1.0.0.0是版本號,682bc775ff82796a是公鑰標記值。推導出實際目錄后,可以將PDB文件復制到該目錄,調試器將加載該目錄。
如果你現在對像這樣挖掘GAC感到有點不安,你應該,因為它是不受支持和脆弱的。有一個更好的方法,似乎幾乎沒人知道DEVPATH。其思想是,您可以在.NET中設置一些設置,它會將您指定的目錄添加到GAC中,因此您只需將程序集和它的PDB文件放入該目錄,這樣調試就容易得多。僅在開發計算機上設置DEVPATH,因為存儲在指定目錄中的任何文件都不會像在真正的GAC中那樣進行版本檢查。
順便說一下,如果你在任何一個互聯網搜索引擎中搜索DEVPATH,其中一個最熱門的條目是Suzanne Cook的一篇過時的博客文章,她說微軟正在擺脫DEVPATH。這不再是事實。和其他博客一樣,看看蘇珊博客上的日期:2003年。這相當於互聯網時代的1670年。
要使用DEVPATH,首先要創建一個目錄,該目錄對所有帳戶都具有讀訪問權限,至少對開發帳戶具有寫訪問權限。此目錄可以位於計算機上的任何位置。第二步是設置一個系統范圍的環境變量DEVPATH,其值是您創建的目錄。有關DEVPATH的文檔並沒有說明這一點,但是在執行下一步之前,請先設置DEVPATH環境變量。
要告訴.NET運行時已設置DEVPATH,需要根據應用程序的需要將以下內容添加到APP.CONFIG、WEB.CONFIG或MACHINE.CONFIG中:
<configuration>
<runtime>
<developmentMode developerInstallation=”true”/>
</runtime>
</configuration>
一旦打開開發模式,您就會知道在進程中缺少DEVPATH環境變量或者您設置的路徑不存在,如果應用程序在啟動時死機,錯誤消息表示完全非直觀的:“注冊表的無效值”。如果確實要在MACHINE.CONFIG中使用DEVPATH,請格外小心,因為計算機上的每個進程都會受到影響。在一台機器上導致所有.NET應用程序失敗不會贏得辦公室里的許多朋友。
每個開發人員需要知道的關於PDB文件的最后一項是源文件信息如何存儲在PDB文件中。對於已在其上運行源索引工具的公共構建,存儲是將源文件獲取到所設置的源緩存中的版本控制命令。對於私有構建,存儲的是編譯器用來生成二進制文件的源文件的完整路徑。換句話說,如果在C:FOO中使用源文件MYCODE.CPP,那么PDB文件中嵌入的是C:FOOMYCODE.CPP。這可能是你已經懷疑過的,但我只是想說清楚。
理想情況下,所有的公共構建都會立即自動被源代碼索引並存儲在符號服務器中,因此如果您甚至不必再考慮源代碼在哪里的話。然而,一些團隊在PDB文件中不做源索引,直到他們做了冒煙測試或其他祝福,看看該構建是否足夠好讓其他人使用。這是一個非常合理的方法,但是如果必須在生成的源代碼被索引之前調試生成,最好將該源代碼拉到與生成計算機使用的驅動器和目錄結構完全相同的位置,否則在源代碼級別調試時可能會遇到一些問題。雖然Visual Studio調試器和WinDBG都有設置源搜索目錄的選項,但我發現很難找到正確的方法。
對於較小的項目,這沒有問題,因為您的源代碼總是有足夠的空間。生活更困難的地方是在更大的項目上。如果你有30MB的源代碼,而你的C:驅動器上只剩下20MB的磁盤空間,你該怎么辦?有辦法控制PDB文件中存儲的路徑不是很好嗎?
雖然我們不能編輯PDB文件,但是有一個簡單的技巧可以控制PDB文件中的路徑:SUBST.EXE。subt所做的是將路徑與驅動器號相關聯。如果您將源代碼下拉到C:DEV並執行“SUBST R:C:DEV”,那么如果您鍵入“DIR C:DEV”,R:drive現在將在其頂層顯示相同的文件和目錄。您還將在資源管理器中看到R:drive作為新驅動器。也可以通過將驅動器映射到資源管理器中的共享目錄來實現驅動器到路徑的影響我個人更喜歡subt方法,因為它不需要機器上的任何共享。雖然有些人認為可以通過<DRIVE>$共享,但有些組織禁用了該功能。
在生成計算機上要做的是設置一個啟動項,該啟動項執行特定的SUBST命令。當生成系統帳戶登錄時,它將提供新的驅動器號,這是您進行生成的位置。通過對PDB文件中嵌入的驅動器和根目錄的完全控制,在測試機上設置源代碼所需做的全部工作就是在任何需要的地方將其拉下來,並使用與生成機使用的驅動器號相同的驅動器號執行子命令。現在調試器中不再考慮源匹配了。