记录程序崩溃时的调用堆栈

最近有个用户遇到程序Crash问题,但我们的机器都不能重现,于是在网上搜了一把,发现有个MSJExceptionHandler类还比较好用,故整理了一下供大家参考。

这个类的使用方法很简单,只要把这个类加入到你的工程(不管是MFC,com,dll都可以)中一起编译就可以了,由于在这个类的实现文件中把定义了一个全局的类对象,所以不用加入任何代码,连#include都不需要。

一、VS2008创建一个基于对话框的工程testCrash

1.首先设置工程为Release编译,将msjexhnd.h和msjexhnd.cpp加入到这个工程

此时编译程序会提示错误fatal error C1010: unexpected end of file while lookingfor precompiled header. Did you forget to add '#include "stdafx.h"'to your source?


2.设置导入的.cpp文件不使用预编译头

工程中选中msjexhnd.cpp右键>属性,在c/c++>PrecompiledHeaders>Create/Use Precompiled Headers选择Not Using PrecompiledHeaders,Ok编译程序,成功。


3.右键工程属性设置生成cod、map文件

c/c++>OutputFiles>Assembler Output,选择Assembly, Machine Code and Source(/FAcs).这个选项将为每个源文件(*.cpp)生成机器码、汇编码和源代码的对应表,可以在“Release”目录下找到和查看这些文件。

Linker>Debugging>Generate Map File,选择Yes(/MAP),这个选项将生成编译后的函数地址和函数名的对应表。


rebuild此工程,可以在生成exe文件的Release目录找到testCrash.map,在生成中间临时文件的Release目录下生成testCrashDlg.cod


4.在工程中加入测试代码,并重新编译程序

[cpp]  view plain  copy
  1. void CtestCrashDlg::OnBnClickedOk()  
  2. {  
  3.     // TODO: Add your control notificationhandler code here  
  4.     int *p = NULL;  
  5.     *p = 0; //给空指针赋值  
  6.   
  7.     OnOK();  
  8. }  


二、查找Crash

详细介绍:查找崩溃


1.运行testCrash.exe,点击ok按钮,程序crash

此时会在exe同一目录下生成文件 testCrash.RPT,你可以自己定义此文件位置及名字,具体看MSJExceptionHandler的构造函数。


2.用文本方式打开testCrash.RPT,可以看到这一行

Call stack:
Address     Frame        Logical addr    Module
00401452  0037F888  01:00000452   d:\myown\test\testcrash\release\testCrash.exe

注意01:00000452就是程序崩溃的地址


3.打开testCrash.map,可以找到

0001:00000450      ?OnBnClickedOk@CtestCrashDlg@@QAEXXZ00401450 f  testCrashDlg.obj

0001:00000460      ?Create@CDialog@@UAEHIPAVCWnd@@@Z00401460 f i testCrashDlg.obj
由于崩溃地址是01:00000452,大于0001:00000450,小于0001:00000460,所以可以肯定是CtestCrashDlg::OnBnClickedOk里崩溃。

并且相对地址是00000452-00000450=2(16进制),代码对应在testCrashDlg.cod因为最后面显示的是testCrashDlg.obj


4.打开testCrashDlg.cod,找到

COMDAT  ?OnBnClickedOk@CtestCrashDlg@@QAEXXZ
_TEXT  SEGMENT
?OnBnClickedOk@CtestCrashDlg@@QAEXXZ PROC    ;CtestCrashDlg::OnBnClickedOk, COMDAT
; _this$ = ecx
; 155   :   //TODO: Add your control notification handler code here
; 156   :   int*p=NULL;
   00000  33c0    xor   eax, eax
; 157   :   *p =0;
   00002  8900    mov   DWORD PTR [eax], eax

; 158  : OnOK();


前面带分号的是注释,不带的是汇编代码,汇编代码前面5位数是代码在此函数的相对地址,00002就是偏移2,正是我们要找的崩溃的地方。

上面的一行是注释实际的源代码; 157  : *p = 0;

好,终于找到元凶!

      

测试工程(包含打印类):http://download.csdn.net/detail/qing666888/9718906


在程序release之后,不可避免的会存在一些bug,测试人员和最终用户如何在发现bug之后指导开发人员进行更正呢?在MS的网站上,有一篇名为"Under the hook"的文章,讲述了如何把程序崩溃时的函数调用情况记录为日志的方法,对此感兴趣的读者可以去看一看原文,那里提供源代码和原理的说明。

文章的作者提供了一个MSJExceptionHandler类来实现这一功能,这个类的使用方法很简单,只要把这个类加入到你的工程中并和你的程序一起编译就可以了,由于在这个类的实现文件中把自己定义为一个全局的类对象,所以,不用加入任何代码,#include都不需要。

当程序崩溃时,MSJExceptionHandler就会把崩溃时的堆栈调用情况记录在一个.rpt文件中,软件的测试人员或最终用户只要把这个文件发给你,而你使用记事本打开这个文件就可以查看崩溃原因了。你需要在发行软件的时候,为你的程序生成一个或几个map文件,用于定位出错的文件和函数。(我的另一篇blog中有关于生成map文件和定位错误的详细说明)为了方便使用,这里附上该类的完整代码:


[cpp]  view plain  copy
  1. // msjexhnd.h  
  2.   
  3. #ifndef __MSJEXHND_H__  
  4. #define __MSJEXHND_H__  
  5.   
  6. class MSJExceptionHandler  
  7. {  
  8.     public:  
  9.      
  10.     MSJExceptionHandler( );  
  11.     ~MSJExceptionHandler( );  
  12.      
  13.     void SetLogFileName( PTSTR pszLogFileName );  
  14.   
  15.     private:  
  16.   
  17.     // entry point where control comes on an unhandled exception  
  18.     static LONG WINAPI MSJUnhandledExceptionFilter(  
  19.                                 PEXCEPTION_POINTERS pExceptionInfo );  
  20.   
  21.     // where report info is extracted and generated  
  22.     static void GenerateExceptionReport( PEXCEPTION_POINTERS pExceptionInfo );  
  23.   
  24.    // Helper functions  
  25.     static LPTSTR GetExceptionString( DWORD dwCode );  
  26.     static BOOL GetLogicalAddress(  PVOID addr, PTSTR szModule, DWORD len,  
  27.                                     DWORD& section, DWORD& offset );  
  28.     static void IntelStackWalk( PCONTEXT pContext );  
  29.     static int __cdecl _tprintf(const TCHAR * format, ...);  
  30.   
  31.    // Variables used by the class  
  32.     static TCHAR m_szLogFileName[MAX_PATH];  
  33.     static LPTOP_LEVEL_EXCEPTION_FILTER m_previousFilter;  
  34.     static HANDLE m_hReportFile;  
  35. };  
  36.   
  37. extern MSJExceptionHandler g_MSJExceptionHandler;  //  global instance of class  
  38.   
  39. #endif  

[cpp]  view plain  copy
  1. // msjexhnd.cpp  
  2.   
  3. //==========================================  
  4. // Matt Pietrek  
  5. // Microsoft Systems Journal, April 1997  
  6. // FILE: MSJEXHND.CPP  
  7. //==========================================  
  8. #include< windows.h>  
  9. #include< tchar.h>  
  10. #include "msjexhnd.h"  
  11.   
  12. //============================== Global Variables =============================  
  13.   
  14. //  
  15. // Declare the static variables of the MSJExceptionHandler class  
  16. //  
  17. TCHAR MSJExceptionHandler::m_szLogFileName[MAX_PATH];  
  18. LPTOP_LEVEL_EXCEPTION_FILTER MSJExceptionHandler::m_previousFilter;  
  19. HANDLE MSJExceptionHandler::m_hReportFile;  
  20.   
  21.   
  22. MSJExceptionHandler g_MSJExceptionHandler;  // Declare global instance of class  
  23.   
  24. //============================== Class Methods =============================  
  25.   
  26. //=============  
  27. // Constructor  
  28. //=============  
  29. MSJExceptionHandler::MSJExceptionHandler( )  
  30. {  
  31.    // Install the unhandled exception filter function  
  32.     m_previousFilter = SetUnhandledExceptionFilter(MSJUnhandledExceptionFilter);  
  33.   
  34.    // Figure out what the report file will be named, and store it away  
  35.     GetModuleFileName( 0, m_szLogFileName, MAX_PATH );  
  36.   
  37.     // Look for the '.' before the "EXE" extension.  Replace the extension  
  38.     // with "RPT"  
  39.     PTSTR pszDot = _tcsrchr( m_szLogFileName, _T('.') );  
  40.     if ( pszDot )  
  41.     {  
  42.         pszDot++;   // Advance past the '.'  
  43.         if ( _tcslen(pszDot) >= 3 )  
  44.             _tcscpy( pszDot, _T("RPT") );   // "RPT" -> "Report"  
  45.     }  
  46. }  
  47.   
  48. //============  
  49. // Destructor  
  50. //============  
  51. MSJExceptionHandler::~MSJExceptionHandler( )  
  52. {  
  53.     SetUnhandledExceptionFilter( m_previousFilter );  
  54. }  
  55.   
  56. //==============================================================  
  57. // Lets user change the name of the report file to be generated  
  58. //==============================================================  
  59. void MSJExceptionHandler::SetLogFileName( PTSTR pszLogFileName )  
  60. {  
  61.     _tcscpy( m_szLogFileName, pszLogFileName );  
  62. }  
  63.   
  64. //===========================================================  
  65. // Entry point where control comes on an unhandled exception  
  66. //===========================================================  
  67. LONG WINAPI MSJExceptionHandler::MSJUnhandledExceptionFilter(  
  68.                                     PEXCEPTION_POINTERS pExceptionInfo )  
  69. {  
  70.     m_hReportFile = CreateFile( m_szLogFileName,  
  71.                                 GENERIC_WRITE,  
  72.                                 0,  
  73.                                 0,  
  74.                                 OPEN_ALWAYS,  
  75.                                 FILE_FLAG_WRITE_THROUGH,  
  76.                                 0 );  
  77.   
  78.     if ( m_hReportFile )  
  79.     {  
  80.         SetFilePointer( m_hReportFile, 0, 0, FILE_END );  
  81.   
  82.         GenerateExceptionReport( pExceptionInfo );  
  83.   
  84.         CloseHandle( m_hReportFile );  
  85.         m_hReportFile = 0;  
  86.     }  
  87.   
  88.     if ( m_previousFilter )  
  89.         return m_previousFilter( pExceptionInfo );  
  90.     else  
  91.         return EXCEPTION_CONTINUE_SEARCH;  
  92. }  
  93.   
  94. //===========================================================================  
  95. // Open the report file, and write the desired information to it.  Called by  
  96. // MSJUnhandledExceptionFilter                                                
  97. //===========================================================================  
  98. void MSJExceptionHandler::GenerateExceptionReport(  
  99.     PEXCEPTION_POINTERS pExceptionInfo )  
  100. {  
  101.     // Start out with a banner  
  102.     _tprintf( _T("//=====================================================\n") );  
  103.   
  104.     PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord;  
  105.   
  106.    // First print information about the type of fault  
  107.     _tprintf(   _T("Exception code: %08X %s\n"),  
  108.                 pExceptionRecord->ExceptionCode,  
  109.                 GetExceptionString(pExceptionRecord->ExceptionCode) );  
  110.   
  111.    // Now print information about where the fault occured  
  112.     TCHAR szFaultingModule[MAX_PATH];  
  113.     DWORD section, offset;  
  114.     GetLogicalAddress(  pExceptionRecord->ExceptionAddress,  
  115.                         szFaultingModule,  
  116.                         sizeof( szFaultingModule ),  
  117.                         section, offset );  
  118.   
  119.     _tprintf( _T("Fault address:  %08X %02X:%08X %s\n"),  
  120.                 pExceptionRecord->ExceptionAddress,  
  121.                 section, offset, szFaultingModule );  
  122.   
  123.     PCONTEXT pCtx = pExceptionInfo->ContextRecord;  
  124.   
  125.    // Show the registers  
  126.     #ifdef _M_IX86  // Intel Only!  
  127.     _tprintf( _T("\nRegisters:\n") );  
  128.   
  129.     _tprintf(_T("EAX:%08X\nEBX:%08X\nECX:%08X\nEDX:%08X\nESI:%08X\nEDI:%08X\n"),  
  130.             pCtx->Eax, pCtx->Ebx, pCtx->Ecx, pCtx->Edx, pCtx->Esi, pCtx->Edi );  
  131.   
  132.     _tprintf( _T("CS:EIP:%04X:%08X\n"), pCtx->SegCs, pCtx->Eip );  
  133.     _tprintf( _T("SS:ESP:%04X:%08X  EBP:%08X\n"),  
  134.                 pCtx->SegSs, pCtx->Esp, pCtx->Ebp );  
  135.     _tprintf( _T("DS:%04X  ES:%04X  FS:%04X  GS:%04X\n"),  
  136.                 pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs );  
  137.     _tprintf( _T("Flags:%08X\n"), pCtx->EFlags );  
  138.   
  139.    // Walk the stack using x86 specific code  
  140.     IntelStackWalk( pCtx );  
  141.   
  142.     #endif  
  143.   
  144.     _tprintf( _T("\n") );  
  145. }  
  146.   
  147. //======================================================================  
  148. // Given an exception code, returns a pointer to a static string with a  
  149. // description of the exception                                          
  150. //======================================================================  
  151. LPTSTR MSJExceptionHandler::GetExceptionString( DWORD dwCode )  
  152. {  
  153.     #define EXCEPTION( x ) case EXCEPTION_##x: return _T(#x);  
  154.   
  155.     switch ( dwCode )  
  156.     {  
  157.         EXCEPTION( ACCESS_VIOLATION )  
  158.         EXCEPTION( DATATYPE_MISALIGNMENT )  
  159.         EXCEPTION( BREAKPOINT )  
  160.         EXCEPTION( SINGLE_STEP )  
  161.         EXCEPTION( ARRAY_BOUNDS_EXCEEDED )  
  162.         EXCEPTION( FLT_DENORMAL_OPERAND )  
  163.         EXCEPTION( FLT_DIVIDE_BY_ZERO )  
  164.         EXCEPTION( FLT_INEXACT_RESULT )  
  165.         EXCEPTION( FLT_INVALID_OPERATION )  
  166.         EXCEPTION( FLT_OVERFLOW )  
  167.         EXCEPTION( FLT_STACK_CHECK )  
  168.         EXCEPTION( FLT_UNDERFLOW )  
  169.         EXCEPTION( INT_DIVIDE_BY_ZERO )  
  170.         EXCEPTION( INT_OVERFLOW )  
  171.         EXCEPTION( PRIV_INSTRUCTION )  
  172.         EXCEPTION( IN_PAGE_ERROR )  
  173.         EXCEPTION( ILLEGAL_INSTRUCTION )  
  174.         EXCEPTION( NONCONTINUABLE_EXCEPTION )  
  175.         EXCEPTION( STACK_OVERFLOW )  
  176.         EXCEPTION( INVALID_DISPOSITION )  
  177.         EXCEPTION( GUARD_PAGE )  
  178.         EXCEPTION( INVALID_HANDLE )  
  179.     }  
  180.   
  181.     // If not one of the "known" exceptions, try to get the string  
  182.     // from NTDLL.DLL's message table.  
  183.   
  184.     static TCHAR szBuffer[512] = { 0 };  
  185.   
  186.     FormatMessage(  FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE,  
  187.                     GetModuleHandle( _T("NTDLL.DLL") ),  
  188.                     dwCode, 0, szBuffer, sizeof( szBuffer ), 0 );  
  189.   
  190.     return szBuffer;  
  191. }  
  192.   
  193. //==============================================================================  
  194. // Given a linear address, locates the module, section, and offset containing   
  195. // that address.                                                                
  196. //                                                                              
  197. // Note: the szModule paramater buffer is an output buffer of length specified  
  198. // by the len parameter (in characters!)                                        
  199. //==============================================================================  
  200. BOOL MSJExceptionHandler::GetLogicalAddress(  
  201.         PVOID addr, PTSTR szModule, DWORD len, DWORD& section, DWORD& offset )  
  202. {  
  203.     MEMORY_BASIC_INFORMATION mbi;  
  204.   
  205.     if ( !VirtualQuery( addr,& mbi, sizeof(mbi) ) )  
  206.         return FALSE;  
  207.   
  208.     DWORD hMod = (DWORD)mbi.AllocationBase;  
  209.   
  210.     if ( !GetModuleFileName( (HMODULE)hMod, szModule, len ) )  
  211.         return FALSE;  
  212.   
  213.    // Point to the DOS header in memory  
  214.     PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hMod;  
  215.   
  216.    // From the DOS header, find the NT (PE) header  
  217.     PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(hMod + pDosHdr->e_lfanew);  
  218.   
  219.     PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION( pNtHdr );  
  220.   
  221.     DWORD rva = (DWORD)addr - hMod; // RVA is offset from module load address  
  222.   
  223.     // Iterate through the section table, looking for the one that encompasses  
  224.     // the linear address.  
  225.     for (   unsigned i = 0;  
  226.             i< pNtHdr->FileHeader.NumberOfSections;  
  227.             i++, pSection++ )  
  228.     {  
  229.         DWORD sectionStart = pSection->VirtualAddress;  
  230.         DWORD sectionEnd = sectionStart  
  231.                     + max(pSection->SizeOfRawData, pSection->Misc.VirtualSize);  
  232.   
  233.        // Is the address in this section???  
  234.         if ( (rva >= sectionStart)&& (rva<= sectionEnd) )  
  235.         {  
  236.             // Yes, address is in the section.  Calculate section and offset,  
  237.             // and store in the "section"& "offset" params, which were  
  238.             // passed by reference.  
  239.             section = i+1;  
  240.             offset = rva - sectionStart;  
  241.             return TRUE;  
  242.         }  
  243.     }  
  244.   
  245.     return FALSE;   // Should never get here!  
  246. }  
  247.   
  248. //============================================================  
  249. // Walks the stack, and writes the results to the report file  
  250. //============================================================  
  251. void MSJExceptionHandler::IntelStackWalk( PCONTEXT pContext )  
  252. {  
  253.     _tprintf( _T("\nCall stack:\n") );  
  254.   
  255.     _tprintf( _T("Address   Frame     Logical addr  Module\n") );  
  256.   
  257.     DWORD pc = pContext->Eip;  
  258.     PDWORD pFrame, pPrevFrame;  
  259.      
  260.     pFrame = (PDWORD)pContext->Ebp;  
  261.   
  262.     do  
  263.     {  
  264.         TCHAR szModule[MAX_PATH] = _T("");  
  265.         DWORD section = 0, offset = 0;  
  266.   
  267.         GetLogicalAddress((PVOID)pc, szModule,sizeof(szModule),section,offset );  
  268.   
  269.         _tprintf( _T("%08X  %08X  %04X:%08X %s\n"),  
  270.                     pc, pFrame, section, offset, szModule );  
  271.   
  272.         pc = pFrame[1];  
  273.   
  274.         pPrevFrame = pFrame;  
  275.   
  276.         pFrame = (PDWORD)pFrame[0]; // precede to next higher frame on stack  
  277.   
  278.         if ( (DWORD)pFrame& 3 )    // Frame pointer must be aligned on a  
  279.             break;                 // DWORD boundary.  Bail if not so.  
  280.   
  281.         if ( pFrame<= pPrevFrame )  
  282.             break;  
  283.   
  284.        // Can two DWORDs be read from the supposed frame address?           
  285.         if ( IsBadWritePtr(pFrame, sizeof(PVOID)*2) )  
  286.             break;  
  287.   
  288.     } while ( 1 );  
  289. }  
  290.   
  291. //============================================================================  
  292. // Helper function that writes to the report file, and allows the user to use  
  293. // printf style formating                                                      
  294. //============================================================================  
  295. int __cdecl MSJExceptionHandler::_tprintf(const TCHAR * format, ...)  
  296. {  
  297.     TCHAR szBuff[1024];  
  298.     int retValue;  
  299.     DWORD cbWritten;  
  300.     va_list argptr;  
  301.            
  302.     va_start( argptr, format );  
  303.     retValue = wvsprintf( szBuff, format, argptr );  
  304.     va_end( argptr );  
  305.   
  306.     WriteFile( m_hReportFile, szBuff, retValue * sizeof(TCHAR),& cbWritten, 0 );  
  307.   
  308.     return retValue;  
  309. }  

猜你喜欢

转载自blog.csdn.net/zb872676223/article/details/80778678
今日推荐