zoukankan      html  css  js  c++  java
  • [转]让程序在崩溃时体面的退出之CallStack

    原文地址:http://blog.csdn.net/starlee/article/details/6618849

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

    [cpp] view plaincopy
     
    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 plaincopy
     
    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] = '';  
    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 plaincopy
     
    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 plaincopy
     
    1. // 设置处理Unhandled Exception的回调函数  
    2. //   
    3. SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler);   

            编译上面的代码,到工程的Debug目录下找到编译好的Test.exe,双击运行,程序在崩溃的时候就会输出CallStack。



  • 相关阅读:
    需求获取过程中的逆向沟通
    程序员==生 涯 篇
    算法设计
    灯的启示:微软对唐骏的面试题
    使用Gzip压缩提升WEB服务器性能
    简历误区
    招聘编辑的七道面试题
    web2.0及其相关技术
    经典面试题助你成功就业
    逗号网站推广营销策略
  • 原文地址:https://www.cnblogs.com/gomen/p/3508482.html
Copyright © 2011-2022 走看看