您的位置:首页 > 其它

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

2015-04-07 10:48 387 查看
在程序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,

                           
d83c
     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;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: