📄 excepthandler.cpp
字号:
/*
Module : EXCEPTHANDLER.CPP
Purpose: Implementation for a class which intercepts and logs details about unhandled exceptions
such as access violations, stack overflows and division by zero.
The class is based upon the February 2002 MSDN article called "Under the Hood: Improved
Error Reporting with DBGHELP 5.1 by Matt Pietrek.
To compile the class you need the November 2002 Platform SDK or later installed.
You can add the ExceptHandler.cpp/h files directly to your C++ project or put the exception
handler class in a DLL and arrange for your app / dll to get it installed via a LoadLibrary
call.
A binary version of the ExceptHandler is provided in the XCPTHLR.DLL which is included
in this distribution.
To get this code to work on client machines you will need to arrange for the distribution
of the DBGHELP 5.1 dll which can be obtained from November 2002 Platform SDK or later. Select
"Install Debugging Tools for Windows" to get the DLL installed on your machine. Also
note that the DBGHELP dll is under Windows System File protection on recent versions of Windows,
so put the DBGHELP dll in the same directory as your application.
To provide symbols for your code in release mode, you should modify your projects settings as
follows:
1. Project Settings -> Link -> Debug (Category) , Enable "Debug Info" and pick "Microsoft Format".
2. Same Place, pick "Separate Types".
3. In the Project Options edit box on the same page, add "/OPT:REF". This will ensure that
unreferrenced functions are removed from the resultant binary.
4. Project Settings -> C++ -> General (Category) Debug Info Combo -> "Program Database"
5. Sample Place, you may need to set "Optimizations" to "Disable (Debug)" if your are getting
call stacks which do not make sense.
Also remember to ship the resultant pdb files with your code (or arrange for them to be somehow
available) so that CExceptionLogger can give the source and line information in the resultant
log.
By default the CExceptionLogger class will generate a log file with the name "name of exe.exception".
In this file (which is ASCII if the CExceptionLogger is build as ASCII and UNICODE if the
CExceptionLogger is build as a UNICODE) is the following information:
1. Date and Time when the exception occurred.
2. Exception Code
3. Details about exception if an access violation
4. Details about exception address including linear address, section, offset and module path
5. Full path of the process
6. Current Win32 working directory
7. Command line for the process
8. Process ID
9. Thread ID where the exception occurred.
10. Enumeration of all the threads in the process (assuming Toolhelp32 is available) including
i) Thread ID
ii) Priority and Delta priority
iii) References
iv) Creation Time
v) Kernel and User Time
11. Enumeration of the Modules in the process (again only if Toolhelp32 is available) including
i) Name and full path
ii) Global and per Process reference count
iii) module handle
iv) size
v) All symbols for that module fully expanded (see details later)
12. All the x86 registers
13. Call Stack where exception occured, including section, offset, module, function and line
information.
14. At each stack frame as well as for all modules, all variables and parameters all also logged.
All basic types such as voids, chars, shorts, words, ints, DWORDS, floats, doubles and longs
are logged. UDT's including structs, unions and classes are also fully recursed into to
display their members. Info for each type includes its name, address, type and value.
In addition if the variable is a array, each value in the array is fully logged.
Please note that due to all the symbols which even a very small appp has, each log of an exception
can generate upwards of 100K of data or more. My opinion on this is that disk space is cheap while
developers time looking for an intermittent release only bug is not!!!!!.
References:
Bugslayer, MSJ, August 1998 by John Robbins, http://www.microsoft.com/msj/defaultframe.asp?page=/msj/0898/bugslayer0898.htm
Under the Hood, MSDN, March 2002 by Matt Pietrek, http://msdn.microsoft.com/msdnmag/issues/02/03/Hood/Hood0203.asp
Created: PJN / 14-03-2002
History: None
Copyright (c) 2002 by PJ Naughter. (Web: www.naughter.com, Email: pjna@naughter.com)
All rights reserved.
Copyright / Usage Details:
You are allowed to include the source code in any product (commercial, shareware, freeware or otherwise)
when your product is released in binary form. You are allowed to modify the source code in any way you want
except you cannot modify the copyright details at the top of each module. If you want to distribute source
code with your application, then you are only allowed to distribute versions released by the author. This is
to maintain a single distribution point for the source code.
*/
/////////////////////// Includes //////////////////////////////
#include "stdafx.h"
#include "ExceptHandler.h"
#ifndef _INC_TCHAR
#pragma message("To avoid this message, put tchar.h in your PCH")
#include <tchar.h>
#endif
#ifndef _INC_TOOLHELP32
#pragma message("To avoid this message, put tlhelp32.h in your PCH")
#include <tlhelp32.h>
#endif
#ifndef _INC_STDIO
#pragma message("To avoid this message, put stdio.h in your PCH")
#include <stdio.h>
#endif
/////////////////////// Macros / Defines / Locals //////////////////////
#pragma comment(lib, "dbghelp.lib")
#pragma comment(lib, "oleaut32.lib")
//Class which looks after pulling in ToolHelp32 and OpenThread dynamically.
//That way the CExceptionlogger class will work on NT 4 (without of course the Thread
//and module lists) and Win95 which does not have OpenThread (but does
//have Toolhelp32!!!!)
class _EXCEPTION_LOGGER_DATA
{
public:
//Constructors /Destructors
_EXCEPTION_LOGGER_DATA();
typedef HANDLE (WINAPI CREATETOOLHELP32SNAPSHOT)(DWORD, DWORD);
typedef CREATETOOLHELP32SNAPSHOT* LPCREATETOOLHELP32SNAPSHOT;
typedef BOOL (WINAPI THREAD32FIRST)(HANDLE, LPTHREADENTRY32);
typedef THREAD32FIRST* LPTHREAD32FIRST;
typedef BOOL (WINAPI THREAD32NEXT)(HANDLE, LPTHREADENTRY32);
typedef THREAD32NEXT* LPTHREAD32NEXT;
typedef BOOL (WINAPI MODULE32FIRST)(HANDLE, LPMODULEENTRY32);
typedef MODULE32FIRST* LPMODULE32FIRST;
typedef BOOL (WINAPI MODULE32NEXT)(HANDLE, LPMODULEENTRY32);
typedef MODULE32NEXT* LPMODULE32NEXT;
typedef HANDLE (WINAPI OPENTHREAD)(DWORD, BOOL, DWORD);
typedef OPENTHREAD* LPOPENTHREAD;
LPCREATETOOLHELP32SNAPSHOT m_lpfnCreateToolhelp32Snapshot;
LPTHREAD32FIRST m_lpfnThread32First;
LPTHREAD32NEXT m_lpfnThread32Next;
LPMODULE32FIRST m_lpfnModule32First;
LPMODULE32NEXT m_lpfnModule32Next;
LPOPENTHREAD m_lpfnOpenThread;
HMODULE m_hKernel32;
void ResetFunctionPointers();
};
_EXCEPTION_LOGGER_DATA::_EXCEPTION_LOGGER_DATA()
{
m_hKernel32 = LoadLibrary(_T("KERNEL32.DLL"));
if (m_hKernel32)
{
m_lpfnOpenThread = (LPOPENTHREAD) GetProcAddress(m_hKernel32, "OpenThread");
m_lpfnCreateToolhelp32Snapshot = (LPCREATETOOLHELP32SNAPSHOT) GetProcAddress(m_hKernel32, "CreateToolhelp32Snapshot");
m_lpfnThread32First = (LPTHREAD32FIRST) GetProcAddress(m_hKernel32, "Thread32First");
m_lpfnThread32Next = (LPTHREAD32NEXT) GetProcAddress(m_hKernel32, "Thread32Next");
#ifdef _UNICODE
m_lpfnModule32First = (LPMODULE32FIRST) GetProcAddress(m_hKernel32, "Module32FirstW");
m_lpfnModule32Next = (LPMODULE32NEXT) GetProcAddress(m_hKernel32, "Module32NextW");
#else
m_lpfnModule32First = (LPMODULE32FIRST) GetProcAddress(m_hKernel32, "Module32First");
m_lpfnModule32Next = (LPMODULE32NEXT) GetProcAddress(m_hKernel32, "Module32Next");
#endif
}
//Any function pointer NULL, then set them all to NULL. Helps simplify the code which
//uses the function pointers
if (m_lpfnCreateToolhelp32Snapshot == NULL || m_lpfnThread32First == NULL ||
m_lpfnThread32Next == NULL || m_lpfnModule32First == NULL || m_lpfnModule32Next == NULL)
ResetFunctionPointers();
}
void _EXCEPTION_LOGGER_DATA::ResetFunctionPointers()
{
m_lpfnCreateToolhelp32Snapshot = NULL;
m_lpfnThread32First = NULL;
m_lpfnThread32Next = NULL;
m_lpfnModule32First = NULL;
m_lpfnModule32Next = NULL;
if (m_hKernel32)
{
FreeLibrary(m_hKernel32);
m_hKernel32 = NULL;
}
}
//Statics used by the CExceptionLogger class
TCHAR CExceptionLogger::m_pszLogFilename[_MAX_PATH];
LPTOP_LEVEL_EXCEPTION_FILTER CExceptionLogger::m_pPreviousFilter = NULL;
HANDLE CExceptionLogger::m_hLogFile = INVALID_HANDLE_VALUE;
TCHAR CExceptionLogger::m_szTempLogLine[4096];
TCHAR CExceptionLogger::m_szTempFileName[_MAX_PATH];
BYTE CExceptionLogger::m_bySymbolInfo[sizeof(SYMBOL_INFO) + 1024];
//The one and only instance of the crash handler
CExceptionLogger g_CrashHandlerLogger;
//The local variable which handle the function pointers
_EXCEPTION_LOGGER_DATA _ExceptionLoggerData;
/////////////////////// Implementation ////////////////////////
CExceptionLogger::CExceptionLogger()
{
//Install our exception handler
m_pPreviousFilter = SetUnhandledExceptionFilter(UnHandledExceptionFilter);
//The name of the Crash handler log file will by default
//be "name of exe.crash.log"
GetModuleFileName(NULL, m_pszLogFilename, _MAX_PATH);
//Note we use the C Runtime here as the exception has not occured yet, so we
//can be confident that the CRT is ok at this time
_tcscat(m_pszLogFilename, _T(".exception"));
}
CExceptionLogger::~CExceptionLogger()
{
//Restore the old exception handler
SetUnhandledExceptionFilter(m_pPreviousFilter);
}
long CExceptionLogger::UnHandledExceptionFilter(_EXCEPTION_POINTERS* pExceptionInfo)
{
//Open up the log file, Notice we use FILE_FLAG_WRITE_THROUGHT to
//avoid caching of writes. That way if our code goes belly up, we will
//at least have all the data up to then safely written to disk
m_hLogFile = CreateFile(m_pszLogFilename, GENERIC_WRITE, 0, 0, OPEN_ALWAYS, FILE_FLAG_WRITE_THROUGH, 0);
if (m_hLogFile != INVALID_HANDLE_VALUE)
{
//Seek to the end of the Log file so that we always append to the crash log file
SetFilePointer(m_hLogFile, 0, 0, FILE_END);
//Let the helper function do its job
GenerateCrashLog(pExceptionInfo);
//Close the file now that we are finished with it
CloseHandle(m_hLogFile);
m_hLogFile = INVALID_HANDLE_VALUE;
}
//Chain to the previous exception if there is one
if (m_pPreviousFilter)
return m_pPreviousFilter(pExceptionInfo);
else
return EXCEPTION_CONTINUE_SEARCH;
}
void CExceptionLogger::GenerateCrashLog(_EXCEPTION_POINTERS* pExceptionInfo)
{
//Write out a banner first to separate multiple log entries
Log(_T("-----------------------------------------------------\r\n"));
//get the current time so that it goes into the file
SYSTEMTIME st;
GetLocalTime(&st);
Log(_T("Time of Exception: %02d:%02d:%02d.%03d %d/%d/%d (D/M/Y)\r\n"), st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, st.wDay, st.wMonth, st.wYear);
//Log the type of unhandled exception
Log(_T("Exception Code: 0x%08x\r\n"), pExceptionInfo->ExceptionRecord->ExceptionCode);
//Display extra info if an Access Violation
if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
{
if (pExceptionInfo->ExceptionRecord->NumberParameters > 0)
{
if (pExceptionInfo->ExceptionRecord->ExceptionInformation[0] == 0)
Log(_T("Access Violation Exception: Due to the thread attempting to read from an inaccessible address\r\n"));
else
Log(_T("Access Violation Exception: Due to the thread attempting to write to an inaccessible address\r\n"));
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -