天天看點

RSDS pdb格式

本描述了“RSDS”或“DS”類型的pdb(程式資料庫)檔案的格式,這些檔案是由Miscrosoft的link.exe從版本7及更高版本發出的。

什麼是PDB檔案?

如果選擇了/DEBUG選項或/DEBUG:FULL選項,則最新的Microsoft連結器将在連結時建立程式資料庫(pdb)檔案。pdb檔案包含有關建立可執行檔案的資訊,還包含最新CodeView格式的符号資訊。可執行檔案包含本地計算機上pdb檔案的路徑和檔案名,以及辨別碼,以便可以找到正确的pdb檔案。pdb檔案本身的格式和最新的CodeView格式都沒有文檔記錄。據我所知,格式已經改變了兩次,而且很可能會再次改變。Microsoft提供API來分析和報告其Debug Information Access(DIA)SDK中pdb檔案的内容。

可執行檔案中的PDB檔案資訊

連結器将在連結時生成的pdb檔案的檔案名及其在本地計算機上的路徑放在可執行檔案的“CODEVIEW”調試目錄中。如果缺少這個,很可能是因為生成了一個dbg檔案。例如,如果在連結後運作REBASE程式,則可能會發生這種情況。在這種情況下,pdb檔案的路徑和檔案名将包含在dbg檔案中。然後,dbg檔案的檔案名将出現在可執行檔案的“MISC”調試目錄中。

如果“CODEVIEW”調試目錄不包含pdb檔案資訊,則其格式如下:-

+0h   dword        "RSDS" signature
+4h   GUID         16-byte Globally Unique Identifier
+14h  dword        "age"
+18h  byte string  zero terminated UTF8 path and file name      

這裡RSDS簽名辨別格式。全局唯一辨別符是特定于計算機的唯一值。它在這裡被寫入可執行檔案和pdb檔案中,以便将兩者辨別為比對。“age”是一個值,每當連結器重新生成可執行檔案及其關聯的pdb檔案時,該值都會遞增。

檢視“CODEVIEW”調試目錄

最簡單的方法之一是使用一個能夠可視化顯示可執行檔案内容的工具。其中一個這樣的工具是韋恩·J·拉德伯恩的PEview。

使用此工具,打開可執行檔案并打開左窗格上的“IMAGE_NT_HEADERS”标題。單擊IMAGE_OPTIONAL_HEADER并向下滾動,直到到達調試目錄條目。這将給出您感興趣的資訊的RVA(相對虛拟位址)。確定工具欄切換到RVA值,以便您可以繼續(RVA是一個位址,如果可執行檔案加載到記憶體中準備運作,則會應用該位址)。您正在可執行檔案中查找DEBUG目錄,它很可能會隐藏在其中一個部分的資料中。最有可能是“rdata”部分。單擊左側窗格中的那個,并檢查DEBUG目錄是否現在出現。如果沒有,請嘗試其他部分,以查找IMAGE_OPTIONAL_HEADER中給定的RVA。調試目錄包含指向調試資訊的指針。如果檔案中有一個“CODEVIEW”調試目錄,它将出現在調試目錄中,并且也将出現在PEview的左窗格中。單擊左窗格中的此項可檢視其内容。

下面是“CODEVIEW”調試目錄内容的典型示例:-

RSDS pdb格式

這裡的GUID是“B2DB2291-8FE8-4502-A20556A28496D442”“age”是7歲。接下來是pdb檔案的路徑和檔案名。注意,這應該是UTF-8格式,這意味着可以使用非羅馬字元的檔案名。

檢視PDB檔案

由于PDB格式不斷變化,您不能期望可視化工具跟上變化,最好使用十六進制編輯器(如Paws)檢視檔案,或轉儲到檔案或使用十六進制檔案轉儲程式(如Borland的tdump)列印檔案。

PDB檔案的性質

正如Sven B.Schreiber所做的那樣,pdb檔案格式與磁盤檔案系統所使用的格式類似。磁盤檔案系統将被劃分為固定大小的稱為“扇區”的資料塊。檔案中的資料包含在檔案寫入磁盤時辨別為備用的扇區中,但它們在磁盤上不一定是連續的。檔案目錄跟蹤資料在磁盤上的位置。在pdb檔案中,可能更适合将資料塊稱為“頁面”,将檔案中的資料稱為“流”,将檔案目錄稱為“流目錄”。

PDB檔案頭

pdb檔案頂部是此轉儲中顯示的頭:-

Turbo Dump  Version 4.2.16.1 Copyright (c) 1988, 1996 Borland International
                   Display of File TESTGOBUG.PDB

000000: 4D 69 63 72 6F 73 6F 66  74 20 43 2F 43 2B 2B 20 Microsoft C/C++ 
000010: 4D 53 46 20 37 2E 30 30  0D 0A 1A 44 53 00 00 00 MSF 7.00...DS...
000020: 00 04 00 00 02 00 00 00  E3 00 00 00 B4 04 00 00 ................
000030: 00 00 00 00 E1 00 00 00  00 00 00 00 00 00 00 00 ................
000040: 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 ................      

這裡要查找的字元是1Ah,它(用ascii術語)是一個“檔案結束”字元。在這種情況下(巧合地)出現在檔案中的偏移+1AH中,并标記字元串“微軟C/C++ +MSF 7”的結尾。應該注意的是,這個字元串的長度在不同的pdb版本之間是不同的。檔案結束字元緊接着是簽名,在本例中是“DS”,然後是空終止符,然後是足夠的填充,将頭帶到下一個dword,在本例中是+20h。

在+20h,我們找到了dword值400h,這是每個資料塊的大小,我們可以稱之為“頁面大小”。換句話說,pdb檔案被分成400h位元組(十進制為1024位元組)的塊。

在+24h是2h。我還不确定這代表什麼。

在+28h處有值0E3h。這表示整個檔案中有多少頁。如果乘以400h的頁面大小,則生成38C00h或232448,這是pdb檔案的位元組大小。

在+2Ch處有值4B4h(十進制1204)。這是流目錄的總大小(位元組)。因為每個頁面是1024位元組,我們現在知道流目錄覆寫一個完整的頁面加上180位元組。這一點很重要,因為流目錄在檔案中也不一定是連續的。

在+30h,值為零。我還沒有發現這代表什麼。

+34h是值0E1h。這是指向流目錄指針的指針。乘以400h的頁面大小,值0E1h變為38400h。是以在38400h時,我們希望在檔案中找到流目錄指針。

在PDB檔案中的流目錄指針

下面是檔案在38400h的轉儲,其中包含流目錄指針:-

038400: DF 00 00 00 E0 00 00 00  00 00 00 00 00 00 00 00 ................
038410: 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 ................
038420: 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 ................      

流目錄指針的結構非常簡單。從pdb頭我們知道流目錄在兩個頁面中,是以我們希望有兩個指針。我們可以看到指針是0DFh和0E0h。需要指針,因為流目錄在檔案中不一定是連續的。為了得到正确的位址,每個指針都需要乘以400h的頁面大小,這樣我們就可以看到流目錄的第一頁是0DFh*400h=37000h,然後繼續是0E0h*400h=38000h。

PDB檔案流目錄

流目錄是以下形式的結構:-

+0h資料流數

+4h每個流的dword,給出流的位元組大小

0=無流

-1=無流

+?指向流的指針的數組

這是在檔案3700h處的轉儲:-

037C00: 15 00 00 00 48 03 00 00  59 00 00 00 98 F2 02 00 ....H...Y.......
037C10: D7 07 00 00 00 00 00 00  D0 0A 00 00 6C 03 00 00 ............l...
037C20: 18 11 00 00 AA 14 00 00  FF FF FF FF 19 00 00 00 ................
037C30: 70 00 00 00 B4 05 00 00  68 01 00 00 1C 00 00 00 p.......h.......
037C40: FF FF FF FF FF FF FF FF  FF FF FF FF FF FF FF FF ................
037C50: FF FF FF FF C8 00 00 00  D9 00 00 00 DE 00 00 00 ................
037C60: DC 00 00 00 18 00 00 00  19 00 00 00 1A 00 00 00 ................
037C70: 1B 00 00 00 1C 00 00 00  1D 00 00 00 1E 00 00 00 ................
037C80: 1F 00 00 00 20 00 00 00  21 00 00 00 22 00 00 00 .... ...!..."...
037C90: 23 00 00 00 24 00 00 00  25 00 00 00 26 00 00 00 #...$...%...&...
037CA0: 27 00 00 00 28 00 00 00  29 00 00 00 2A 00 00 00 '...(...)...*...
037CB0: 2B 00 00 00 2C 00 00 00  2D 00 00 00 2E 00 00 00 +...,...-.......
037CC0: 2F 00 00 00 30 00 00 00  31 00 00 00 32 00 00 00 /...0...1...2...
037CD0: 33 00 00 00 34 00 00 00  35 00 00 00 36 00 00 00 3...4...5...6...
037CE0: 37 00 00 00 38 00 00 00  39 00 00 00 3A 00 00 00 7...8...9...:...      

第一個dword包含值15h。這表示檔案中有21個資料流。這也意味着有21個dword跟随(給出流大小)。是以,頁面指針從+58h開始,即檔案中的37C58h。流大小訓示每個流有多少頁指針。這與用于訓示流目錄本身有多少指針的系統相同。

例如,我們可以看到流1的長度是348h位元組。這可以安裝到一個頁面中,是以我們隻希望找到指向流1的一個指針。該指針(37C58h)為0D9h,乘以400h的頁面大小即為36400h。流2的長度為59h位元組,其指針為0DEh*400h=37800h。流3的長度為2F298h位元組(十進制193176)。是以,它有189頁,從37C5Ch開始有189個指針,第一頁是0DEh(37800h),第二頁是0DCh(37000h),第三頁是18h(6000h),以此類推。有些流大小不是0就是-1,這些可以忽略。這些流根本就沒有頁面指針。

The streams

我并沒有非常努力地去識别這些流的内容,因為找到感興趣的主要内容(符号)相當容易。與“JG”類型的pdb檔案一樣,符号流是第八流或第九流。流1到流4似乎總是包含相同類型的資訊。在流4以上,流的内容趨于變化。有時流完全丢失或添加了其他流。到目前為止,我還沒有找到訓示流包含什麼内容的索引。到目前為止,我确定的是:-

  • 流1—(可能)上一個流目錄。
  • 流2-pdb檔案真實性。
  • 流3-來自對象檔案中.debug$S和.debug$T節的材料。這可能非常龐大,因為它将包含許多未使用的材料,例如源腳本中引用的include檔案中的結構和結構成員。
  • 流4-生成過程中使用的檔案。
  • 流8或流9-符号。
  • 在流8之上,您将發現節資料、其他調試符号、連結器自身檔案資訊和連結的導入資訊。

流2-pdb檔案真實性

這些字段很重要,因為它允許進行檢查以確定pdb檔案與相關的可執行檔案

037800: 94 2E 31 01 25 55 1A 40  07 00 00 00 91 22 DB B2 ..1.%U.@....."..
037810: E8 8F 02 45 A2 05 56 A2  84 96 D4 42 11 00 00 00 ...E..V....B....
037820: 2F 4C 69 6E 6B 49 6E 66  6F 00 2F 6E 61 6D 65 73 /LinkInfo./names
037830: 00 02 00 00 00 04 00 00  00 01 00 00 00 06 00 00 ................
037840: 00 00 00 00 00 0A 00 00  00 0A 00 00 00 00 00 00 ................
037850: 00 04 00 00 00 00 00 00  00 00 00 00 00 00 00 00 ................      

符号流

+0h word - size of structure not including this word but
                  including the padding after the string
+2h word - type of symbol.  So far the following are known:-
           1108h = data type (from h or inc file)
           110Ch = symbol marked as "static" in the object file
           110Eh = global data variables, function names, imported functions
                   local variables
           1125h = function prototype
+4h dword - reserved
+8h dword - offset value
+0Ch word - section number
+0Eh bytes - null terminated string containing symbol name
+?h bytes - padding to next dword