转载自, 原文地址: http://www.cppblog.com/fwxjj/archive/2007/12/05/37867.html
在程序release之后,不可避免的会存在一些bug,测试人员和最终用户如何在发现bug之后指导开发人员进行更正呢?在MS的网站上,有一篇名为""的文章,讲述了如何把程序崩溃时的函数调用情况记录为日志的方法,对此感兴趣的读者可以去看一看原文,那里提供源代码和原理的说明。
文章的作者提供了一个MSJExceptionHandler类来实现这一功能,这个类的使用方法很简单,只要把这个类加入到你的工程中并和你的程序一起编译就可以了,由于在这个类的实现文件中把自己定义为一个全局的类对象,所以,不用加入任何代码,#include都不需要。
当程序崩溃时,MSJExceptionHandler就会把崩溃时的堆栈调用情况记录在一个.rpt文件中,软件的测试人员或最终用户只要把这个文件发给你,而你使用记事本打开这个文件就可以查看崩溃原因了。你需要在发行软件的时候,为你的程序生成一个或几个map文件,用于定位出错的文件和函数。(我的中有关于生成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("//=====================================================\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;
}