让程序在崩溃时体面的退出之CallStack

 在我的那篇《让程序在崩溃时体面的退出之Unhandled Exception》中提供了一个捕捉程序崩溃事件的方法,可以添加代码在程序崩溃的时候做出适当的处理。不过,只知道程序在什么时候崩溃,但是不知道为什么崩溃,这对于程序开发者来说没有任何意义。因为如果不知道程序崩溃的原因,就没法去找到代码中的缺陷,当然就没法去修改代码而避免程序的崩溃。
        所有调试过代码的开发者都知道CallStack的重要性。如果在程序崩溃的时候得到CallStack,那么就能定位程序崩溃的具体位置,并最终找到解决方法。那么有没有什么方法在程序崩溃的时候得到CallStack呢?答案是肯定的。微软提供了一个DbgHelp.dll,里面包含了一系列的Windows API来供开发者调用。它是一个调试跟踪相关的模块,用于跟踪进程工作,在进程崩溃时收集程序产生异常时的堆栈信息,以供开发人员分析,从而很快找出使程序出现异常的原因。
        下面用具体的例子代码来说明怎样使用DbgHelp.dll中的Windows API来得到CallStack。代码里面有详细的注释来帮助理解。
        用VC创建一个名为Test的控制台程序,添加下面的代码。

[cpp]  view plain copy
  1. // 一个有函数调用的类  
  2. //   
  3. class CrashTest  
  4. {  
  5. public:  
  6.     void Test()   
  7.     {   
  8.         Crash();   
  9.     }  
  10.   
  11. private:  
  12.     void Crash()   
  13.     {   
  14.         // 除零,人为的使程序崩溃  
  15.         //  
  16.         int i = 13;  
  17.         int j = 0;  
  18.         int m = i / j;  
  19.     }  
  20. };  
  21.   
  22. int _tmain(int argc, _TCHAR* argv[])  
  23. {  
  24.     CrashTest test;  
  25.     test.Test();  
  26.   
  27.     return 0;  
  28. }  

        编译上面的代码,到工程的Debug目录下找到编译好的Test.exe,双击运行,程序就会崩溃。从代码我们知道函数调用顺序是main() -> CrashTest::Test() -> CrashTest::Crash()。那么怎么在程序崩溃的时候得到CallStack呢?首先,先添加一些工具函数。

[cpp]  view plain copy
  1. #include <Windows.h>  
  2. #include <DbgHelp.h>  
  3. #include <iostream>  
  4. #include <vector>  
  5.   
  6. // 添加对dbghelp.lib的编译依赖  
  7. //  
  8. #pragma comment(lib, "dbghelp.lib")  
  9.   
  10. using namespace std;  
  11.   
  12. const int MAX_ADDRESS_LENGTH = 32;  
  13. const int MAX_NAME_LENGTH = 1024;  
  14.   
  15. // 崩溃信息  
  16. //   
  17. struct CrashInfo  
  18. {  
  19.     CHAR ErrorCode[MAX_ADDRESS_LENGTH];  
  20.     CHAR Address[MAX_ADDRESS_LENGTH];  
  21.     CHAR Flags[MAX_ADDRESS_LENGTH];  
  22. };  
  23.   
  24. // CallStack信息  
  25. //   
  26. struct CallStackInfo  
  27. {  
  28.     CHAR ModuleName[MAX_NAME_LENGTH];  
  29.     CHAR MethodName[MAX_NAME_LENGTH];  
  30.     CHAR FileName[MAX_NAME_LENGTH];  
  31.     CHAR LineNumber[MAX_NAME_LENGTH];  
  32. };  
  33.   
  34. // 安全拷贝字符串函数  
  35. //  
  36. void SafeStrCpy(char* szDest, size_t nMaxDestSize, const char* szSrc)  
  37. {  
  38.     if (nMaxDestSize <= 0) return;  
  39.     if (strlen(szSrc) < nMaxDestSize)  
  40.     {  
  41.         strcpy_s(szDest, nMaxDestSize, szSrc);  
  42.     }  
  43.     else  
  44.     {  
  45.         strncpy_s(szDest, nMaxDestSize, szSrc, nMaxDestSize);  
  46.         szDest[nMaxDestSize-1] = '\0';  
  47.     }  
  48. }    
  49.   
  50. // 得到程序崩溃信息  
  51. //  
  52. CrashInfo GetCrashInfo(const EXCEPTION_RECORD *pRecord)  
  53. {  
  54.     CrashInfo crashinfo;  
  55.     SafeStrCpy(crashinfo.Address, MAX_ADDRESS_LENGTH, "N/A");  
  56.     SafeStrCpy(crashinfo.ErrorCode, MAX_ADDRESS_LENGTH, "N/A");  
  57.     SafeStrCpy(crashinfo.Flags, MAX_ADDRESS_LENGTH, "N/A");  
  58.   
  59.     sprintf_s(crashinfo.Address, "%08X", pRecord->ExceptionAddress);  
  60.     sprintf_s(crashinfo.ErrorCode, "%08X", pRecord->ExceptionCode);  
  61.     sprintf_s(crashinfo.Flags, "%08X", pRecord->ExceptionFlags);  
  62.   
  63.     return crashinfo;  
  64. }  
  65.   
  66. // 得到CallStack信息  
  67. //  
  68. vector<CallStackInfo> GetCallStack(const CONTEXT *pContext)  
  69. {  
  70.     HANDLE hProcess = GetCurrentProcess();  
  71.   
  72.     SymInitialize(hProcess, NULL, TRUE);  
  73.   
  74.     vector<CallStackInfo> arrCallStackInfo;  
  75.   
  76.     CONTEXT c = *pContext;  
  77.   
  78.     STACKFRAME64 sf;  
  79.     memset(&sf, 0, sizeof(STACKFRAME64));  
  80.     DWORD dwImageType = IMAGE_FILE_MACHINE_I386;  
  81.   
  82.     // 不同的CPU类型,具体信息可查询MSDN  
  83.     //  
  84. #ifdef _M_IX86  
  85.     sf.AddrPC.Offset = c.Eip;  
  86.     sf.AddrPC.Mode = AddrModeFlat;  
  87.     sf.AddrStack.Offset = c.Esp;  
  88.     sf.AddrStack.Mode = AddrModeFlat;  
  89.     sf.AddrFrame.Offset = c.Ebp;  
  90.     sf.AddrFrame.Mode = AddrModeFlat;  
  91. #elif _M_X64  
  92.     dwImageType = IMAGE_FILE_MACHINE_AMD64;  
  93.     sf.AddrPC.Offset = c.Rip;  
  94.     sf.AddrPC.Mode = AddrModeFlat;  
  95.     sf.AddrFrame.Offset = c.Rsp;  
  96.     sf.AddrFrame.Mode = AddrModeFlat;  
  97.     sf.AddrStack.Offset = c.Rsp;  
  98.     sf.AddrStack.Mode = AddrModeFlat;  
  99. #elif _M_IA64  
  100.     dwImageType = IMAGE_FILE_MACHINE_IA64;  
  101.     sf.AddrPC.Offset = c.StIIP;  
  102.     sf.AddrPC.Mode = AddrModeFlat;  
  103.     sf.AddrFrame.Offset = c.IntSp;  
  104.     sf.AddrFrame.Mode = AddrModeFlat;  
  105.     sf.AddrBStore.Offset = c.RsBSP;  
  106.     sf.AddrBStore.Mode = AddrModeFlat;  
  107.     sf.AddrStack.Offset = c.IntSp;  
  108.     sf.AddrStack.Mode = AddrModeFlat;  
  109. #else  
  110.     #error "Platform not supported!"  
  111. #endif  
  112.   
  113.     HANDLE hThread = GetCurrentThread();  
  114.   
  115.     while (true)  
  116.     {  
  117.         // 该函数是实现这个功能的最重要的一个函数  
  118.         // 函数的用法以及参数和返回值的具体解释可以查询MSDN  
  119.         //  
  120.         if (!StackWalk64(dwImageType, hProcess, hThread, &sf, &c, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL))  
  121.         {  
  122.             break;  
  123.         }  
  124.   
  125.         if (sf.AddrFrame.Offset == 0)  
  126.         {  
  127.             break;  
  128.         }  
  129.                   
  130.         CallStackInfo callstackinfo;  
  131.         SafeStrCpy(callstackinfo.MethodName, MAX_NAME_LENGTH, "N/A");  
  132.         SafeStrCpy(callstackinfo.FileName, MAX_NAME_LENGTH, "N/A");  
  133.         SafeStrCpy(callstackinfo.ModuleName, MAX_NAME_LENGTH, "N/A");  
  134.         SafeStrCpy(callstackinfo.LineNumber, MAX_NAME_LENGTH, "N/A");  
  135.   
  136.         BYTE symbolBuffer[sizeof(IMAGEHLP_SYMBOL64) + MAX_NAME_LENGTH];  
  137.         IMAGEHLP_SYMBOL64 *pSymbol = (IMAGEHLP_SYMBOL64*)symbolBuffer;  
  138.         memset(pSymbol, 0, sizeof(IMAGEHLP_SYMBOL64) + MAX_NAME_LENGTH);  
  139.   
  140.         pSymbol->SizeOfStruct = sizeof(symbolBuffer);  
  141.         pSymbol->MaxNameLength = MAX_NAME_LENGTH;  
  142.   
  143.         DWORD symDisplacement = 0;  
  144.           
  145.         // 得到函数名  
  146.         //  
  147.         if (SymGetSymFromAddr64(hProcess, sf.AddrPC.Offset, NULL, pSymbol))  
  148.         {  
  149.             SafeStrCpy(callstackinfo.MethodName, MAX_NAME_LENGTH, pSymbol->Name);  
  150.         }  
  151.   
  152.         IMAGEHLP_LINE64 lineInfo;  
  153.         memset(&lineInfo, 0, sizeof(IMAGEHLP_LINE64));  
  154.   
  155.         lineInfo.SizeOfStruct = sizeof(IMAGEHLP_LINE64);  
  156.   
  157.         DWORD dwLineDisplacement;  
  158.   
  159.         // 得到文件名和所在的代码行  
  160.         //  
  161.         if (SymGetLineFromAddr64(hProcess, sf.AddrPC.Offset, &dwLineDisplacement, &lineInfo))  
  162.         {  
  163.             SafeStrCpy(callstackinfo.FileName, MAX_NAME_LENGTH, lineInfo.FileName);  
  164.             sprintf_s(callstackinfo.LineNumber, "%d", lineInfo.LineNumber);  
  165.         }  
  166.   
  167.         IMAGEHLP_MODULE64 moduleInfo;  
  168.         memset(&moduleInfo, 0, sizeof(IMAGEHLP_MODULE64));  
  169.   
  170.         moduleInfo.SizeOfStruct = sizeof(IMAGEHLP_MODULE64);  
  171.   
  172.         // 得到模块名  
  173.         //  
  174.         if (SymGetModuleInfo64(hProcess, sf.AddrPC.Offset, &moduleInfo))  
  175.         {  
  176.             SafeStrCpy(callstackinfo.ModuleName, MAX_NAME_LENGTH, moduleInfo.ModuleName);  
  177.         }  
  178.   
  179.         arrCallStackInfo.push_back(callstackinfo);  
  180.     }  
  181.   
  182.     SymCleanup(hProcess);  
  183.   
  184.     return arrCallStackInfo;  
  185. }  

        然后,就可以用《让程序在崩溃时体面的退出之Unhandled Exception》中提供的捕捉程序崩溃事件的方法添加一个回调函数,在这个函数里面调用上面的函数来得到程序崩溃时的CallStack。

[cpp]  view plain copy
  1. // 处理Unhandled Exception的回调函数  
  2. //  
  3. LONG ApplicationCrashHandler(EXCEPTION_POINTERS *pException)  
  4. {     
  5.     // 确保有足够的栈空间  
  6.     //  
  7. #ifdef _M_IX86  
  8.     if (pException->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW)  
  9.     {  
  10.         static char TempStack[1024 * 128];  
  11.         __asm mov eax,offset TempStack[1024 * 128];  
  12.         __asm mov esp,eax;  
  13.     }  
  14. #endif    
  15.   
  16.     CrashInfo crashinfo = GetCrashInfo(pException->ExceptionRecord);  
  17.   
  18.     // 输出Crash信息  
  19.     //  
  20.     cout << "ErrorCode: " << crashinfo.ErrorCode << endl;  
  21.     cout << "Address: " << crashinfo.Address << endl;  
  22.     cout << "Flags: " << crashinfo.Flags << endl;  
  23.       
  24.     vector<CallStackInfo> arrCallStackInfo = GetCallStack(pException->ContextRecord);  
  25.   
  26.     // 输出CallStack  
  27.     //  
  28.     cout << "CallStack: " << endl;  
  29.     for (vector<CallStackInfo>::iterator i = arrCallStackInfo.begin(); i != arrCallStackInfo.end(); ++i)  
  30.     {  
  31.         CallStackInfo callstackinfo = (*i);  
  32.   
  33.         cout << callstackinfo.MethodName << "() : [" << callstackinfo.ModuleName << "] (File: " << callstackinfo.FileName << " @Line " << callstackinfo.LineNumber << ")" << endl;  
  34.     }  
  35.   
  36.     // 这里弹出一个错误对话框并退出程序  
  37.     //  
  38.     FatalAppExit(-1,  _T("*** Unhandled Exception! ***"));  
  39.   
  40.     return EXCEPTION_EXECUTE_HANDLER;  
  41. }  

        最后,在main函数的开头添加下面的代码。

[cpp]  view plain copy
  1. // 设置处理Unhandled Exception的回调函数  
  2. //   
  3. SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler);   

        编译上面的代码,到工程的Debug目录下找到编译好的Test.exe,双击运行,程序在崩溃的时候就会输出CallStack。
让程序在崩溃时体面的退出之CallStack_第1张图片



你可能感兴趣的:(让程序在崩溃时体面的退出之CallStack)