1. 程式人生 > >讓程式在崩潰時體面的退出之CallStack(轉)

讓程式在崩潰時體面的退出之CallStack(轉)

在我的那篇《讓程式在崩潰時體面的退出之Unhandled Exception》中提供了一個捕捉程式崩潰事件的方法,可以新增程式碼在程式崩潰的時候做出適當的處理。不過,只知道程式在什麼時候崩潰,但是不知道為什麼崩潰,這對於程式開發者來說沒有任何意義。因為如果不知道程式崩潰的原因,就沒法去找到程式碼中的缺陷,當然就沒法去修改程式碼而避免程式的崩潰。
        所有除錯過程式碼的開發者都知道CallStack的重要性。如果在程式崩潰的時候得到CallStack,那麼就能定位程式崩潰的具體位置,並最終找到解決方法。那麼有沒有什麼方法在程式崩潰的時候得到CallStack呢?答案是肯定的。微軟提供了一個DbgHelp.dll,裡面包含了一系列的Windows API來供開發者呼叫。它是一個除錯跟蹤相關的模組,用於跟蹤程序工作,在程序崩潰時收集程式產生異常時的堆疊資訊,以供開發人員分析,從而很快找出使程式出現異常的原因。
        下面用具體的例子程式碼來說明怎樣使用DbgHelp.dll中的Windows API來得到CallStack。程式碼裡面有詳細的註釋來幫助理解。
        用VC建立一個名為Test的控制檯程式,新增下面的程式碼。

  1. // 一個有函式呼叫的類
  2. // 
  3. class CrashTest  
  4. {  
  5. public:  
  6.     void Test()   
  7.     {   
  8.         Crash();   
  9.     }  
  10. private:  
  11.     void Crash()   
  12.     {   
  13.         // 除零,人為的使程式崩潰
  14.         //
  15.         int i = 13;  
  16.         int j = 0;  
  17.         int m = i / j;  
  18.     }  
  19. };  
  20. int _tmain(int argc, _TCHAR* argv[])  
  21. {  
  22.     CrashTest test;  
  23.     test.Test();  
  24.     return 0;  
  25. }  

        編譯上面的程式碼,到工程的Debug目錄下找到編譯好的Test.exe,雙擊執行,程式就會崩潰。從程式碼我們知道函式呼叫順序是main() -> CrashTest::Test() -> CrashTest::Crash()。那麼怎麼在程式崩潰的時候得到CallStack呢?首先,先新增一些工具函式。

  1. #include <Windows.h>
  2. #include <DbgHelp.h>
  3. #include <iostream>
  4. #include <vector>
  5. // 新增對dbghelp.lib的編譯依賴
  6. //
  7. #pragma comment(lib, "dbghelp.lib")
  8. usingnamespace std;  
  9. constint MAX_ADDRESS_LENGTH = 32;  
  10. constint MAX_NAME_LENGTH = 1024;  
  11. // 崩潰資訊
  12. // 
  13. struct CrashInfo  
  14. {  
  15.     CHAR ErrorCode[MAX_ADDRESS_LENGTH];  
  16.     CHAR Address[MAX_ADDRESS_LENGTH];  
  17.     CHAR Flags[MAX_ADDRESS_LENGTH];  
  18. };  
  19. // CallStack資訊
  20. // 
  21. struct CallStackInfo  
  22. {  
  23.     CHAR ModuleName[MAX_NAME_LENGTH];  
  24.     CHAR MethodName[MAX_NAME_LENGTH];  
  25.     CHAR FileName[MAX_NAME_LENGTH];  
  26.     CHAR LineNumber[MAX_NAME_LENGTH];  
  27. };  
  28. // 安全拷貝字串函式
  29. //
  30. void SafeStrCpy(char* szDest, size_t nMaxDestSize, constchar* szSrc)  
  31. {  
  32.     if (nMaxDestSize <= 0) return;  
  33.     if (strlen(szSrc) < nMaxDestSize)  
  34.     {  
  35.         strcpy_s(szDest, nMaxDestSize, szSrc);  
  36.     }  
  37.     else
  38.     {  
  39.         strncpy_s(szDest, nMaxDestSize, szSrc, nMaxDestSize);  
  40.         szDest[nMaxDestSize-1] = '\0';  
  41.     }  
  42. }    
  43. // 得到程式崩潰資訊
  44. //
  45. CrashInfo GetCrashInfo(const EXCEPTION_RECORD *pRecord)  
  46. {  
  47.     CrashInfo crashinfo;  
  48.     SafeStrCpy(crashinfo.Address, MAX_ADDRESS_LENGTH, "N/A");  
  49.     SafeStrCpy(crashinfo.ErrorCode, MAX_ADDRESS_LENGTH, "N/A");  
  50.     SafeStrCpy(crashinfo.Flags, MAX_ADDRESS_LENGTH, "N/A");  
  51.     sprintf_s(crashinfo.Address, "%08X", pRecord->ExceptionAddress);  
  52.     sprintf_s(crashinfo.ErrorCode, "%08X", pRecord->ExceptionCode);  
  53.     sprintf_s(crashinfo.Flags, "%08X", pRecord->ExceptionFlags);  
  54.     return crashinfo;  
  55. }  
  56. // 得到CallStack資訊
  57. //
  58. vector<CallStackInfo> GetCallStack(const CONTEXT *pContext)  
  59. {  
  60.     HANDLE hProcess = GetCurrentProcess();  
  61.     SymInitialize(hProcess, NULL, TRUE);  
  62.     vector<CallStackInfo> arrCallStackInfo;  
  63.     CONTEXT c = *pContext;  
  64.     STACKFRAME64 sf;  
  65.     memset(&sf, 0, sizeof(STACKFRAME64));  
  66.     DWORD dwImageType = IMAGE_FILE_MACHINE_I386;  
  67.     // 不同的CPU型別,具體資訊可查詢MSDN
  68.     //
  69. #ifdef _M_IX86
  70.     sf.AddrPC.Offset = c.Eip;  
  71.     sf.AddrPC.Mode = AddrModeFlat;  
  72.     sf.AddrStack.Offset = c.Esp;  
  73.     sf.AddrStack.Mode = AddrModeFlat;  
  74.     sf.AddrFrame.Offset = c.Ebp;  
  75.     sf.AddrFrame.Mode = AddrModeFlat;  
  76. #elif _M_X64
  77.     dwImageType = IMAGE_FILE_MACHINE_AMD64;  
  78.     sf.AddrPC.Offset = c.Rip;  
  79.     sf.AddrPC.Mode = AddrModeFlat;  
  80.     sf.AddrFrame.Offset = c.Rsp;  
  81.     sf.AddrFrame.Mode = AddrModeFlat;  
  82.     sf.AddrStack.Offset = c.Rsp;  
  83.     sf.AddrStack.Mode = AddrModeFlat;  
  84. #elif _M_IA64
  85.     dwImageType = IMAGE_FILE_MACHINE_IA64;  
  86.     sf.AddrPC.Offset = c.StIIP;  
  87.     sf.AddrPC.Mode = AddrModeFlat;  
  88.     sf.AddrFrame.Offset = c.IntSp;  
  89.     sf.AddrFrame.Mode = AddrModeFlat;  
  90.     sf.AddrBStore.Offset = c.RsBSP;  
  91.     sf.AddrBStore.Mode = AddrModeFlat;  
  92.     sf.AddrStack.Offset = c.IntSp;  
  93.     sf.AddrStack.Mode = AddrModeFlat;  
  94. #else
  95.     #error "Platform not supported!"
  96. #endif
  97.     HANDLE hThread = GetCurrentThread();  
  98.     while (true)  
  99.     {  
  100.         // 該函式是實現這個功能的最重要的一個函式
  101.         // 函式的用法以及引數和返回值的具體解釋可以查詢MSDN
  102.         //
  103.         if (!StackWalk64(dwImageType, hProcess, hThread, &sf, &c, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL))  
  104.         {  
  105.             break;  
  106.         }  
  107.         if (sf.AddrFrame.Offset == 0)  
  108.         {  
  109.             break;  
  110.         }  
  111.         CallStackInfo callstackinfo;  
  112.         SafeStrCpy(callstackinfo.MethodName, MAX_NAME_LENGTH, "N/A");  
  113.         SafeStrCpy(callstackinfo.FileName, MAX_NAME_LENGTH, "N/A");  
  114.         SafeStrCpy(callstackinfo.ModuleName, MAX_NAME_LENGTH, "N/A");  
  115.         SafeStrCpy(callstackinfo.LineNumber, MAX_NAME_LENGTH, "N/A");  
  116.         BYTE symbolBuffer[sizeof(IMAGEHLP_SYMBOL64) + MAX_NAME_LENGTH];  
  117.         IMAGEHLP_SYMBOL64 *pSymbol = (IMAGEHLP_SYMBOL64*)symbolBuffer;  
  118.         memset(pSymbol, 0, sizeof(IMAGEHLP_SYMBOL64) + MAX_NAME_LENGTH);  
  119.         pSymbol->SizeOfStruct = sizeof(symbolBuffer);  
  120.         pSymbol->MaxNameLength = MAX_NAME_LENGTH;  
  121.         DWORD symDisplacement = 0;  
  122.         // 得到函式名
  123.         //
  124.         if (SymGetSymFromAddr64(hProcess, sf.AddrPC.Offset, NULL, pSymbol))  
  125.         {  
  126.             SafeStrCpy(callstackinfo.MethodName, MAX_NAME_LENGTH, pSymbol->Name);  
  127.         }  
  128.         IMAGEHLP_LINE64 lineInfo;  
  129.         memset(&lineInfo, 0, sizeof(IMAGEHLP_LINE64));  
  130.         lineInfo.SizeOfStruct = sizeof(IMAGEHLP_LINE64);  
  131.         DWORD dwLineDisplacement;  
  132.         // 得到檔名和所在的程式碼行
  133.         //
  134.         if (SymGetLineFromAddr64(hProcess, sf.AddrPC.Offset, &dwLineDisplacement, &lineInfo))  
  135.         {  
  136.             SafeStrCpy(callstackinfo.FileName, MAX_NAME_LENGTH, lineInfo.FileName);  
  137.             sprintf_s(callstackinfo.LineNumber, "%d", lineInfo.LineNumber);  
  138.         }  
  139.         IMAGEHLP_MODULE64 moduleInfo;  
  140.         memset(&moduleInfo, 0, sizeof(IMAGEHLP_MODULE64));  
  141. 相關推薦

    程式崩潰體面退出CallStack()

    在我的那篇《讓程式在崩潰時體面的退出之Unhandled Exception》中提供了一個捕捉程式崩潰事件的方法,可以新增程式碼在程式崩潰的時候做出適當的處理。不過,只知道程式在什麼時候崩潰,但是不知道為什麼崩潰,這對於程式開發者來說沒有任何意義。因為如果不知道程式崩潰

    程式崩潰體面退出CallStack

            在我的那篇《讓程式在崩潰時體面的退出之Unhandled Exception》中提供了一個捕捉程式崩潰事件的方法,可以新增程式碼在程式崩潰的時候做出適當的處理。不過,只知道程式在什麼時候崩潰,但是不知道為什麼崩潰,這對於程式開發者來說沒有任何意義。因為如果不知

    程式崩潰體面退出Dump檔案

    在我的那篇《讓程式在崩潰時體面的退出之CallStack》中提供了一個在程式崩潰時得到CallStack的方法。可是要想得到CallStack,必須有pdb檔案的支援。但是一般情況下,釋出出去的程式都是 Release版本的,都不會附帶pdb檔案。那麼我們怎麼能在程式崩潰的

    程式崩潰體面退出Unhandled Exception

             程式是由程式碼編譯出來的,而程式碼是由人寫的。人非聖賢,孰能無過。所以由人寫的程式碼有缺陷是很正常的。當然很多異常都在開發階段被考慮到而添加了處理程式碼,或者用try/catch對可能

    使用Dump檔案程式崩潰體面退出

    在我的那篇《讓程式在崩潰時體面的退出之CallStack》中提供了一個在程式崩潰時得到CallStack的方法。可是要想得到CallStack,必須有pdb檔案的支援。但是一般情況下,釋出出去的程式都是Release版本的,都不會附帶pdb檔案。那麼我們怎麼能在程式崩潰的時

    如何防止後臺執行緒丟擲的異常程式崩潰退出

    如果你的程式拋了異常,你是怎麼處理的呢?等待程式崩潰退出?還是進行補救? 如果是做 UI 開發,很容易就找到 Dispatcher.UnhandledException 事件,然後在事件中進行補救。如果補救成功,可以設定 e.Handled = true 來阻

    windows程式崩潰自動生成dump檔案方法

      /****************第一步新增createdump.h********************************* 新增一個頭檔案:createdump.h #pragma once #include <windows.h> #inclu

    C++程式執行記憶體佈局----------區域性變數,全域性變數,靜態變數,函式程式碼,new出來的變數

    宣告兩點: (1)開發測試環境為VS2010+WindowsXP32位; (2)記憶體佈局指的是虛擬記憶體地址,不是實體地址。   1.測試程式碼 #include <iostream> using namespace std; int g_int_a; i

    利用Windows自帶的功能當程式崩潰產生崩潰儲檔案(dmp)

     何志丹 以管理員身份 執行 :OpenDump.bat 其本質是寫登錄檔。 執行後: 任何程式崩潰都會在C:\CrashDump 產生dmp檔案(比較大,約50到200M)。 至少在Win7、Win10的電腦,Win10的平板上執行正確。 OpenDump.bat @e

    gdb除錯命令及程式崩潰的核心存core dump

    1.gcc -g filename.c -o filename 需要生成帶除錯資訊的檔案 2.除錯   方式一:gdb filename 除錯file可執行檔案   方式二:>>gdb              >>file filename $gd

    windows 應用程式崩潰的記憶體儲及dump檔案的分析

    1、在現場設定程式崩潰時的自動記憶體轉儲,得到dump檔案        在windows 登錄檔如下項:      //HKEY_LOCAL_MACHINE/Software/Microsoft/Windows NT/CurrentVersion/AeDebug

    程式崩潰但是不閃退的方法(可以用在真機測試上)

    /** 讓程式崩潰但是不閃退的方法 */ void handException(NSException * exception){          /** 彈出提示框 */     UIAlertView * alert = [[UIAlertView alloc]in

    C++ 記錄Windows程式崩潰的dumpfile

    【原理】      windows程式當遇到異常,沒有try-catch或者try-catch也無法捕獲到的異常時,程式就會自動退出,如果這時候沒有dump檔案的話,我們是沒有得到任何程式退出的資訊。在windows程式異常退出之前,會預先呼叫一個在程式中註冊的異常處理回撥

    記錄程式崩潰的呼叫堆疊

    最近有個使用者遇到程式Crash問題,但我們的機器都不能重現,於是在網上搜了一把,發現有個MSJExceptionHandler類還比較好用,故整理了一下供大家參考。 這個類的使用方法很簡單,只要把這個類加入到你的工程(不管是MFC,com,dll都可以)中一起編譯就可以了

    程式崩潰生成Dump檔案

    Dump檔案是程序的記憶體映象,可以把程式執行時的狀態完整的儲存下來,之後通過除錯工具可查出崩潰大致原因。 SetUnhandledExceptionFilter()設定一個在程式崩潰時被呼叫的回撥函式。MiniDumpWriteDump()建立Dump檔案。 我寫了一個

    C++程式崩潰,生成dmp除錯檔案

    包含如下標頭檔案 和 引用Lib庫 #include <dbghelp.h> #pragma comment(lib, "dbghelp.lib")定義 LONG WINAPI MyUnh

    如何連結延幾秒後跳呢? 已解決

     js 用 setTimeout     引用當初做的專案一個例項   $('#real_submit').click(function () { $.ajax({ cach

    ArcGIS for Android 1.1 MapView 的Activity退出整個程式崩潰問題

    在專案中當我們從一個activity中跳轉到Mapview的activity中後,點選回退鍵後,等待不長時間程式崩潰問題,此時報call to OpenGL ES API with no current context (logged once per thread),

    關於QT在開啟子視窗程式崩潰的其中一個原因分析

    其實這個問題當時是糾結了我很長的一段時間,這段時間裡面,我一直在網上面找相關的資料但是卻沒有有用的資訊。 但是在後面的一個機緣巧合之下,我通過函式執行順序來Debug,慢慢的發現問題出現在什麼地方了。現在來總結一下這個問題吧。 其實我現在的經驗覺得,對於QT裡面(由於QT是基於C++的),不

    利用MapFile定位程式崩潰(報紅牌)的程式碼位置

    原文:http://www.codeproject.com/KB/debug/mapfile.aspx 1、生成MapFile Project—Setting—C+±—DebugInfo,選擇Line Numbers Only Project—Setting—Link—選擇Generat