最近有个用户遇到程序Crash问题,但我们的机器都不能重现,于是在网上搜了一把,发现有个MSJExceptionHandler类还比较好用,故整理了一下供大家参考。
这个类的使用方法很简单,只要把这个类加入到你的工程(不管是MFC,com,dll都可以)中一起编译就可以了,由于在这个类的实现文件中把定义了一个全局的类对象,所以不用加入任何代码,连#include都不需要。
一、VS2008创建一个基于对话框的工程testCrash
1.首先设置工程为Release编译,将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.设置导入的.cpp文件不使用预编译头
工程中选中msjexhnd.cpp右键>属性,在c/c++>PrecompiledHeaders>Create/Use Precompiled Headers选择Not Using PrecompiledHeaders,Ok编译程序,成功。
3.右键工程属性设置生成cod、map文件
在c/c++>OutputFiles>Assembler Output,选择Assembly, Machine Code and Source(/FAcs).这个选项将为每个源文件(*.cpp)生成机器码、汇编码和源代码的对应表,可以在“Release”目录下找到和查看这些文件。
在Linker>Debugging>Generate Map File,选择Yes(/MAP),这个选项将生成编译后的函数地址和函数名的对应表。
rebuild此工程,可以在生成exe文件的Release目录找到testCrash.map,在生成中间临时文件的Release目录下生成testCrashDlg.cod
4.在工程中加入测试代码,并重新编译程序
- void CtestCrashDlg::OnBnClickedOk()
- {
-
- int *p = NULL;
- *p = 0;
-
- OnOK();
- }
二、查找Crash
详细介绍:查找崩溃
1.运行testCrash.exe,点击ok按钮,程序crash
此时会在exe同一目录下生成文件 testCrash.RPT,你可以自己定义此文件位置及名字,具体看MSJExceptionHandler的构造函数。
2.用文本方式打开testCrash.RPT,可以看到这一行
Call stack:
Address Frame Logical addr Module
00401452 0037F888 01:00000452 d:\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@@QAEXXZ
PROC
;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;
好,终于找到元凶!
测试工程(包含打印类):http://download.csdn.net/detail/qing666888/9718906
在程序release之后,不可避免的会存在一些bug,测试人员和最终用户如何在发现bug之后指导开发人员进行更正呢?在MS的网站上,有一篇名为"Under the hook"的文章,讲述了如何把程序崩溃时的函数调用情况记录为日志的方法,对此感兴趣的读者可以去看一看原文,那里提供源代码和原理的说明。
文章的作者提供了一个MSJExceptionHandler类来实现这一功能,这个类的使用方法很简单,只要把这个类加入到你的工程中并和你的程序一起编译就可以了,由于在这个类的实现文件中把自己定义为一个全局的类对象,所以,不用加入任何代码,#include都不需要。
当程序崩溃时,MSJExceptionHandler就会把崩溃时的堆栈调用情况记录在一个.rpt文件中,软件的测试人员或最终用户只要把这个文件发给你,而你使用记事本打开这个文件就可以查看崩溃原因了。你需要在发行软件的时候,为你的程序生成一个或几个map文件,用于定位出错的文件和函数。(我的另一篇blog中有关于生成map文件和定位错误的详细说明)为了方便使用,这里附上该类的完整代码:
-
-
- #ifndef __MSJEXHND_H__
- #define __MSJEXHND_H__
-
- class MSJExceptionHandler
- {
- public:
-
- MSJExceptionHandler( );
- ~MSJExceptionHandler( );
-
- void SetLogFileName( PTSTR pszLogFileName );
-
- private:
-
-
- static LONG WINAPI MSJUnhandledExceptionFilter(
- PEXCEPTION_POINTERS pExceptionInfo );
-
-
- static void GenerateExceptionReport( PEXCEPTION_POINTERS pExceptionInfo );
-
-
- 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, ...);
-
-
- static TCHAR m_szLogFileName[MAX_PATH];
- static LPTOP_LEVEL_EXCEPTION_FILTER m_previousFilter;
- static HANDLE m_hReportFile;
- };
-
- extern MSJExceptionHandler g_MSJExceptionHandler;
-
- #endif
-
-
-
-
-
-
-
- #include< windows.h>
- #include< tchar.h>
- #include "msjexhnd.h"
-
-
-
-
-
-
- TCHAR MSJExceptionHandler::m_szLogFileName[MAX_PATH];
- LPTOP_LEVEL_EXCEPTION_FILTER MSJExceptionHandler::m_previousFilter;
- HANDLE MSJExceptionHandler::m_hReportFile;
-
-
- MSJExceptionHandler g_MSJExceptionHandler;
-
-
-
-
-
-
- MSJExceptionHandler::MSJExceptionHandler( )
- {
-
- m_previousFilter = SetUnhandledExceptionFilter(MSJUnhandledExceptionFilter);
-
-
- GetModuleFileName( 0, m_szLogFileName, MAX_PATH );
-
-
-
- PTSTR pszDot = _tcsrchr( m_szLogFileName, _T('.') );
- if ( pszDot )
- {
- pszDot++;
- if ( _tcslen(pszDot) >= 3 )
- _tcscpy( pszDot, _T("RPT") );
- }
- }
-
-
-
-
- MSJExceptionHandler::~MSJExceptionHandler( )
- {
- SetUnhandledExceptionFilter( m_previousFilter );
- }
-
-
-
-
- void MSJExceptionHandler::SetLogFileName( PTSTR pszLogFileName )
- {
- _tcscpy( m_szLogFileName, pszLogFileName );
- }
-
-
-
-
- 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;
- }
-
-
-
-
-
- void MSJExceptionHandler::GenerateExceptionReport(
- PEXCEPTION_POINTERS pExceptionInfo )
- {
-
- _tprintf( _T("//=====================================================\n") );
-
- PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord;
-
-
- _tprintf( _T("Exception code: %08X %s\n"),
- pExceptionRecord->ExceptionCode,
- GetExceptionString(pExceptionRecord->ExceptionCode) );
-
-
- 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;
-
-
- #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 );
-
-
- IntelStackWalk( pCtx );
-
- #endif
-
- _tprintf( _T("\n") );
- }
-
-
-
-
-
- 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 )
- }
-
-
-
-
- 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;
- }
-
-
-
-
-
-
-
-
- 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;
-
-
- PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hMod;
-
-
- PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(hMod + pDosHdr->e_lfanew);
-
- PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION( pNtHdr );
-
- DWORD rva = (DWORD)addr - hMod;
-
-
-
- for ( unsigned i = 0;
- i< pNtHdr->FileHeader.NumberOfSections;
- i++, pSection++ )
- {
- DWORD sectionStart = pSection->VirtualAddress;
- DWORD sectionEnd = sectionStart
- + max(pSection->SizeOfRawData, pSection->Misc.VirtualSize);
-
-
- if ( (rva >= sectionStart)&& (rva<= sectionEnd) )
- {
-
-
-
- section = i+1;
- offset = rva - sectionStart;
- return TRUE;
- }
- }
-
- return FALSE;
- }
-
-
-
-
- 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];
-
- if ( (DWORD)pFrame& 3 )
- break;
-
- if ( pFrame<= pPrevFrame )
- break;
-
-
- if ( IsBadWritePtr(pFrame, sizeof(PVOID)*2) )
- break;
-
- } while ( 1 );
- }
-
-
-
-
-
- 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;
- }