在日常分析C++軟件異常的日常工作中,大多數(shù)情況下我們都是使用Windbg去靜態(tài)分析dump文件去排查軟件異常的,今天我們就來詳細(xì)地講一下如何使用Windbg去靜態(tài)分析dump文件,以供參考。
image.png
1、概述
基本大部分軟件都內(nèi)置了異常捕獲模塊,在軟件發(fā)生閃退崩潰時,都會彈出相關(guān)的提示框,比如PC版的微信在崩潰時,其內(nèi)置的異常捕獲模塊會捕獲到并生成日志及dump文件,同時會彈出如下的發(fā)送錯誤報告的提示框:
image.png
提示框的下方會自動帶上崩潰相關(guān)的文件,其中最后一個文件就是我們要講的dump文件,點擊確定則會將這些文件發(fā)送到騰訊遠(yuǎn)端的服務(wù)器上。騰訊后臺的運維人員就會收到通知,然后到服務(wù)器上將dump等文件下載下去去分析。
有些軟件可能沒有上傳崩潰日志到服務(wù)器的功能,捕捉到異常時會自動將dump文件保存到指定的路徑中,事后可以到該路徑中取到對應(yīng)的dump文件。如果客戶機器上遇到崩潰,可以和客戶聯(lián)系,讓客戶幫忙從對應(yīng)的路徑中取來dump文件。
下面就來詳細(xì)講一下拿到dump文件之后如何使用Windbg去靜態(tài)分析dump文件。
2、靜態(tài)分析dump文件的一般步驟
用Windbg打開dump文件,先使用.ecxr命令切換到異常的上下文,然后根據(jù)切換成功后顯示的異常類型(ExceptionCode值對應(yīng)的含義,比如0xC0000005對應(yīng)的就是AccessViolation內(nèi)存訪問違例)、發(fā)生崩潰的那條匯編指令及相關(guān)寄存器的值。通過查看匯編指令及各寄存器的值,可以初步判斷當(dāng)前發(fā)生的是什么異常,比如C++類空指針、內(nèi)存訪問違例。
image.png
接著,使用kn/kv/kp查看函數(shù)調(diào)用堆棧,看看是調(diào)用了什么函數(shù)觸發(fā)的異常。調(diào)用堆棧中會顯示函數(shù)所在模塊信息,一般我們需要拿到這些模塊的pdb文件,加載pdb文件后函數(shù)堆棧中才會顯示具體的函數(shù)名和行號的。我們需要使用lm命令查看這些模塊的時間戳,然后找來這些模塊對應(yīng)的pdb文件,然后將pdb文件的路徑設(shè)置到windbg中。
然后,根據(jù)函數(shù)調(diào)用堆棧中顯示的函數(shù)名及行號,對照著源代碼去分析發(fā)生異常的原因。有時候為了搞清楚發(fā)生異常的本質(zhì),我們還需要使用IDA查看相關(guān)二進(jìn)制文件的匯編代碼,查看一下發(fā)生異常的那條匯編指令的上下文,對照著C++源碼,看看那條匯編指令為啥會出現(xiàn)異常。
3、分析實例說明
我們?yōu)榱朔奖阏归_講解,我們特意使用VisualStudio創(chuàng)建了一個基于MFC對話框的exe程序工程,對話框中有個名為button1的按鈕,如下所示:
image.png
我們在此按鈕的響應(yīng)中添加了一段引發(fā)崩潰的測試代碼,故意讓程序產(chǎn)生崩潰,測試然后拿到崩潰時的dump文件。
具體的測試代碼如下所示:
// 添加的一段測試代碼SHELLEXECUTEINFO *pShExeInfo = NULL;int nVal = pShExeInfo->cbSize; // 通過空指針訪問結(jié)構(gòu)體成員,導(dǎo)致崩潰 CString strTip;strTip.Format( _T(“nVal=%d.”), nVal );AfxMessageBox( strTip );
代碼中使用到的結(jié)構(gòu)體SHELLEXECUTEINFO 定義如下:
typedef struct _SHELLEXECUTEINFOW{ DWORD cbSize; // in, required, sizeof of this structure ULONG fMask; // in, SEE_MASK_XXX values HWND hwnd; // in, optional LPCWSTR lpVerb; // in, optional when unspecified the default verb is choosen LPCWSTR lpFile; // in, either this value or lpIDList must be specified LPCWSTR lpParameters; // in, optional LPCWSTR lpDirectory; // in, optional int nShow; // in, required HINSTANCE hInstApp; // out when SEE_MASK_NOCLOSEPROCESS is specified void *lpIDList; // in, valid when SEE_MASK_IDLIST is specified, PCIDLIST_ABSOLUTE, for use with SEE_MASK_IDLIST & SEE_MASK_INVOKEIDLIST LPCWSTR lpClass; // in, valid when SEE_MASK_CLASSNAME is specified HKEY hkeyClass; // in, valid when SEE_MASK_CLASSKEY is specified DWORD dwHotKey; // in, valid when SEE_MASK_HOTKEY is specified union { HANDLE hIcon; // not used#if (NTDDI_VERSION >= NTDDI_WIN2K) HANDLE hMonitor; // in, valid when SEE_MASK_HMONITOR specified#endif // (NTDDI_VERSION >= NTDDI_WIN2K) } DUMMYUNIONNAME; HANDLE hProcess; // out, valid when SEE_MASK_NOCLOSEPROCESS specified} SHELLEXECUTEINFOW, *LPSHELLEXECUTEINFOW; #ifdef UNICODEtypedef SHELLEXECUTEINFOW SHELLEXECUTEINFO;typedef LPSHELLEXECUTEINFOW LPSHELLEXECUTEINFO;#elsetypedef SHELLEXECUTEINFOA SHELLEXECUTEINFO;typedef LPSHELLEXECUTEINFOA LPSHELLEXECUTEINFO;#endif // UNICODE
在測試代碼中定義了SHELLEXECUTEINFO結(jié)構(gòu)體指針pShExeInfo,并初始化為NULL,然后并沒有給該指針賦一個有效的結(jié)構(gòu)體對象地址,然后使用pShExeInfo訪問結(jié)構(gòu)體的cbSize成員的內(nèi)存,因為pShExeInfo中的值為NULL,所以結(jié)構(gòu)體cbSize成員的內(nèi)存地址是結(jié)構(gòu)體對象起始地址的偏移,因為結(jié)構(gòu)體對象地址為NULL,cbSize成員位于結(jié)構(gòu)體的首位,所以cbSize成員就是結(jié)構(gòu)體對象的首地址,就是NULL,所以就訪問64KB小地址內(nèi)存塊的異常,引發(fā)內(nèi)存訪問違例,導(dǎo)致程序發(fā)生崩潰閃退。
至于如何在程序中設(shè)置異常捕獲模塊去捕獲異常、自動生成dump文件,可以嘗試使用google開源的CrashRpt庫,我們產(chǎn)品很早就有了,具體怎么獲取目前還沒有研究過,大家需要的話可以到網(wǎng)上搜一下。
4、用Windbg打開dump文件,初步分析
使用Windbg打開dump文件(直接將dump文件拖入到Windbg),然后輸入.ecxr命令,切換到異常的上下文,就可以發(fā)生的異常類型、發(fā)生了異常崩潰的那條匯編及崩潰時的各個寄存器的值。
首先,我們查看一下發(fā)生的異常類型,比如AccessViolation內(nèi)存訪問違例、StackOverflow線程棧溢出的異常,這樣對異常有個初步的認(rèn)知,如下:
image.png
接著就是查看發(fā)生異常的那條指令,查看指令的構(gòu)成以及崩潰時的各個寄存器的值,可能能初步估計出發(fā)生異常的原因。如果指令中訪問了一個很小的地址或者訪問了一個很大的地址,都會觸發(fā)內(nèi)存訪問違例。
1)訪問64KB小地址內(nèi)存區(qū),引發(fā)訪問違例在Windows系統(tǒng)中,64KB以內(nèi)的內(nèi)存地址是禁止訪問的,如果程序訪問這個范圍內(nèi)的內(nèi)存,則會觸發(fā)內(nèi)存訪問違例,系統(tǒng)會強行將進(jìn)程終止掉。2)訪問了內(nèi)核態(tài)的大地址內(nèi)存區(qū),引發(fā)訪問違例對于32位程序,系統(tǒng)會給進(jìn)程分配4GB的虛擬內(nèi)存,一般情況下用戶態(tài)和內(nèi)核態(tài)會各占一半,即各占2GB,我們編寫的代碼基本都是運行在用戶態(tài)的,用戶態(tài)的代碼時不能訪問內(nèi)核態(tài)內(nèi)存地址的(內(nèi)核態(tài)地址是供系統(tǒng)內(nèi)核模塊使用的),如果崩潰指令中訪問了一個很大的內(nèi)存地址,超過用戶態(tài)的地址范圍0-2GB,內(nèi)存地址大于0x8000000,則會觸發(fā)內(nèi)存違例,因為用戶態(tài)的代碼是禁止訪問內(nèi)核態(tài)內(nèi)存地址的。
如果匯編指令中使用到了ecx寄存器進(jìn)行地址計算,去訪問計算出來的內(nèi)存地址,如果訪問了一個小內(nèi)存地址并且ecx寄存器為0,那么這個崩潰可能是空指針引發(fā)的。在C++中,在調(diào)用C++類的成員函數(shù)時是通過ecx寄存器傳遞C++對象地址的,所以在通過C++類對象去調(diào)用虛函數(shù)(調(diào)用虛函數(shù)時的二次尋址)及通過C++對象去訪問對象中的數(shù)據(jù)成員時,都會用到ecx寄存器的。
上面故意添加的會引發(fā)崩潰的測試代碼,運行后產(chǎn)生dump文件,我們來分析一下這個dump文件。用Windbg打開dump文件后,輸入.excr命令,接著輸入kn命令,查看到如下的結(jié)果:
image.png
首先,這是Access Violation內(nèi)存訪問違例的異常。其次,從崩潰的這條匯編指令來看,是訪問了小地址0x00000000地址,這是訪問了小于64KB小地址內(nèi)存區(qū),這個范圍的地址是禁止訪問的,所以引發(fā)了內(nèi)存訪問違例。從匯編指令及寄存器的值來看,看不出來什么明顯的線索。
所以接著輸入了kn命令,將函數(shù)調(diào)用堆棧打印出來。函數(shù)調(diào)用堆棧是從下往上看的,最上面一行就是最后調(diào)用的一個函數(shù),也是崩潰的那條匯編指令所在的函數(shù)。從函數(shù)調(diào)用堆棧的最后一幀調(diào)用的函數(shù)來看,程序的崩潰是發(fā)生在TestDlg.exe文件模塊中,不是其他的dll模塊。顯示的函數(shù)地址是相對TestDlg.exe文件模塊起始地址的偏移,為啥看不到模塊中具體函數(shù)名稱呢?那是因為Windbg找不到TestDlg.exe對應(yīng)的pdb文件,pdb文件中包含對應(yīng)的二進(jìn)制文件中的函數(shù)名稱及變量等信息,Windbg加載到pdb文件才能顯示完整的函數(shù)名。
查看函數(shù)調(diào)用堆棧的命令,除了kn,還有kv和kp命令,其中kv還可以看到函數(shù)調(diào)用堆棧中調(diào)用函數(shù)時傳遞的參數(shù),如下所示:
我們需要取來pdb符號庫文件,去查看具體的函數(shù)名及行號的,這樣才好找到直接的線索的。下面就來看看如何獲取到TestDlg.exe模塊的pdb文件。
5、找到pdb文件,設(shè)置到windbg中,查看完整的函數(shù)調(diào)用堆棧
如何才能找到TestDlg.exe文件對應(yīng)的dpb文件?我們可以通過查看TestDlg.exe文件的時間戳找到文件的編譯時間,通過編譯時間找到文件對應(yīng)的pdb文件。在Windbg中輸入lm vm TestDlg命令,可以查看到TestDlg.exe文件的詳細(xì)信息,其中就包含文件的時間戳:(當(dāng)前的lm命令中使用m通配符參數(shù),所以在TestDlg后面加上了號)
image.png
可以看到文件是2022年6月25日8點26分23秒生成的,就可以找到對應(yīng)時間點的pdb文件了。
一般在公司正式的項目中,通過自動化軟件編譯系統(tǒng),每天都會自動編譯軟件版本,并將軟件的安裝包及相關(guān)模塊的pdb文件保存到文件服務(wù)器中,如下所示:
這樣我們就可以根據(jù)模塊的編譯時間找到對應(yīng)版本的pdb文件了。
我們找到了TestDlg.exe對應(yīng)的pdb文件TestDlg.pdb,將其所在的路徑設(shè)置到Windbg中。點擊Windbg菜單欄中的File->Symbol File Path…,打開設(shè)置pdb文件路徑的窗口,將pdb文件的路徑設(shè)置進(jìn)去,如下所示:
image.png
點擊OK按鈕之前,最好勾選上Reload選項,這樣Windbg就會去自動加載pdb文件了。但有時勾選了該選項,好像不會自動去加載,我們就需要使用.reload /f TestDlg.exe命令去讓W(xué)indbg強制去加載pdb文件(命令中必須是包含文件后綴的文件全名)。
設(shè)置完成后,我們可以再次運行l(wèi)m vm TestDlg*命令去看看pdb文件有沒有加載進(jìn)來:
image.png
如果已經(jīng)加載進(jìn)來,則會在上圖中的位置顯示出已經(jīng)加載進(jìn)來的pdb文件的完整路徑。,如上所示。
加載到TestDlg.exe文件對應(yīng)的pdb文件之后,我們再次執(zhí)行kn命令就可以包含具體的函數(shù)名及及代碼的行號信息了,如下:
image.png
我們看到了具體的函數(shù)名CTestDlgDlg::OnBnClickedButton1,還看到了對應(yīng)的代碼行號312。通過這些信息,我們就能到源代碼中找到對應(yīng)的位置了,如下所示:
image.png
是訪問了空指針產(chǎn)生的異常。當(dāng)然上面的代碼是我們故意這樣寫的,目的是為了構(gòu)造一個異常來詳細(xì)講解如何使用Windbg進(jìn)行動態(tài)調(diào)試跟蹤的。
6、將C++源代碼路徑設(shè)置到Windbg中,Windbg會自動跳轉(zhuǎn)到源代碼行號上
為了方便查看,我們可以直接在Windbg中設(shè)置C++源碼路徑,這樣Windbg會自動跳轉(zhuǎn)到源碼對應(yīng)的位置。點擊Windbg菜單欄的File->Source File Path…,將源碼路徑設(shè)置進(jìn)去:
image.png
然后Windbg會自動跳轉(zhuǎn)到對應(yīng)的函數(shù)及行號上:
image.png
然后點擊函數(shù)調(diào)用堆棧中每行最前面的數(shù)字超鏈接,就可以自動切換到對應(yīng)的函數(shù)中。上圖中的函數(shù)調(diào)用堆棧中很多模塊是系統(tǒng)庫中的,比如mfc100u、User32等,這些庫是系統(tǒng)庫,是沒有源碼的。我們可以點擊最下面的第23個鏈接,其位于我們應(yīng)用程序的模塊中,會自動跳轉(zhuǎn)到對應(yīng)的代碼中,如下:
image.png
7、有時需要查看函數(shù)調(diào)用堆棧中函數(shù)的局部變量或C++類對象中變量的值
有時我們通過查看變量的值,找到排查問題的線索,比如變量中值為0或者很大的異常值。這點我們在多次問題排查中使用到,確實能找到一些線索。
可以查看函數(shù)中局部變量的值,也可以查看函數(shù)所在類對象的this指針指向的類對象中變量的值。我們要查看哪個函數(shù),就點擊函數(shù)調(diào)用堆棧中每一行前面的數(shù)字超鏈接,如下所示:
image.png
我們看到了局部變量pShExeInfo 的值:
struct _SHELLEXECUTEINFOW * pShExeInfo = 0x00000000
我們可以點擊this對象的超鏈接:
image.png
就能查看當(dāng)前函數(shù)對應(yīng)的C++類對象中成員變量的值,如下:
image.png
但有時不一定能查看變量的值,因為當(dāng)前通過異常捕獲模塊自動生成的dump文件一般是minidump文件,文件也就幾MB左右,不可能包含所有變量的值。所以要在minidump文件中查看變量的值,要看運氣的,有時能查看到,有時是看不到的。這里要講一下dump文件的分類,主要分為minidump文件和全dump文件。
我們將windbg附加到進(jìn)程上使用.dump命令導(dǎo)出的dump文件,是全dump文件,全dump文件中包含了所有的信息,可以查看到所有變量的信息。另外通過任務(wù)管理器導(dǎo)出的dump文件:
image.png
也是全dump文件。全dump文件因為包含了所有的信息,所以會比較大,會達(dá)到數(shù)百MB,甚至上GB的大小。但如果通過安裝在程序的異常捕獲模塊CrashReport導(dǎo)出的dump文件就是非全dump文件,是mini dump文件,大概只有幾MB左右,因為異常捕獲模塊捕獲到異常后,會自動導(dǎo)出dump文件,保存到磁盤上,如果都導(dǎo)出體量很大的全dump文件,很大量消耗用戶的磁盤空間,所以我們會設(shè)置生成mini dump文件。
在異常捕獲模塊中我們是通過調(diào)用系統(tǒng)API函數(shù)MiniDumpWriteDump導(dǎo)出dump文件的,我們通過設(shè)置不同的函數(shù)調(diào)用參數(shù)去控制生成mini dump文件的。
8、有時我們需要用IDA打開二進(jìn)制文件去查看匯編代碼上下文
有時通過函數(shù)調(diào)用堆棧中函數(shù)及行號,我們很難搞清楚到底為什么會發(fā)生崩潰,這時候我們就需要回歸本源了,就需要去查看發(fā)生異常崩潰的那條匯編指令的上下文了。
匯編指令最能直觀地反映出問題的本質(zhì),通過閱讀匯編代碼,就能搞清楚為啥會觸發(fā)崩潰了。一般我們在使用.ecxr命令切換到發(fā)生異常的匯編指令時,我們可以直接在該條匯編指令的上下文了,點擊菜單欄的View->Disassembly,即可打開顯示匯編代碼的頁面,如下所示:
image.png
但直接在Windbg中查看匯編代碼,對于我們這些不精通匯編代碼的人來說,是有很大困難的,我們是大概率看不懂的。
一般需要借助IDA工具打開二進(jìn)制文件去查看匯編代碼的,IDA在解析出匯編時會在匯編代碼中加上一些注釋,特別是在有pdb符號庫文件時,會添加更多的注釋,通過這些注釋,我們就能對照了C++源代碼,就能大概讀懂匯編上下文了。如果能找到pdb符號庫文件,則需要將pdb文件放在目標(biāo)二進(jìn)制文件的同級目錄中,IDA打開二進(jìn)制文件時會去自動加載pdb文件。
8.1、IDA工具介紹
image.png
IDA是比利時Hex-Rays公司出品的一款交互式靜態(tài)反匯編工具。它可以直接反匯編出二進(jìn)制文件的匯編代碼,是目前軟件逆向與安全分析領(lǐng)域最好用、最強大的一個靜態(tài)反匯編軟件,已成為眾多軟件安全分析人員不可缺少的利器!它支持Windows、Linux等多個平臺,支持Intel X84、X64、ARM、MIPS等數(shù)十種CPU指令集。
在實際工作中,我們一般使用反匯編工具IDA去打開二進(jìn)制文件,查看二進(jìn)制文件中的匯編代碼。IDA既支持打開Windows下的.exe、.dll等二進(jìn)制文件,也可以打開Linux下的.bin、.so等二進(jìn)制文件。
8.2、使用IDA打開二進(jìn)制文件
IDA安裝完成后,雙擊啟動程序,會彈出如下的提示框:
image.png
點擊“New”即新建一個對象。緊接著彈出讓選擇要打開的文件:
image.png
可以找到目標(biāo)文件的路徑,打開目標(biāo)文件即可。也可以點擊取消,然后直接將文件拖到IDA中。打開文件時會讓選擇加載文件的方式:
image.png
對于Windows庫,選擇PE方式即可。接下來,會彈出是否要加載pdb文件的提示框:
image.png
選擇Yes即可。前面我們說過,需要將pdb文件放置到目標(biāo)二進(jìn)制文件的同一級目錄中,這樣IDA在打開二進(jìn)制文件時就會搜索到對應(yīng)的pdb文件,回去自動加載pdb文件。
打開后的效果如下:
image.png
默認(rèn)是先是Grapg View圖狀關(guān)系視圖,需要右鍵點擊視圖區(qū)域,在彈出的右鍵菜單中點擊Text View菜單項,切換到Text View文字視圖,這樣就能看到具體的匯編代碼了:
image.png
8.3、在有pdb文件時到IDA中定位到發(fā)生異常的匯編指令的位置
Windbg中發(fā)生異常的那條匯編指令,我們需要到IDA中找到對應(yīng)的位置,然后查看目標(biāo)位置的匯編指令的上下文。在目標(biāo)二進(jìn)制文件有pdb的情況下,要在IDA中定位到發(fā)生異常的匯編指令的位置,會比較簡單。
首先在Windbg中顯示的函數(shù)調(diào)用堆棧中找到發(fā)生崩潰的那條匯編指令所在的函數(shù)名CTestDlgDlg::OnBnClickedButton1,如下:
image.png
然后到IDA中,點擊菜單欄的Jump->Jumptofunction…,在打開的函數(shù)列表窗口中,點擊下方的Search按鈕,在搜索框中輸入函數(shù)名后搜索:
image.png
在列表中找到函數(shù),雙擊之久切換到該函數(shù)的匯編代碼處:
image.png
這樣就看到該函數(shù)的代碼段地址,然后加上Windbg中顯示的偏移值:
image.png
即:
CTestDlgDlg::OnBnClickedButton1+0x32 = 0x00401A30 + 0x32 = 0x00401A62
就得到發(fā)生崩潰的那條匯編指令在當(dāng)前二進(jìn)制中的地址,因為匯編指令就為在當(dāng)前這個函數(shù)中,鼠標(biāo)向下拉動找到地址即可,這樣就能找到發(fā)生異常的那條匯編指令了。
其實還有個快捷操作,在計算出發(fā)生異常的那條匯編指令的地址后,按下快捷鍵G,在彈出窗口中輸入剛才的地址0x00401A62,就可以直接go到發(fā)生異常的那條匯編指令的位置,如下:
image.png
8.4、在沒有pdb文件時到IDA中定位到發(fā)生異常的匯編指令的位置
在沒有目標(biāo)二進(jìn)制文件的pdb文件的情況下,要在IDA中定位到發(fā)生異常的匯編指令的位置,會相對麻煩一點。
發(fā)生異常的匯編指令的地址,是程序?qū)嶋H運行時的代碼段地址,需要在Windbg中計算出該條匯編指令的地址相對于所在模塊起始地址的偏移值,然后加上IDA中該模塊的的默認(rèn)加載地址,就能得到當(dāng)前匯編指令在IDA中的靜態(tài)地址,然后直接go過去,就能看到產(chǎn)生異常的匯編指令的上下文了。在本例中,發(fā)生異常的匯編指令的地址就是0x00401a62,如下:(發(fā)生異常的這行匯編指令最前面的那個地址,就是當(dāng)前匯編指令的地址)
image.png
從函數(shù)調(diào)用堆棧上看,當(dāng)前這條發(fā)生異常的匯編指令所在的函數(shù)為:
TestDlg!CTestDlgDlg::OnBnClickedButton1+0x32
所以這條指令所屬模塊為TestDlg.exe,所以使用lm命令查看TestDlg.exe模塊的起始地址:
image.png
TestDlg.exe模塊其實地址為0x00400000(這是系統(tǒng)將TestDlg.exe模塊加載到進(jìn)程空間中的起始地址,代碼段地址 ),所以發(fā)生異常的這條匯編指令相對于所在模塊TestDlg.exe的偏移地址為:
0x00401a62 – 0x00400000 = 0x00001a62
然后再到打開TestDlg.exe二進(jìn)制文件的IDA中,查看該TestDlg.exe模塊默認(rèn)的加載地址:(將滾輪滾動到最上面即可看到,注意此處是默認(rèn)預(yù)加載地址,并不是加載起來后的真正的地址)
image.png
這樣根據(jù)之前計算出來的偏移,加上TestDlg.exe模塊的默認(rèn)加載地址,就得到發(fā)生異常的那條匯編指令在IDA打開的靜態(tài)文件中的位置:
0x00001a62 + 0x00400000 = 0x00401a62
然后到IDA中按下G快捷鍵,GO到0x00401a62地址處,即找到發(fā)生異常的那條匯編指令:
image.png
在IDA中找到發(fā)生異常的匯編指令的位置,就可以去查看其附近的匯編代碼上下文了。
8.5、閱讀匯編代碼上下文
我們在閱讀匯編代碼的上下文時,一般是對照著C++源碼進(jìn)行的,然后依托匯編代碼上下文中的注釋,找到匯編代碼與C++源碼的對應(yīng)關(guān)系。在閱讀匯編代碼時,要了解一些常用的匯編指令及常用寄存器的使用(比如EAX用來存放函數(shù)的返回值,ECX用來傳遞C++對象地址的),熟悉函數(shù)調(diào)用時的參數(shù)入棧、棧分布及參數(shù)尋址,了解內(nèi)存拷貝的匯編代碼實現(xiàn)、了解虛函數(shù)調(diào)用的二次尋址的過程,去啃發(fā)生異常的那條匯編指令的上下文中的匯編代碼。
匯編指令比較多,我們只需要了解一些常用的匯編指令即可,如果遇到不熟悉的匯編指令去搜索一下就可以了。此外,有一點需要注意的是,在Release下編譯器會對C++源碼會做優(yōu)化,部分C++源碼可能會被優(yōu)化掉,C++源碼有時不能完全和匯編代碼對應(yīng)起來的,但這基本不影響匯編代碼上下文的閱讀。