說說Debug文件夾下的.pdb文件

.PDB文件,全稱爲「程序數據庫」文件。咱們使用它(更確切的說是看到它被應用)大多數場景是調試應用程序。目前咱們對.PDB文件的廣泛認知是它存儲了被編譯文件的調試信息,做爲符號文件存在。那麼,它具體包含哪些內容呢?在調試過程當中是怎樣發揮做用的呢?咱們有沒有辦法去操做這個文件呢?html

一、PDB文件內容

.PDB文件的內部格式,微軟並無公開,如今仍然是一個祕密,可是它提供了相關的API用於調試器來從中獲取信息。

一個非託管C++程序的PDB文件包含以下信息:數據庫

    • l Public, private,和static函數地址
    • l 全局變量的名稱和地址
    • l 參數和局部變量的名稱及它們在棧中的偏移量
    • l 類型定義,包括class, structure,和 data definitions
    • l FPO(Frame Pointer Omission,幀指針省略)數據
    • l 源文件名稱和行號
說明:從XP SP2起就再也不啓用FPO。
對於 .NET PDB文件,只包含上面說到的兩種信息:
  • l 源文件名稱和行號
  • l 局部變量名稱
.NET PDB文件包含如此少的信息,緣由在於其餘信息咱們能夠從元數據中獲取,因此也就沒有必要重複存儲了。

二、PDB文件匹配

當你講一個模塊加載到當前進程的地址空間中,調試器根據兩個信息去尋找找匹配的PDB文件。第一個信息很明瞭,根據模塊的名稱。好比你加載的模塊爲「Test.DLL」,那麼調試器將會尋找Test.PDB文件。可是可是經過名稱,是沒法判斷模塊和PDB文件是不是完整匹配的,調試器經過第二個信息來判斷——一個GUID值。這個GUID值同時存在於模塊文件和PDB文件中,若是GUID值不匹配,那麼咱們是沒法在源代碼級別來調試程序的。
這個GUID值是編譯器和連接器放到文件中的。目前咱們沒有辦法來改變這個值,可是咱們查看在編譯生成的文件中的GUID值。這裏咱們使用的工具是DUMPBIN,使用它咱們能夠列舉全部PE文件的信息。若是您安裝了VS2008或者VS2010的話,能夠直接從VS命令行啓動該工具,如圖1所示。
這個GUID值是編譯器和連接器放到文件中的。目前咱們沒有辦法來改變這個值,可是咱們查看在編譯生成的文件中的GUID值。這裏咱們使用的工具是DUMPBIN,使用它咱們能夠列舉全部PE文件的信息。若是您安裝了VS2008或者VS2010的話,能夠直接從VS命令行啓動該工具,如圖1所示。

啓動DUMPBIN圖示

圖 1 啓動DUMPBIN
DUMPBIN工具的命令行選項,您能夠參閱相關的文檔,這裏咱們使用它的/headers選項,看看會輸出什麼樣的結果。
在使用dumpbin以前,咱們首先建立一個控制檯程序,內容代碼以下所示。
using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

namespace dumptest

{

class Program

    {

static void Main(string[] args)

        {

        }

    }

}

編譯上面的代碼,obj目錄中的文件如圖2所示。



圖 2 obj目錄中的文件

Bin目錄下的文件內容如圖3所示。



圖 3 Bin目錄下的文件
準備工做作完以後,咱們如今從命令行切換到bin目錄下,執行「dumpbin /headers dumptest.exe」命令,結果以下面代碼所示。
E:\test\c#\dumptest\dumptest\obj\x86\Debug>dumpbin /headers dumptest.exe

Microsoft (R) COFF/PE Dumper Version 10.00.30319.01

Copyright (C) Microsoft Corporation. All rights reserved.

Dump of file dumptest.exe

PE signature found

File Type: EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (x86)

3 number of sections

4E92A178 time date stamp Mon Oct 10 15:40:40 2011

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

102 characteristics

Executable

32 bit word machine

OPTIONAL HEADER VALUES

10B magic # (PE32)

8.00 linker version

800 size of code

800 size of initialized data

0 size of uninitialized data

272E entry point (0040272E)

2000 base of code

4000 base of data

400000 image base (00400000 to 00407FFF)

2000 section alignment

200 file alignment

4.00 operating system version

0.00 image version

4.00 subsystem version

0 Win32 version

8000 size of image

200 size of headers

0 checksum

3 subsystem (Windows CUI)

8540 DLL characteristics

Dynamic base

NX compatible

No structured exception handler

Terminal Server Aware

100000 size of stack reserve

1000 size of stack commit

100000 size of heap reserve

1000 size of heap commit

0 loader flags

10 number of directories

0 [0] RVA [size] of Export Directory

26D4 [57] RVA [size] of Import Directory

4000 [588] RVA [size] of Resource Directory

0 [0] RVA [size] of Exception Directory

0 [0] RVA [size] of Certificates Directory

6000 [C] RVA [size] of Base Relocation Directory

2668 [1C] RVA [size] of Debug Directory

0 [0] RVA [size] of Architecture Directory

0 [0] RVA [size] of Global Pointer Directory

0 [0] RVA [size] of Thread Storage Directory

0 [0] RVA [size] of Load Configuration Directory

0 [0] RVA [size] of Bound Import Directory

2000 [8] RVA [size] of Import Address Table Directory

0 [0] RVA [size] of Delay Import Directory

2008 [48] RVA [size] of COM Descriptor Directory

0 [0] RVA [size] of Reserved Directory

SECTION HEADER #1

.text name

734 virtual size

2000 virtual address (00402000 to 00402733)

800 size of raw data

200 file pointer to raw data (00000200 to 000009FF)

0 file pointer to relocation table

0 file pointer to line numbers

0 number of relocations

0 number of line numbers

60000020 flags

Code

Execute Read

<strong>Debug Directories

Time Type Size RVA Pointer

-------- ------ -------- -------- --------

4E92A178 cv 50 00002684 884 Format: RSDS, {99F34C5E-5BC3-4043-AE11-D85F7990AF00}, 1, E:\test\c#\dumptest\dumptest\obj\x86\Debug\dumptest.pdb
</strong>
SECTION HEADER #2

.rsrc name

588 virtual size

4000 virtual address (00404000 to 00404587)

600 size of raw data

A00 file pointer to raw data (00000A00 to 00000FFF)

0 file pointer to relocation table

0 file pointer to line numbers

0 number of relocations

0 number of line numbers

40000040 flags

Initialized Data

Read Only

SECTION HEADER #3

.reloc name

C virtual size

6000 virtual address (00406000 to 0040600B)

200 size of raw data

1000 file pointer to raw data (00001000 to 000011FF)

0 file pointer to relocation table

0 file pointer to line numbers

0 number of relocations

0 number of line numbers

42000040 flags

Initialized Data

Discardable

Read Only

Summary

2000 .reloc

2000 .rsrc

2000 .text
如今咱們將目光集中在代碼清單2中斜體加粗的部分,這裏咱們能夠找到調試路徑的信息,一個GUID值(99F34C5E-5BC3-4043-AE11-D85F7990AF00)和一個路徑(E:\test\c#\dumptest\dumptest\obj\x86\Debug\dumptest.pdb)。如今咱們已經清楚了調試器如何判斷PDB文件是否匹配,下面咱們再來看調試器是如何尋找PDB文件的。

三、PDB文件尋路

若是咱們觀察VS啓動調試加載模塊和符號文件的過程,會發現它一般會從可執行文件或者DLL文件的相同目錄中加載符號文件。這正是調試器尋找PDB文件的第一選擇。
若是在模塊文件的相同目錄下找不到匹配的PDB文件,會發生什麼呢?咱們在前文知道編譯器在PE文件中硬編碼了一個路徑(好比:E:\test\c#\dumptest\dumptest\obj\x86\Debug\dumptest.pdb),這個路徑就是調試器的第二個選擇。對於對外發布的應用,極可能這兩個路徑下都找不到PDB文件。此時調試器會在本地的符號服務器緩存路徑下尋找PDB文件。若是本地的符號服務器緩存路徑下仍然找不到,它會調試器自己配置的符號服務器中尋找符號文件。圖4是VS2010配置符號服務器和本地符號緩存路徑的界面。



圖 4 VS2010配置符號存儲

4PDB與GAC

上面講到的調試器尋找PDB文件的方式在大多數狀況下都工做的很好,當咱們遇到必需要講編制以後的文件安裝的GAC中的時候,狀況開始變得有意思起來。當咱們在本地編譯並調試程序集的時候,即便程序集被安裝到GAC中,調試器仍然能在編譯目錄下找到PDB文件,可是若是咱們已經將Private Build的應用部署到其餘機器上的時候,此時還想在被部署的機器上調試安裝到GAC上的程序集,將會是一件很麻煩的事情。咱們有兩種方案來解決這個問題。
注:

Private Build與Public Build的區別c#

private build, 用來表示在開發人員本身機器上生成的build;public build,表示在公用的build機器上生成的build。對於public build,須要symbol server存儲全部的PDB,而後當用戶報告錯誤的時候,debugger才能夠自動地找到binay相應的PDB文件, visual studio 和 windbg都知道如何訪問symbol server。在將PDB和binay存儲到symbol server前,還須要對PDB運行進行source indexing, source indexing的做用是將PDB和source關聯起來。緩存

第一種方案是咱們在GAC的目錄中找到被安裝的程序集,而後將PDB文件拷貝到該目錄下。一般咱們安裝到GAC中的程序集會存在於相似這樣的路徑中:C:Windows\assembly\GAC_MSIL\Example\1.0.0.0__682bc775ff82796a,該示例目錄中「Example」表明程序集的名稱,「1.0.0.0」表明版本號,「682bc775ff82796a」表明程序集的Public Token。當你找到確切的目錄,將PDB文件放到該目錄下,調試器就能夠加載符號文件了。
這裏還有一種更好的方案,就是設置一個名爲「DEVPATH」的系統環境變量。該環境變量設定一個磁盤目錄做爲其值,該目錄將做爲GAC的輔助目錄存在,在GAC中查找程序集一樣會搜索該目錄。可是在這樣的目錄中程序集並不會執行版本檢查,這是須要咱們注意的地方。
使用DEVPATH,咱們首先要選定一個目錄,而後確保應用程序對它持有讀、寫權限。而後建立DEVPATH系統環境變量。固然這只是準備工做,咱們還要告知.NET運行時,應用程序啓用DEVPATH做爲GAC的擴展目錄。因此接下來咱們在配置文件(APP.CONFIG,WEB.CONFIG,MACHINE.CONFIG)中添加以下配置:
<configuration> 
<runtime> 
<developmentMode developerInstallation="true"/> 
</runtime> 
</configuration>
在你打開了development模式後,若是DEVPATH沒有定義或路徑不存在的話會致使程序啓動時異常"Invalid value for registry"。並且若是在machine.config中開啓DEVPATH的使用會影響其餘的全部的程序,因此要慎重使用machine.config。

五、PDB與源文件

如今咱們再來討論一個開發人員常常問到的問題:源文件的信息是如何在PDB文件中存儲的?對於Public Build,PDB文件存儲的是如何利用版本控制工具從代碼緩存獲取源碼代碼的命令。對於Private Build,很顯然,符號文件存儲的是源代碼的完整路徑。
理想狀態下,對於Public Build而言源代碼索引和符號被緩存到符號服務器的操做都會自動執行,咱們無需考慮源代碼和符號文件在哪存儲的問題。事實上,不少開發團隊並無公用的符號服務器或者源代碼索引服務。對於一個小型項目而言,每一個開發人員都有足夠的磁盤空間來存儲源代碼和符號文件,也許不會過多的考慮這個問題。可是也許會有這樣的場景:將一個Private Build項目轉移到另外一臺機器上有30M的源碼須要放到C盤,可是此時C盤只有20M的剩餘空間該怎麼辦?咱們能修改PDB文件中源碼的路徑嗎?
咱們前面提到沒有辦法修改PDB文件,可是這裏有一個取巧的方法能夠嘗試,所謂山不向我走來,我向山走去。這裏有一個工具剛好能夠派上用場,它就是subst.exe,它是Windows自帶的命令行工具,能夠從cmd窗口啓動。

說明:服務器

subst用於路徑替換 ,將路徑與驅動器號關聯,就是把一個目錄看成一個磁盤驅動器來看,不過不能格式化。運用必定技巧,subst命令還能夠實現隱藏驅動器、特殊軟件的安裝、模擬光盤自動運行等功能。函數

用法格式工具

1、subst [盤符] [路徑] 將指定的路徑替代盤符,該路徑將做爲驅動器使用ui

2、subst /d 解除替代編碼

3、不加任何參數鍵入 SUBST,能夠顯示當前虛擬驅動器的清單。spa

[例子]

C:\DOS>subst a: c:\temp 將c:\temp虛擬化成a盤

C:\>subst a: /d? 解除替代

想到解決上面問題的方法了嗎?咱們只需將本機源碼的路徑虛擬化成一個磁盤,例如M,以後不管你將代碼部署到任何機器上,只需將被部署的路徑虛擬成M就能夠了,就不會出現符號文件和目標代碼不匹配的狀況了。

做者:玄魂

相關文章
相關標籤/搜索