如何通過崩潰地址找到出錯的代碼行作爲程序員,我們平時最擔心見到的事情是什麼?是內存泄漏?是界面不好看?
是,程序發生了崩潰!
經過下面四步,輕鬆解決上述難題:
第一:生成MAP文件;
第二:查找崩潰的內存地址;
第三:定位異常代碼出錯函數;
第四:定位出錯行號。
下面詳細介紹具體步驟:
第一:生成MAP文件
首先必須生成程序的MAP文件。什麼是MAP文件?
簡單地講, MAP文件是程序的全局符號、源文件和代碼行號信息的唯一的文本表示方法,它可以在任何地方、任何時候使用,不需要有額外的程序進行支持。
如何生成MAP呢?
在VC6.0中,我們可以按下Alt+F7,打開“Project Settings”選項頁,選擇C/C++選項卡,並在最下面的 Project Options 裏面輸入:/Zd,然後要選擇Link選項卡,在最下面的Project Options裏面輸入:/mapinfo:lines 和 /map:PROJECT_NAME.map。最後按下F7來編譯生成EXE可執行文件和MAP文件。
在此我先解釋一下加入的參數的含義:
/Zd 表示在編譯的時候生成行信息
/map[:filename] 表示生成MAP文件的路徑和文件名
/mapinfo:lines
表示生成MAP文件時,加入行信息
/mapinfo:exports表示生成MAP文件時,加入exported functions(如果生成的是DLL文件,就要加上)
生成的MAP文件在項目文件夾下,文件名爲:PROJECT_NAME.map。
第二:查找崩潰的內存地址;
OK,通過上面的步驟,我們已經得到了MAP文件,那麼我們該如何利用它呢?讓我們從簡單的實例入手,請打開你的VC,新建這樣一個例子文件:
2 void Crash(void)
3 {
4 int i = 1;
5 int j = 0;
6 i /= j;
7 }
8
9 void main(void)
10 {
11 Crash();
12 }
很顯然本程序有“除0錯誤”(墳墓不能爲0),在Debug方式下編譯的話,運行時肯定會產生“非法操作”。好,讓我們運行它,果然,“非法操作”對話框出現了,這時我們點擊“詳細信息”按鈕,記錄下產生崩潰的地址--在我的機器上是0x00401046。
如何查看異常“詳細信息”?
在調試狀態下,菜單欄:查看->調試窗口->Dissasembly。或則直接Alt+8。
第三:定位異常代碼出錯函數;
在MAP文件中:(由於文件內容太長,中間沒用的部分我進行了省略),MAP文件可以用文本編輯器打開
addressError
Timestamp is 54abf59b(Tue Jan 06 22:47:55 2015)
Preferred load address is 00400000
Start Length Name Class
0001:00000000 0001055cH .text CODE
0002:00000000 00001380H .rdata DATA
0002:00001380 00000000H .edata DATA
0003:00000000 00000104H .CRT$XCA DATA
0003:00000104 00000104H .CRT$XCZ DATA
0003:00000208 00000104H .CRT$XIA DATA
0003:0000030c 00000109H .CRT$XIC DATA
0003:00000418 00000104H .CRT$XIZ DATA
0003:0000051c 00000104H .CRT$XPA DATA
0003:00000620 00000104H .CRT$XPX DATA
0003:00000724 00000104H .CRT$XPZ DATA
0003:00000828 00000104H .CRT$XTA DATA
0003:0000092c 00000104H .CRT$XTZ DATA
0003:00000a30 00003220H .data DATA
0003:00003c50 000019a8H .bss DATA
0004:00000000 00000014H .idata$2 DATA
0004:00000014 00000014H .idata$3 DATA
0004:00000028 00000120H .idata$4 DATA
0004:00000148 00000120H .idata$5 DATA
0004:00000268 000004f4H .idata$6 DATA
Address Publics by Value Rva+Base Lib:Object
0001:00000020 ?Crash@@YAXXZ 00401020 f addressError.obj
0001:00000050 _main 00401050 f addressError.obj
0001:00000080 __chkesp 00401080 f LIBCD:chkesp.obj
0001:000000c0 _mainCRTStartup 004010c0 f LIBCD:crt0.obj
0001:000001f0 __amsg_exit 004011f0 f LIBCD:crt0.obj
0001:00000250 __CrtDbgBreak 00401250 f LIBCD:dbgrpt.obj
0001:00000260 __CrtSetReportMode 00401260 f LIBCD:dbgrpt.obj
0001:000002c0 __CrtSetReportFile 004012c0 f LIBCD:dbgrpt.obj
0001:00000340 __CrtSetReportHook 00401340 f LIBCD:dbgrpt.obj
0001:00000360 __CrtDbgReport 00401360 f LIBCD:dbgrpt.obj
….
entrypoint at 0001:000000c0
Linenumbers for .\Debug\addressError.obj(E:\addressError\addressError.cpp) segment.text
3 0001:00000020 4 0001:00000034 5 0001:0000003b 6 0001:00000042
7 0001:0000004c 10 0001:00000050 110001:00000053 12 0001:00000058
仔細瀏覽Rva+Base這欄,你會發現第一個比崩潰地址0x00401046大的函數地址是00401050,所以在0x00401050這個地址之前的那個入口就是產生崩潰的函數,也就是這行:
0001:00000020 ?Crash@@YAXXZ 00401020 f addressError.obj
因此,發生崩潰的函數就是 ?Crash@@YAXXZ,所有以問號開頭的函數名稱都是C++修飾的名稱。在我們的源程序中,也就是Crash()這個子函數。
第四:定位出錯行號
請注意MAP文件的最後部分--代碼行信息(Line numbers information),它是以這樣的形式顯示的:
3 0001:00000020
第一個數字代表在源代碼中的代碼行號,第二個數是該代碼行在所屬的代碼段中的偏移量。
如果要查找代碼行號,需要使用下面的公式做一些十六進制的減法運算:
崩潰地址 = 偏移地址(Rva)+基地址(Base)
崩潰行偏移 = 崩潰地址(Crash Address)- 基地址(ImageBaseAddress)- 0x1000
爲什麼要這樣做呢?細心的朋友可能會留意到Rva+Base這欄了,我們得到的崩潰地址都是由偏移地址(Rva)+基地址(Base)得來的,所以在計算行號的時候要把基地址減去.
一般情況下,基地址的值是0x00400000(可由MAP文件的第五行得到:Preferred load address is00400000)。另外,由於一般的PE文件的代碼段都是從0x1000(可由MAP文件的Line numbers information得到:3 0001:00000020)偏移開始的,所以也必須減去0x1000。
崩潰行偏移 = 0x00401046-0x00400000 - 0x1000 = 0x46
如果瀏覽MAP文件的代碼行信息,會看到不超過計算結果,但卻最接近的數是 CrashDemo.cpp 文件中的:
6 0001:00000042 也就是在源代碼中的第6行,讓我們來看看源代碼:16 i /= j;果然就是第6行!
成功查找完畢。
注:VS2005等的查找方法類似,自己思考下就行,不懂的可以留言聯繫。