身為程式設計師,平時最擔心看到什麼?記憶體洩漏?介面難看?錯了!我相信沒人會反對——那就是程式發生崩潰!
「該程式執行了非法操作,即將關閉。請與你的軟體供應商聯繫。」這句 Microsoft 的「名言」,恐怕是程式設計師最不想見到的。有時自己程式在自己機器跑得好好的,到了別人機器就崩潰;有時自己開發測試過程中莫名遇到非法操作,卻無法確定到底是原始碼哪一行引起的……很痛苦吧?別緊張,本文幫你走出困境,甚至事後你可自豪要求使用者提供崩潰位址,然後精確定位到原始碼出錯那一行(很神奇吧?)
首先必須產生程式的 MAP 檔案。什麼是 MAP 檔案?簡單說,MAP 檔案是程式全域符號、原始檔案與程式碼行號資訊的唯一純文字表示,任何地方、任何時候可用,無需額外程式支援。而且是找出程式崩潰位置的唯一救星。
VC 環境:按 Alt+F7 開啟「Project Settings」,選 C/C++ 索引標籤,最下方 Project Options 輸入 /Zd;選 Link 索引標籤,最下方 Project Options 輸入 /mapinfo:lines 和 /map:PROJECT_NAME.map。按 F7 編譯產生 EXE 與 MAP 檔。
MASM 環境:設定編譯連結參數,通常寫成 makem.bat:
rc %1.rc
ml /c /coff /Zd %1.asm
link /subsystem:windows /mapinfo:exports /mapinfo:lines /map:%1.map %1.obj %1.res
命令列輸入 makem filename 編譯產生 EXE 與 MAP 檔。
參數說明:
/Zd:編譯時產生行號資訊/map[:filename]:產生 MAP 檔路徑與檔名/mapinfo:lines:MAP 檔加入行號資訊/mapinfo:exports:MAP 檔加入 exported functions(產生 DLL 時需加入)VC 新建檔案:
01 //****************************************************************
02 // 程式名稱:演示如何透過崩潰位址找出原始碼出錯行
03 // 作者:羅聰
04 // 日期:2003-2-7
05 // 出處:https://www.luocong.com
06 // 本程式會產生「除 0 錯誤」導致「非法操作」對話框
07 // 注意:轉載請保持完整並註明出處
08 //****************************************************************
09
10 void Crash(void)
11 {
12 int i = 1;
13 int j = 0;
14 i /= j; // 第 16 行:除零錯誤
15 }
16
17 void main(void)
18 {
19 Crash();
20 }
Debug 方式編譯執行,點擊「詳細資訊」記錄崩潰位址——作者機器上為 0x0040104a。
MAP 檔案關鍵區段:
Address Publics by Value Rva+Base Lib:Object
0001:00000020 ?Crash@@YAXXZ 00401020 f CrashDemo.obj
0001:00000070 _main 00401070 f CrashDemo.obj
...
Line numbers for .\Debug\CrashDemo.obj(d:\msdev\myprojects\crashdemo\crashdemo.cpp) segment .text
13 0001:00000020
14 0001:00000038
15 0001:0000003f
16 0001:00000046
17 0001:00000050
20 0001:00000070
21 0001:00000088
22 0001:0000008d
步驟 1:找出崩潰函式
瀏覽 Rva+Base 欄,第一個比崩潰位址 0x0040104a 大的函式位址是 0x00401070 (_main),所以它之前的那個入口即為崩潰函式:
0001:00000020 ?Crash@@YAXXZ 00401020 f CrashDemo.obj
發生崩潰的函式為 ?Crash@@YAXXZ(C++ 修飾名稱),對應原始碼 Crash() 函式。
步驟 2:精確定位崩潰行號
MAP 檔最後部分為程式碼行號資訊:行號 段:偏移量。計算崩潰行偏移公式:
崩潰行偏移 = 崩潰位址 - 基底位址 - 0x1000
原因:崩潰位址 = RVA + Base,基底位址通常 0x00400000,PE 檔案程式碼段通常從 0x1000 偏移開始。
計算:0x0040104a - 0x00400000 - 0x1000 = 0x4a
瀏覽行號資訊,不超過 0x4a 但最接近的是:
16 0001:00000046
即原始碼第 16 行:i /= j; —— 完全正確!
方法介紹完畢。以後可精確定位原始碼崩潰行,只要編譯器能產生 MAP 檔(VC、MASM、VB、BCB、Delphi…)皆適用。Microsoft 無意間提供許多有價值資訊,只看我們是否善用。現在面對「非法操作」更從容,甚至可要求使用者提供崩潰位址,在家舒服找到出錯行並修正。很爽吧?:)