利用MSJExceptionHandler类查找Crash问题

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

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

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

1.将msjexhnd.h和msjexhnd.cpp加入到这个工程

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

2.工程中选中msjexhnd.cpp右键>属性,在c/c++>Precompiled Headers>Create/Use Precompiled Headers选择Not Using Precompiled Headers,Ok

   编译程序,成功。

3.首先设置工程为Release编译,然后选中工程右键>属性,在c/c++>Output Files>Assembler Output选择Assembly, Machine Code and Source (/FAcs).

   这个选项将为每个源文件(*.cpp)生成机器码、汇编码和源代码的对应表,可以在“Release”目录下找到和查看这些文件。
   然后在Linker>Debugging>Generate Map File选择Yes (/MAP),这个选项将生成编译后的函数地址和函数名的对应表。

   点击ok后rebuild此工程,可以在Release目录找到testCrash.map,testCrashDlg.cod

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

void CtestCrashDlg::OnBnClickedOk()
{
 // TODO: Add your control notification handler code here
 int *p=NULL;
 *p = 0; //给空指针赋值
 OnOK();
}

 

二、查找Crash

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

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

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

  Fault address:  00401452 01:00000452 d:\myown\test\testcrash\release\testCrash.exe

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

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

  0001:00000450       ?OnBnClickedOk@CtestCrashDlg@@QAEXXZ 00401450 f   testCrashDlg.obj
  0001:00000460       ?Create@CDialog@@UAEHIPAVCWnd@@@Z 00401460 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 33 c0   xor  eax, eax

; 157  :  *p = 0;

  00002 89 00   mov  DWORD PTR [eax], eax

; 158  :  OnOK();

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

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

好,终于找到元凶!

// msjexhnd.cpp  
 
//==========================================  
// Matt Pietrek  
// Microsoft Systems Journal, April 1997  
// FILE: MSJEXHND.CPP  
//==========================================  
#include "stdafx.h"
#include "msjexhnd.h"
#include< windows.h>  
#include< tchar.h>  
 
 
//============================== Global Variables =============================  
 
//  
// Declare the static variables of the MSJExceptionHandler class  
//  
TCHAR MSJExceptionHandler::m_szLogFileName[MAX_PATH];  
LPTOP_LEVEL_EXCEPTION_FILTER MSJExceptionHandler::m_previousFilter;  
HANDLE MSJExceptionHandler::m_hReportFile;  
 
 
MSJExceptionHandler g_MSJExceptionHandler;  // Declare global instance of class  
 
//============================== Class Methods =============================  
 
//=============  
// Constructor  
//=============  
MSJExceptionHandler::MSJExceptionHandler( )  
{  
   // Install the unhandled exception filter function  
    m_previousFilter = SetUnhandledExceptionFilter(MSJUnhandledExceptionFilter);  
 
   // Figure out what the report file will be named, and store it away  
    GetModuleFileName( 0, m_szLogFileName, MAX_PATH );  
 
    // Look for the '.' before the "EXE" extension.  Replace the extension  
    // with "RPT"  
    PTSTR pszDot = _tcsrchr( m_szLogFileName, _T('.') );  
    if ( pszDot )  
    {  
        pszDot++;   // Advance past the '.'  
        if ( _tcslen(pszDot) >= 3 )  
            _tcscpy( pszDot, _T("RPT") );   // "RPT" -> "Report"  
    }  
}  
 
//============  
// Destructor  
//============  
MSJExceptionHandler::~MSJExceptionHandler( )  
{  
    SetUnhandledExceptionFilter( m_previousFilter );  
}  
 
//==============================================================  
// Lets user change the name of the report file to be generated  
//==============================================================  
void MSJExceptionHandler::SetLogFileName( PTSTR pszLogFileName )  
{  
    _tcscpy( m_szLogFileName, pszLogFileName );  
}  
 
//===========================================================  
// Entry point where control comes on an unhandled exception  
//===========================================================  
LONG WINAPI MSJExceptionHandler::MSJUnhandledExceptionFilter(  
                                    PEXCEPTION_POINTERS pExceptionInfo )  
{  
    m_hReportFile = CreateFile( m_szLogFileName,  
                                GENERIC_WRITE,  
                                0,  
                                0,  
                                OPEN_ALWAYS,  
                                FILE_FLAG_WRITE_THROUGH,  
                                0 );  
 
    if ( m_hReportFile )  
    {  
        SetFilePointer( m_hReportFile, 0, 0, FILE_END );  
 
        GenerateExceptionReport( pExceptionInfo );  
 
        CloseHandle( m_hReportFile );  
        m_hReportFile = 0;  
    }  
 
    if ( m_previousFilter )  
        return m_previousFilter( pExceptionInfo );  
    else  
        return EXCEPTION_CONTINUE_SEARCH;  
}  
 
//===========================================================================  
// Open the report file, and write the desired information to it.  Called by  
// MSJUnhandledExceptionFilter                                                
//===========================================================================  
void MSJExceptionHandler::GenerateExceptionReport(  
    PEXCEPTION_POINTERS pExceptionInfo )  
{  
    // Start out with a banner  
    _tprintf( _T("//=====================================================\n") );  
 
    PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord;  
 
   // First print information about the type of fault  
    _tprintf(   _T("Exception code: %08X %s\n"),  
                pExceptionRecord->ExceptionCode,  
                GetExceptionString(pExceptionRecord->ExceptionCode) );  
 
   // Now print information about where the fault occured  
    TCHAR szFaultingModule[MAX_PATH];  
    DWORD section, offset;  
    GetLogicalAddress(  pExceptionRecord->ExceptionAddress,  
                        szFaultingModule,  
                        sizeof( szFaultingModule ),  
                        section, offset );  
 
    _tprintf( _T("Fault address:  %08X %02X:%08X %s\n"),  
                pExceptionRecord->ExceptionAddress,  
                section, offset, szFaultingModule );  
 
    PCONTEXT pCtx = pExceptionInfo->ContextRecord;  
 
   // Show the registers  
    #ifdef _M_IX86  // Intel Only!  
    _tprintf( _T("\nRegisters:\n") );  
 
    _tprintf(_T("EAX:%08X\nEBX:%08X\nECX:%08X\nEDX:%08X\nESI:%08X\nEDI:%08X\n"),  
            pCtx->Eax, pCtx->Ebx, pCtx->Ecx, pCtx->Edx, pCtx->Esi, pCtx->Edi );  
 
    _tprintf( _T("CS:EIP:%04X:%08X\n"), pCtx->SegCs, pCtx->Eip );  
    _tprintf( _T("SS:ESP:%04X:%08X  EBP:%08X\n"),  
                pCtx->SegSs, pCtx->Esp, pCtx->Ebp );  
    _tprintf( _T("DS:%04X  ES:%04X  FS:%04X  GS:%04X\n"),  
                pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs );  
    _tprintf( _T("Flags:%08X\n"), pCtx->EFlags );  
 
   // Walk the stack using x86 specific code  
    IntelStackWalk( pCtx );  
 
    #endif  
 
    _tprintf( _T("\n") );  
}  
 
//======================================================================  
// Given an exception code, returns a pointer to a static string with a  
// description of the exception                                          
//======================================================================  
LPTSTR MSJExceptionHandler::GetExceptionString( DWORD dwCode )  
{  
    #define EXCEPTION( x ) case EXCEPTION_##x: return _T(#x);  
 
    switch ( dwCode )  
    {  
        EXCEPTION( ACCESS_VIOLATION )  
        EXCEPTION( DATATYPE_MISALIGNMENT )  
        EXCEPTION( BREAKPOINT )  
        EXCEPTION( SINGLE_STEP )  
        EXCEPTION( ARRAY_BOUNDS_EXCEEDED )  
        EXCEPTION( FLT_DENORMAL_OPERAND )  
        EXCEPTION( FLT_DIVIDE_BY_ZERO )  
        EXCEPTION( FLT_INEXACT_RESULT )  
        EXCEPTION( FLT_INVALID_OPERATION )  
        EXCEPTION( FLT_OVERFLOW )  
        EXCEPTION( FLT_STACK_CHECK )  
        EXCEPTION( FLT_UNDERFLOW )  
        EXCEPTION( INT_DIVIDE_BY_ZERO )  
        EXCEPTION( INT_OVERFLOW )  
        EXCEPTION( PRIV_INSTRUCTION )  
        EXCEPTION( IN_PAGE_ERROR )  
        EXCEPTION( ILLEGAL_INSTRUCTION )  
        EXCEPTION( NONCONTINUABLE_EXCEPTION )  
        EXCEPTION( STACK_OVERFLOW )  
        EXCEPTION( INVALID_DISPOSITION )  
        EXCEPTION( GUARD_PAGE )  
        EXCEPTION( INVALID_HANDLE )  
    }  
 
    // If not one of the "known" exceptions, try to get the string  
    // from NTDLL.DLL's message table.  
 
    static TCHAR szBuffer[512] = { 0 };  
 
    FormatMessage(  FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE,  
                    GetModuleHandle( _T("NTDLL.DLL") ),  
                    dwCode, 0, szBuffer, sizeof( szBuffer ), 0 );  
 
    return szBuffer;  
}  
 
//==============================================================================  
// Given a linear address, locates the module, section, and offset containing   
// that address.                                                                
//                                                                              
// Note: the szModule paramater buffer is an output buffer of length specified  
// by the len parameter (in characters!)                                        
//==============================================================================  
BOOL MSJExceptionHandler::GetLogicalAddress(  
        PVOID addr, PTSTR szModule, DWORD len, DWORD& section, DWORD& offset )  
{  
    MEMORY_BASIC_INFORMATION mbi;  
 
    if ( !VirtualQuery( addr,& mbi, sizeof(mbi) ) )  
        return FALSE;  
 
    DWORD hMod = (DWORD)mbi.AllocationBase;  
 
    if ( !GetModuleFileName( (HMODULE)hMod, szModule, len ) )  
        return FALSE;  
 
   // Point to the DOS header in memory  
    PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hMod;  
 
   // From the DOS header, find the NT (PE) header  
    PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(hMod + pDosHdr->e_lfanew);  
 
    PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION( pNtHdr );  
 
    DWORD rva = (DWORD)addr - hMod; // RVA is offset from module load address  
 
    // Iterate through the section table, looking for the one that encompasses  
    // the linear address.  
    for (   unsigned i = 0;  
            i< pNtHdr->FileHeader.NumberOfSections;  
            i++, pSection++ )  
    {  
        DWORD sectionStart = pSection->VirtualAddress;  
        DWORD sectionEnd = sectionStart  
                    + max(pSection->SizeOfRawData, pSection->Misc.VirtualSize);  
 
       // Is the address in this section???  
        if ((rva >= sectionStart)&& (rva<= sectionEnd) )  
        {  
            // Yes, address is in the section.  Calculate section and offset,  
            // and store in the "section"& "offset" params, which were  
            // passed by reference.  
            section = i+1;  
            offset = rva - sectionStart;  
            return TRUE;  
        }  
    }  
 
    return FALSE;   // Should never get here!  
}  
 
//============================================================  
// Walks the stack, and writes the results to the report file  
//============================================================  
void MSJExceptionHandler::IntelStackWalk( PCONTEXT pContext )  
{  
    _tprintf( _T("\nCall stack:\n") );  
 
    _tprintf( _T("Address   Frame     Logical addr  Module\n") );  
 
    DWORD pc = pContext->Eip;  
    PDWORD pFrame, pPrevFrame;  
     
    pFrame = (PDWORD)pContext->Ebp;  
 
    do  
    {  
        TCHAR szModule[MAX_PATH] = _T("");  
        DWORD section = 0, offset = 0;  
 
        GetLogicalAddress((PVOID)pc, szModule,sizeof(szModule),section,offset );  
 
        _tprintf( _T("%08X  %08X  %04X:%08X %s\n"),  
                    pc, pFrame, section, offset, szModule );  
 
        pc = pFrame[1];  
 
        pPrevFrame = pFrame;  
 
        pFrame = (PDWORD)pFrame[0]; // precede to next higher frame on stack  
 
        if ( (DWORD)pFrame& 3 )    // Frame pointer must be aligned on a  
            break;                 // DWORD boundary.  Bail if not so.  
 
        if ( pFrame<=pPrevFrame )  
            break;  
 
       // Can two DWORDs be read from the supposed frame address?           
        if ( IsBadWritePtr(pFrame, sizeof(PVOID)*2) )  
            break;  
 
    } while ( 1 );  
}  
 
//============================================================================  
// Helper function that writes to the report file, and allows the user to use  
// printf style formating                                                      
//============================================================================  
int __cdecl MSJExceptionHandler::_tprintf(const TCHAR * format, ...)  
{  
    TCHAR szBuff[1024];  
    int retValue;  
    DWORD cbWritten;  
    va_list argptr;  
           
    va_start( argptr, format );  
    retValue = wvsprintf( szBuff, format, argptr );  
    va_end( argptr );  
 
    WriteFile( m_hReportFile, szBuff, retValue * sizeof(TCHAR),& cbWritten, 0 );  
 
    return retValue;  
}  

    // msjexhnd.h  
      
#ifndef __MSJEXHND_H__  
#define __MSJEXHND_H__  
 
class MSJExceptionHandler  
{  
    public:  
     
    MSJExceptionHandler( );  
    ~MSJExceptionHandler( );  
     
    void SetLogFileName( PTSTR pszLogFileName );  
 
    private:  
 
    // entry point where control comes on an unhandled exception  
    static LONG WINAPI MSJUnhandledExceptionFilter(  
                                PEXCEPTION_POINTERS pExceptionInfo );  
 
    // where report info is extracted and generated  
    static void GenerateExceptionReport( PEXCEPTION_POINTERS pExceptionInfo );  
 
   // Helper functions  
    static LPTSTR GetExceptionString( DWORD dwCode );  
    static BOOL GetLogicalAddress(  PVOID addr, PTSTR szModule, DWORD len,  
                                    DWORD& section, DWORD& offset );  
    static void IntelStackWalk( PCONTEXT pContext );  
    static int __cdecl _tprintf(const TCHAR * format, ...);  
 
   // Variables used by the class  
    static TCHAR m_szLogFileName[MAX_PATH];  
    static LPTOP_LEVEL_EXCEPTION_FILTER m_previousFilter;  
    static HANDLE m_hReportFile;  
};  
 
extern MSJExceptionHandler g_MSJExceptionHandler;  //  global instance of class  
 
#endif  


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值