您的位置:首页 > 其它

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

2011-03-10 11:47 239 查看
在程序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("//=====================================================/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;
}

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/fisker0303/archive/2006/04/15/664588.aspx
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: