我們知道符号檔案對我們調試是非常重要的,如果沒有符号檔案,我們在調試器裡看到的要麼是偏移位址,要麼看到的是錯誤符号,這會導緻我們定不了位或錯誤定位,如果有了比對的符号檔案,這一切都不是問題了。
一、調試器尋找符号檔案
首先在我們編譯我們的程式時,如果設定了符号選項,那麼在編譯連接配接時,除了産生我們需要的符号檔案外,還會在我們的PE檔案(exe,dll)裡記錄下對應的符号檔案資訊。
這裡的路徑其實是開發編譯機上生成的路徑,當我們在這台機上調試時,就會到這個目錄下尋找符号檔案。當在别的機器上調試時,調試器就會會拿路徑裡的檔案名去查找。
當調試器加載一個PE子產品時,第一個搜尋的路徑就是這個子產品所在的路徑,如果不在子產品所在的路徑,則查找子產品中記錄的build目錄,就時上圖裡的路徑, 如果以上兩個路徑都沒有找到PDB,則根據symbol server的設定,在本地的symbol server的cache中查找,如果在本地的symbol server的cache中沒有對應的PDB,則最後才到遠端的symbol server中查找。
當在某個路徑找到了檔案名比對的符号檔案,就要看符合檔案根子產品是否比對。根據上圖,我們知道編譯器在生成PE子產品時,調試資訊裡面會生成一個GUID值,同時符号檔案裡也會記錄這個GUID值,每一次編譯連接配接,這個值都會發生變化。
當兩個檔案裡的GUID值相等時,符号檔案才跟子產品檔案比對,這時才能進行正常的調試,獲得正确的符号資訊。是以我們在調試時,一定要把正确的符号檔案跟子產品檔案放在同一目錄下才行。
二、調試器定位
當有了正确的符号檔案後,調試器是如何定位源代碼、行号等資訊的呢?我們以VS來大概講一下,預設情況下,在pdb檔案中,儲存了可執行檔案中所有的符号(函數名、變量名等)所在源檔案、行号、OFFSET(檔案中的偏移)等資訊。但是這些資訊,是在編譯階段得到的,編譯器在編譯每個cpp的過程中,就可以把這些符号的相關資訊收集起來,存放在各個cpp所生成的obj檔案中,然後在連結的時候,提取每個obj中的這些資訊,生成一個單獨的pdb檔案。這樣,以後調試程式的時候,調試器隻要找得到這個pdb,就可以知道可執行檔案中,所有符号所在的源檔案、行号和OFFSET了。反過來說,當給出一個源檔案和行号,就可以拿到對應的OFFSET了,是以在還沒有啟動調試的時候,我們下的斷點,實際上調試器是知道這個斷點應該在哪個OFFSET上了,等啟動調試的時候,用這個OFFSET加上這個子產品所加載到的基位址值,就可以得到這個斷點所在的VA(程式加載到記憶體後的一個虛拟位址)了,然後在這個VA處強行寫上int 3指令,并繼續執行,當執行到這裡,便中斷下來給我們一個調試機會了。當我們在VS裡滑鼠放在某個變量上時,調試器可以拿到這個變量的名稱,根據我們前面說的,用這個名稱去pdb中查找,自然就可以找到pdb檔案中儲存的OFFSET了,加上這個子產品的基位址,就找到了這個變量所在記憶體的VA,剩下的就是讀一下這個VA記憶體中的内容了。這樣也就實作了觀察變量值得功能。