C++中的异常处理技术及个人理解(2)

五、CRT 错误处理

除了 SEH 异常和 C++ 类型异常之外,C 运行时库 (CRT) 还提供了属于自己的错误处理机制,应该在程序中加以考虑。当发生 CRT 错误时,通常会看到一个 CRT 错误消息窗口。

C++中的异常处理技术及个人理解(2)_第1张图片

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 
#include 
#include 
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 
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 
#include 
#include   // 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 异常过滤器函数;
  • SIGFPESIGSEGV信号处理程序中,可以从_pxcptinfoptrs中声明的全局 CRT 变量中检索异常信息。
  • 在其他信号处理程序和 CRT 错误处理程序中,无法轻松提取异常信息。

如何获取发生异常时的当前 CPU 状态及对应的异常信息,代码示例如下:

#if _MSC_VER>=1300
#include 
#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;  
}

八、异常处理具体示例

该示例可以引发和捕获不同类型的异常并生成崩溃小型转储文件。应用程序如下图所示。

C++中的异常处理技术及个人理解(2)_第2张图片

要查看其工作原理,需要选择一个类型(输入 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.hCrashHandler.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);
}

你可能感兴趣的:(c++)