zoukankan      html  css  js  c++  java
  • (转)记录程序崩溃时的调用堆栈

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

    转载自大龙的博客, 原文地址: http://www.cppblog.com/fwxjj/archive/2007/12/05/37867.html

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

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

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

    // 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

    // msjexhnd.cpp

    //==========================================
    // Matt Pietrek
    // Microsoft Systems Journal, April 1997
    // FILE: MSJEXHND.CPP
    //==========================================

    #include <windows.h>
    #include <tchar.h>
    #include "msjexhnd.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("//===================================================== ") );

        PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord;

        // First print information about the type of fault
        _tprintf(   _T("Exception code: %08X %s "),
                    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 "),
                    pExceptionRecord->ExceptionAddress,
                    section, offset, szFaultingModule );

        PCONTEXT pCtx = pExceptionInfo->ContextRecord;

        // Show the registers
        #ifdef _M_IX86  // Intel Only!
        _tprintf( _T(" Registers: ") );

        _tprintf(_T("EAX:%08X EBX:%08X ECX:%08X EDX:%08X ESI:%08X EDI:%08X "),
                pCtx->Eax, pCtx->Ebx, pCtx->Ecx, pCtx->Edx, pCtx->Esi, pCtx->Edi );

        _tprintf( _T("CS:EIP:%04X:%08X "), pCtx->SegCs, pCtx->Eip );
        _tprintf( _T("SS:ESP:%04X:%08X  EBP:%08X "),
                    pCtx->SegSs, pCtx->Esp, pCtx->Ebp );
        _tprintf( _T("DS:%04X  ES:%04X  FS:%04X  GS:%04X "),
                    pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs );
        _tprintf( _T("Flags:%08X "), pCtx->EFlags );

        // Walk the stack using x86 specific code
        IntelStackWalk( pCtx );

        #endif

        _tprintf( _T(" ") );
    }

    //======================================================================
    // 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(" Call stack: ") );

        _tprintf( _T("Address   Frame     Logical addr  Module ") );

        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 "),
                        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;
    }

  • 相关阅读:
    JDK15视频会及新特性总节
    设计模式之访问者模式
    datax分析与思考(一)
    beanfactory中单例bean的初始化过程(一)
    IIS 404错误,错误代码:0x80070002
    WebApi
    多线程--程序员必修课
    委托(续2)
    委托(续)
    委托
  • 原文地址:https://www.cnblogs.com/aanbpsd/p/VC_callstack.html
Copyright © 2011-2022 走看看