五、CRT 错误处理
除了 SEH 异常和 C++ 类型异常之外,C 运行时库 (CRT) 还提供了属于自己的错误处理机制,应该在程序中加以考虑。当发生 CRT 错误时,通常会看到一个 CRT 错误消息窗口。
1、终止处理程序set_terminate()
当 CRT 遇到未处理的 C++ 类型异常时,它会调用该terminate()
函数。要拦截此类调用并采取适当的措施,应该使用该set_terminate()
函数设置错误处理程序。
示例代码:
void my_terminate_handler()
{
// Abnormal program termination (terminate() function was called)
// Do something here
// Finally, terminate program
exit(1);
}
void main()
{
set_terminate(my_terminate_handler);
terminate();
}
存在
unexpected()
未与 Visual C++ 异常处理的当前实现一起使用的函数。但是,也可以考虑使用该set_unexpected()
函数为该函数设置处理程序unexpected()
。
【注】
在多线程环境中,每个线程的unexpected()和terminate()函数是单独维护的。每个新线程都需要安装自己的unexpected()和terminate()功能。因此,每个线程负责自己的意外和终止处理。
2、纯调用处理程序_set_purecall_handler()
使用_set_purecall_handler()
函数来处理纯虚函数调用。此函数可用于 VC++ .NET 2003 及更高版本。此函数适用于调用者进程的所有线程。
// _set_purecall_handler.cpp
// compile with: /W1
#include <tchar.h>
#include <stdio.h>
#include <stdlib.h>
class CDerived;
class CBase
{
public:
CBase(CDerived *derived): m_pDerived(derived) {};
~CBase();
virtual void function(void) = 0;
CDerived * m_pDerived;
};
class CDerived : public CBase
{
public:
CDerived() : CBase(this) {}; // C4355
virtual void function(void) {};
};
CBase::~CBase()
{
m_pDerived -> function();
}
void myPurecallHandler(void)
{
printf("In _purecall_handler.");
exit(0);
}
int _tmain(int argc, _TCHAR* argv[])
{
_set_purecall_handler(myPurecallHandler);
CDerived myDerived;
}
3、新的操作员故障处理程序_set_new_handler()
使用_set_new_handler()
函数来处理内存分配错误。
此函数可用于 VC++ .NET 2003 及更高版本。此函数适用于调用者进程的所有线程。考虑使用_set_new_mode()
函数来定义函数的错误行为。
#include <new.h>
int handle_program_memory_depletion( size_t )
{
// Your code
}
int main( void )
{
_set_new_handler( handle_program_memory_depletion );
int *pi = new int[BIG_NUMBER];
}
4、无效的参数处理程序_set_invalid_parameter_handler()
_set_invalid_parameter_handler()
当 CRT 在系统函数调用中检测到无效参数时,使用该函数来处理情况。此函数可用于 VC++ 2005 及更高版本。此函数适用于调用者进程的所有线程。
// crt_set_invalid_parameter_handler.c
// compile with: /Zi /MTd
#include <stdio.h>
#include <stdlib.h>
#include <crtdbg.h> // For _CrtSetReportMode
void myInvalidParameterHandler(const wchar_t* expression,
const wchar_t* function,
const wchar_t* file,
unsigned int line,
uintptr_t pReserved)
{
wprintf(L"Invalid parameter detected in function %s."
L" File: %s Line: %d\n", function, file, line);
wprintf(L"Expression: %s\n", expression);
}
int main( )
{
char* formatString;
_invalid_parameter_handler oldHandler, newHandler;
newHandler = myInvalidParameterHandler;
oldHandler = _set_invalid_parameter_handler(newHandler);
// Disable the message box for assertions.
_CrtSetReportMode(_CRT_ASSERT, 0);
// Call printf_s with invalid parameters.
formatString = NULL;
printf(formatString);
}
六、C++ 信号处理
C++ 提供了一种称为信号的程序中断机制,可以使用signal()
函数处理信号。
在 Visual C++ 中,有六种类型的信号:
SIGABRT
异常终止SIGFPE
浮点错误SIGILL
非法指令SIGINT
CTRL+C 信号SIGSEGV
非法存储访问SIGTERM
终止请求
实践表明,如果在主线程中设置信号处理程序,它会被 CRT 调用,而不是由函数设置的 SEH 异常处理程序调用,并且全局变量包含一个指向异常信息的指针。在其他线程中,使用函数设置的异常过滤器被调用而不是处理程序。
【注】
- 在 Linux 中,信号是异常处理的主要方式;
- 在 Windows 中,信号并没有得到应有的密集使用。C 运行时库不使用信号,而是提供多个C++ 特定的错误处理程序函数,例
_invalid_parameter_handler()
等等。
可以使用
raise()
功能手动生成所有六个信号
void sigabrt_handler(int)
{
// Caught SIGABRT C++ signal
// Terminate program
exit(1);
}
void main()
{
signal(SIGABRT, sigabrt_handler);
// Cause abort
abort();
}
七、关于异常信息的采集
发生异常时,通常希望获取 CPU 状态以确定代码中导致问题的位置。同时,可能希望将此信息传递给MiniDumpWriteDump()
函数,生成dump文件以稍后调试问题。
在与函数一起设置的 SEH 异常处理程序中SetUnhandledExceptionFilter()
,从EXCEPTION_POINTERS
作为函数参数传递的结构中检索异常信息。
- 在
__try{}__catch(Expression){}
构造中,使用GetExceptionInformation()
内部函数检索异常信息并将其作为参数传递给 SEH 异常过滤器函数; - 在
SIGFPE
和SIGSEGV
信号处理程序中,可以从_pxcptinfoptrs
中声明的全局 CRT 变量中检索异常信息。 - 在其他信号处理程序和 CRT 错误处理程序中,无法轻松提取异常信息。
如何获取发生异常时的当前 CPU 状态及对应的异常信息,代码示例如下:
#if _MSC_VER>=1300
#include <rtcapi.h>
#endif
#ifndef _AddressOfReturnAddress
// Taken from: http://msdn.microsoft.com/en-us/library/s975zw7k(VS.71).aspx
#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif
// _ReturnAddress and _AddressOfReturnAddress should be prototyped before use
EXTERNC void * _AddressOfReturnAddress(void);
EXTERNC void * _ReturnAddress(void);
#endif
// The following function retrieves exception info
void GetExceptionPointers(DWORD dwExceptionCode,
EXCEPTION_POINTERS** ppExceptionPointers)
{
// The following code was taken from VC++ 8.0 CRT (invarg.c: line 104)
EXCEPTION_RECORD ExceptionRecord;
CONTEXT ContextRecord;
memset(&ContextRecord, 0, sizeof(CONTEXT));
#ifdef _X86_
__asm {
mov dword ptr [ContextRecord.Eax], eax
mov dword ptr [ContextRecord.Ecx], ecx
mov dword ptr [ContextRecord.Edx], edx
mov dword ptr [ContextRecord.Ebx], ebx
mov dword ptr [ContextRecord.Esi], esi
mov dword ptr [ContextRecord.Edi], edi
mov word ptr [ContextRecord.SegSs], ss
mov word ptr [ContextRecord.SegCs], cs
mov word ptr [ContextRecord.SegDs], ds
mov word ptr [ContextRecord.SegEs], es
mov word ptr [ContextRecord.SegFs], fs
mov word ptr [ContextRecord.SegGs], gs
pushfd
pop [ContextRecord.EFlags]
}
ContextRecord.ContextFlags = CONTEXT_CONTROL;
#pragma warning(push)
#pragma warning(disable:4311)
ContextRecord.Eip = (ULONG)_ReturnAddress();
ContextRecord.Esp = (ULONG)_AddressOfReturnAddress();
#pragma warning(pop)
ContextRecord.Ebp = *((ULONG *)_AddressOfReturnAddress()-1);
#elif defined (_IA64_) || defined (_AMD64_)
/* Need to fill up the Context in IA64 and AMD64. */
RtlCaptureContext(&ContextRecord);
#else /* defined (_IA64_) || defined (_AMD64_) */
ZeroMemory(&ContextRecord, sizeof(ContextRecord));
#endif /* defined (_IA64_) || defined (_AMD64_) */
ZeroMemory(&ExceptionRecord, sizeof(EXCEPTION_RECORD));
ExceptionRecord.ExceptionCode = dwExceptionCode;
ExceptionRecord.ExceptionAddress = _ReturnAddress();
EXCEPTION_RECORD* pExceptionRecord = new EXCEPTION_RECORD;
memcpy(pExceptionRecord, &ExceptionRecord, sizeof(EXCEPTION_RECORD));
CONTEXT* pContextRecord = new CONTEXT;
memcpy(pContextRecord, &ContextRecord, sizeof(CONTEXT));
*ppExceptionPointers = new EXCEPTION_POINTERS;
(*ppExceptionPointers)->ExceptionRecord = pExceptionRecord;
(*ppExceptionPointers)->ContextRecord = pContextRecord;
}
八、异常处理具体示例
该示例可以引发和捕获不同类型的异常并生成崩溃小型转储文件。应用程序如下图所示。
要查看其工作原理,需要选择一个类型(输入 0 到 13 之间的数字)并按 Enter,然后引发异常并由异常处理程序捕获,然后异常处理程序使用dbghelp.dll 中的MiniDumpWriteDump()
函数调用生成崩溃小型转储文件的代码。
应用程序中有两个逻辑部分:
- 引发异常的部分
- 捕获异常并生成崩溃小型转储的部分
文件main.cpp包含的main()
函数包含异常类型选择的代码和一个选择引发什么异常的大开关。要引发异常,主函数可能会调用raise()
或调用RaiseException()
函数、抛出 C++ 类型的异常、调用一些辅助代码,例如RecurseAlloc()
orsigfpe_test()
等。
在main()
函数的开头,CCrashHandler
创建了一个类的实例。此实例用于稍后捕获异常。
-
调用
CCrashHandler::SetProcessExceptionHandlers()
方法来设置为整个进程工作的异常处理程序(如 SEH 异常处理程序)。 -
调用
CCrashHandler::SetThreadExceptionHandlers()
方法来安装仅适用于当前线程的异常处理程序(如意外或终止处理程序)。
主要功能的代码:
void main()
{
CCrashHandler ch;
ch.SetProcessExceptionHandlers();
ch.SetThreadExceptionHandlers();
printf("Choose an exception type:\n");
printf("0 - SEH exception\n");
printf("1 - terminate\n");
printf("2 - unexpected\n");
printf("3 - pure virtual method call\n");
printf("4 - invalid parameter\n");
printf("5 - new operator fault\n");
printf("6 - SIGABRT\n");
printf("7 - SIGFPE\n");
printf("8 - SIGILL\n");
printf("9 - SIGINT\n");
printf("10 - SIGSEGV\n");
printf("11 - SIGTERM\n");
printf("12 - RaiseException\n");
printf("13 - throw C++ typed exception\n");
printf("Your choice > ");
int ExceptionType = 0;
scanf_s("%d", &ExceptionType);
switch(ExceptionType)
{
case 0: // SEH
{
// Access violation
int *p = 0;
#pragma warning(disable : 6011)
// warning C6011: Dereferencing NULL pointer 'p'
*p = 0;
#pragma warning(default : 6011)
}
break;
case 1: // terminate
{
// Call terminate
terminate();
}
break;
case 2: // unexpected
{
// Call unexpected
unexpected();
}
break;
case 3: // pure virtual method call
{
// pure virtual method call
CDerived derived;
}
break;
case 4: // invalid parameter
{
char* formatString;
// Call printf_s with invalid parameters.
formatString = NULL;
#pragma warning(disable : 6387)
// warning C6387: 'argument 1' might be '0': this does
// not adhere to the specification for the function 'printf'
printf(formatString);
#pragma warning(default : 6387)
}
break;
case 5: // new operator fault
{
// Cause memory allocation error
RecurseAlloc();
}
break;
case 6: // SIGABRT
{
// Call abort
abort();
}
break;
case 7: // SIGFPE
{
// floating point exception ( /fp:except compiler option)
sigfpe_test();
}
break;
case 8: // SIGILL
{
raise(SIGILL);
}
break;
case 9: // SIGINT
{
raise(SIGINT);
}
break;
case 10: // SIGSEGV
{
raise(SIGSEGV);
}
break;
case 11: // SIGTERM
{
raise(SIGTERM);
}
break;
case 12: // RaiseException
{
// Raise noncontinuable software exception
RaiseException(123, EXCEPTION_NONCONTINUABLE, 0, NULL);
}
break;
case 13: // throw
{
// Throw typed C++ exception.
throw 13;
}
break;
default:
{
printf("Unknown exception type specified.");
_getch();
}
break;
}
}
文件CrashHandler.h和CrashHandler.cpp包含异常处理和崩溃信息转储生成功能的实现
class CCrashHandler
{
public:
// Constructor
CCrashHandler();
// Destructor
virtual ~CCrashHandler();
// Sets exception handlers that work on per-process basis
void SetProcessExceptionHandlers();
// Installs C++ exception handlers that function on per-thread basis
void SetThreadExceptionHandlers();
// Collects current process state.
static void GetExceptionPointers(
DWORD dwExceptionCode,
EXCEPTION_POINTERS** pExceptionPointers);
// This method creates minidump of the process
static void CreateMiniDump(EXCEPTION_POINTERS* pExcPtrs);
/* Exception handler functions. */
static LONG WINAPI SehHandler(PEXCEPTION_POINTERS pExceptionPtrs);
static void __cdecl TerminateHandler();
static void __cdecl UnexpectedHandler();
static void __cdecl PureCallHandler();
static void __cdecl InvalidParameterHandler(const wchar_t* expression,
const wchar_t* function, const wchar_t* file,
unsigned int line, uintptr_t pReserved);
static int __cdecl NewHandler(size_t);
static void SigabrtHandler(int);
static void SigfpeHandler(int /*code*/, int subcode);
static void SigintHandler(int);
static void SigillHandler(int);
static void SigsegvHandler(int);
static void SigtermHandler(int);
};
从上面的代码中可以看出,CCrashHandler
该类有两种设置异常处理程序的方法:SetProcessExceptionHandlers()
和SetThreadExceptionHandlers()
,分别用于整个进程和当前线程。这两种方法的代码如下所示。
void CCrashHandler::SetProcessExceptionHandlers()
{
// Install top-level SEH handler
SetUnhandledExceptionFilter(SehHandler);
// Catch pure virtual function calls.
// Because there is one _purecall_handler for the whole process,
// calling this function immediately impacts all threads. The last
// caller on any thread sets the handler.
// http://msdn.microsoft.com/en-us/library/t296ys27.aspx
_set_purecall_handler(PureCallHandler);
// Catch new operator memory allocation exceptions
_set_new_handler(NewHandler);
// Catch invalid parameter exceptions.
_set_invalid_parameter_handler(InvalidParameterHandler);
// Set up C++ signal handlers
_set_abort_behavior(_CALL_REPORTFAULT, _CALL_REPORTFAULT);
// Catch an abnormal program termination
signal(SIGABRT, SigabrtHandler);
// Catch illegal instruction handler
signal(SIGINT, SigintHandler);
// Catch a termination request
signal(SIGTERM, SigtermHandler);
}
void CCrashHandler::SetThreadExceptionHandlers()
{
// Catch terminate() calls.
// In a multithreaded environment, terminate functions are maintained
// separately for each thread. Each new thread needs to install its own
// terminate function. Thus, each thread is in charge of its own termination handling.
// http://msdn.microsoft.com/en-us/library/t6fk7h29.aspx
set_terminate(TerminateHandler);
// Catch unexpected() calls.
// In a multithreaded environment, unexpected functions are maintained
// separately for each thread. Each new thread needs to install its own
// unexpected function. Thus, each thread is in charge of its own unexpected handling.
// http://msdn.microsoft.com/en-us/library/h46t5b69.aspx
set_unexpected(UnexpectedHandler);
// Catch a floating point error
typedef void (*sigh)(int);
signal(SIGFPE, (sigh)SigfpeHandler);
// Catch an illegal instruction
signal(SIGILL, SigillHandler);
// Catch illegal storage access errors
signal(SIGSEGV, SigsegvHandler);
}
在类声明中,还可以看到几个异常处理函数,例如SehHandler()
,TerminateHandler()
等等。发生异常时,可以调用这些异常处理程序中的任何一个。处理函数(可选)检索异常信息并调用崩溃小型转储生成代码,然后通过TerminateProcess()
函数调用终止进程。
-
GetExceptionPointers()
静态方法来检索异常信息。 -
CreateMiniDump()
方法用于崩溃信息转储生成。它需要一个指向EXCEPTION_POINTERS
包含异常信息的结构的指针。该方法调用MiniDumpWriteDump()
Microsoft 调试帮助库中的函数以生成小型转储文件。该方法的代码如下所示。
// This method creates minidump of the process
void CCrashHandler::CreateMiniDump(EXCEPTION_POINTERS* pExcPtrs)
{
HMODULE hDbgHelp = NULL;
HANDLE hFile = NULL;
MINIDUMP_EXCEPTION_INFORMATION mei;
MINIDUMP_CALLBACK_INFORMATION mci;
// Load dbghelp.dll
hDbgHelp = LoadLibrary(_T("dbghelp.dll"));
if(hDbgHelp==NULL)
{
// Error - couldn't load dbghelp.dll
return;
}
// Create the minidump file
hFile = CreateFile(
_T("crashdump.dmp"),
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if(hFile==INVALID_HANDLE_VALUE)
{
// Couldn't create file
return;
}
// Write minidump to the file
mei.ThreadId = GetCurrentThreadId();
mei.ExceptionPointers = pExcPtrs;
mei.ClientPointers = FALSE;
mci.CallbackRoutine = NULL;
mci.CallbackParam = NULL;
typedef BOOL (WINAPI *LPMINIDUMPWRITEDUMP)(
HANDLE hProcess,
DWORD ProcessId,
HANDLE hFile,
MINIDUMP_TYPE DumpType,
CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
CONST PMINIDUMP_USER_STREAM_INFORMATION UserEncoderParam,
CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
LPMINIDUMPWRITEDUMP pfnMiniDumpWriteDump =
(LPMINIDUMPWRITEDUMP)GetProcAddress(hDbgHelp, "MiniDumpWriteDump");
if(!pfnMiniDumpWriteDump)
{
// Bad MiniDumpWriteDump function
return;
}
HANDLE hProcess = GetCurrentProcess();
DWORD dwProcessId = GetCurrentProcessId();
BOOL bWriteDump = pfnMiniDumpWriteDump(
hProcess,
dwProcessId,
hFile,
MiniDumpNormal,
&mei,
NULL,
&mci);
if(!bWriteDump)
{
// Error writing dump.
return;
}
// Close file
CloseHandle(hFile);
// Unload dbghelp.dll
FreeLibrary(hDbgHelp);
}