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

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

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

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

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

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

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

   编译程序,成功。

3.首先设置工程为Release编译,然后选中工程右键>属性,在c/c++>OutputFiles>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 notificationhandler code here
 int *p=NULL;
 *p =0; //给空指针赋值
 OnOK();
}

 

二、查找Crash

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

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

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

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

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

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

 0001:00000450      ?OnBnClickedOk@CtestCrashDlg@@QAEXXZ00401450 f  testCrashDlg.obj
 0001:00000460      ?Create@CDialog@@UAEHIPAVCWnd@@@Z00401460 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@@QAEXXZPROC  ;CtestCrashDlg::OnBnClickedOk, COMDAT
; _this$ = ecx

; 155  :  //TODO: Add your control notification handler code here
; 156  :  int*p=NULL;

  00000 33c0  xor  eax, eax

; 157  :  *p =0;

  00002 8900  mov  DWORD PTR [eax], eax

; 158 OnOK();

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

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

好,终于找到元凶!

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

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

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

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

// msjexhnd.h

#ifndef __MSJEXHND_H__
#define __MSJEXHND_H__

class MSJExceptionHandler
{
   public:
   
   MSJExceptionHandler( );
   ~MSJExceptionHandler( );
   
   void SetLogFileName( PTSTR pszLogFileName );

   private:

    // entry point where controlcomes on an unhandled exception
   static LONG WINAPI MSJUnhandledExceptionFilter(
                               PEXCEPTION_POINTERS pExceptionInfo );

    //where report info is extracted and generated
   static void GenerateExceptionReport( PEXCEPTION_POINTERSpExceptionInfo );

   // 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 MSJExceptionHandlerg_MSJExceptionHandler;  //  global instanceof class

#endif

// msjexhnd.cpp

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

#include
#include
#include "msjexhnd.h"

//============================== GlobalVariables =============================

//
// Declare the static variables of the MSJExceptionHandlerclass
//

TCHAR MSJExceptionHandler::m_szLogFileName[MAX_PATH];
LPTOP_LEVEL_EXCEPTION_FILTERMSJExceptionHandler::m_previousFilter;
HANDLE MSJExceptionHandler::m_hReportFile;


MSJExceptionHandler g_MSJExceptionHandler; // Declare global instance ofclass

//============================== ClassMethods =============================

//=============
// Constructor
//=============

MSJExceptionHandler::MSJExceptionHandler( )
{
   // Install the unhandled exception filterfunction
   m_previousFilter =SetUnhandledExceptionFilter(MSJUnhandledExceptionFilter);

   // Figure out what the report file will benamed, 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 begenerated
//==============================================================

void MSJExceptionHandler::SetLogFileName( PTSTR pszLogFileName)
{
   _tcscpy( m_szLogFileName, pszLogFileName );
}

//===========================================================
// Entry point where control comes on an unhandled exception
//===========================================================

LONG WINAPIMSJExceptionHandler::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 toit.  Called by
//MSJUnhandledExceptionFilter                                              
//===========================================================================
void MSJExceptionHandler::GenerateExceptionReport(
   PEXCEPTION_POINTERS pExceptionInfo )
{
    // Start out with abanner
   _tprintf(_T("//=====================================================\n"));

   PEXCEPTION_RECORD pExceptionRecord =pExceptionInfo->ExceptionRecord;

   // First print information about the type offault
   _tprintf(  _T("Exception code: X %s\n"),
               pExceptionRecord->ExceptionCode,
               GetExceptionString(pExceptionRecord->ExceptionCode) );

   // Now print information about where thefault occured
   TCHAR szFaultingModule[MAX_PATH];
   DWORD section, offset;
   GetLogicalAddress( pExceptionRecord->ExceptionAddress,
                       szFaultingModule,
                       sizeof( szFaultingModule ),
                       section, offset );

   _tprintf( _T("Fault address:  X X:X%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:X\nEBX:X\nECX:X\nEDX:X\nESI:X\nEDI:X\n"),
           pCtx->Eax, pCtx->Ebx, pCtx->Ecx, pCtx->Edx,pCtx->Esi, pCtx->Edi );

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

   // Walk the stack using x86 specificcode
   IntelStackWalk( pCtx );

   #endif

   _tprintf( _T("\n") );
}

//======================================================================
// Given an exception code, returns a pointer to a static stringwith a
// description of theexception                                        
//======================================================================

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 thestring
   // 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 offsetcontaining 
// thataddress.                                                              
//                                                                            
// Note: the szModule paramater buffer is an output buffer oflength specified
// by the len parameter (incharacters!)                                      
//==============================================================================
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 inmemory
   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 loadaddress

   // Iterate through the section table, looking for the one thatencompasses
   // 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 thissection???
       if ( (rva >= sectionStart) && (rva <= sectionEnd))
       {
           // Yes, address is in the section. Calculate section and offset,
           // and store in the "section" & "offset" params, whichwere
           // 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("X X:X %s\n"),
                   pc, pFrame, section, offset, szModule );

       pc = pFrame[1];

       pPrevFrame = pFrame;

       pFrame = (PDWORD)pFrame[0]; // precede tonext higher frame on stack

       if ( (DWORD)pFrame & 3)   // Frame pointer must be aligned ona
           break;                 // DWORD boundary. Bail if not so.

       if ( pFrame <= pPrevFrame )
           break;

       // Can two DWORDs be read from the supposedframeaddress?         
       if ( IsBadWritePtr(pFrame, sizeof(PVOID)*2) )
           break;

   } while ( 1 );
}

//============================================================================
// Helper function that writes to the report file, and allows theuser to use
// printf styleformating                                                    
//============================================================================

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


转自:http://blog.sina.com.cn/s/blog_51396f890102vk5j.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值