views:

498

answers:

2

I wanted to handle all internal errors gracefully, without program termination.

As discussed here, using _set_se_translator catches divide-by-zero errors.

But it does not catch, for example, C runtime library error -1073740777 (0xc0000417), which can be caused by format strings for printf which have the percent sign where they shouldn't. (That is just an example; of course we should check for such strings). To handle these, _set_invalid_parameter_handler is needed.

There are about ten other such handlers listed here.

In addition, this one will catch uncaught C++ exceptions: SetUnhandledExceptionFilter. Thus, it can be used together with the _set_ ... functions. (An article about using it in MSVC 2008.)

I want to catch any and all errors so I can handle them (by logging, throwing a modern C++ standard exception and returning an application-specific error code ). Is there a single handler that catches everything and anything?

See also this on StackOverflow.

I am using Visual Studio 2008.

+2  A: 

There is no universal handler. You need to install each one. I have used something like this:

///////////////////////////////////////////////////////////////////////////
template<class K, class V>
class MapInitializer
{
    std::map<K,V> m;
public:
    operator std::map<K,V>() const 
    { 
        return m; 
    }

    MapInitializer& Add( const K& k, const V& v )
    {
        m[ k ] = v;
        return *this;
    }
};

///////////////////////////////////////////////////////////////////////////
struct StructuredException : std::exception
{
    const char *const msg;
    StructuredException( const char* const msg_ ) : msg( msg_ ) {}
    virtual const char* what() const { return msg; }
};

///////////////////////////////////////////////////////////////////////////
class ExceptionHandlerInstaller
{
public:
    ExceptionHandlerInstaller()
        : m_oldTerminateHandler( std::set_terminate( TerminateHandler ) )
        , m_oldUnexpectedHandler( std::set_unexpected( UnexpectedHandler ) )
        , m_oldSEHandler( _set_se_translator( SEHandler ) )
    {}

    ~ExceptionHandlerInstaller() 
    { 
        std::set_terminate( m_oldTerminateHandler );
        std::set_unexpected( m_oldUnexpectedHandler );
        _set_se_translator( m_oldSEHandler );
    }

private:
    static void TerminateHandler() 
    { 
        TRACE( "\n\n**** terminate handler called! ****\n\n" );
    }

    static void UnexpectedHandler() 
    { 
        TRACE( "\n\n**** unexpected exception handler called! ****\n\n" );
    }

    static void SEHandler( const unsigned code, EXCEPTION_POINTERS* )
    {
        SEMsgMap::const_iterator it = m_seMsgMap.find( code );
        throw StructuredException( it != m_seMsgMap.end() 
            ? it->second 
            : "Structured exception translated to C++ exception." );
    }

    const std::terminate_handler  m_oldTerminateHandler;
    const std::unexpected_handler m_oldUnexpectedHandler;
    const _se_translator_function m_oldSEHandler;

    typedef std::map<unsigned, const char*> SEMsgMap;
    static const SEMsgMap m_seMsgMap;
};

///////////////////////////////////////////////////////////////////////////
// Message map for structured exceptions copied from the MS help file
///////////////////////////////////////////////////////////////////////////
const ExceptionHandlerInstaller::SEMsgMap ExceptionHandlerInstaller::m_seMsgMap 
    = MapInitializer<ExceptionHandlerInstaller::SEMsgMap::key_type, 
                     ExceptionHandlerInstaller::SEMsgMap::mapped_type>()
    .Add( EXCEPTION_ACCESS_VIOLATION,         "The thread attempts to read from or write to a virtual address for which it does not have access. This value is defined as STATUS_ACCESS_VIOLATION." )
    .Add( EXCEPTION_ARRAY_BOUNDS_EXCEEDED,    "The thread attempts to access an array element that is out of bounds, and the underlying hardware supports bounds checking. This value is defined as STATUS_ARRAY_BOUNDS_EXCEEDED." )
    .Add( EXCEPTION_BREAKPOINT,               "A breakpoint is encountered. This value is defined as STATUS_BREAKPOINT." )
    .Add( EXCEPTION_DATATYPE_MISALIGNMENT,    "The thread attempts to read or write data that is misaligned on hardware that does not provide alignment. For example, 16-bit values must be aligned on 2-byte boundaries, 32-bit values on 4-byte boundaries, and so on. This value is defined as STATUS_DATATYPE_MISALIGNMENT." )
    .Add( EXCEPTION_FLT_DENORMAL_OPERAND,     "One of the operands in a floating point operation is denormal. A denormal value is one that is too small to represent as a standard floating point value. This value is defined as STATUS_FLOAT_DENORMAL_OPERAND." )
    .Add( EXCEPTION_FLT_DIVIDE_BY_ZERO,       "The thread attempts to divide a floating point value by a floating point divisor of 0 (zero). This value is defined as STATUS_FLOAT_DIVIDE_BY_ZERO." )
    .Add( EXCEPTION_FLT_INEXACT_RESULT,       "The result of a floating point operation cannot be represented exactly as a decimal fraction. This value is defined as STATUS_FLOAT_INEXACT_RESULT." )
    .Add( EXCEPTION_FLT_INVALID_OPERATION,    "A floatin point exception that is not included in this list. This value is defined as STATUS_FLOAT_INVALID_OPERATION." )
    .Add( EXCEPTION_FLT_OVERFLOW,             "The exponent of a floating point operation is greater than the magnitude allowed by the corresponding type. This value is defined as STATUS_FLOAT_OVERFLOW." )
    .Add( EXCEPTION_FLT_STACK_CHECK,          "The stack has overflowed or underflowed, because of a floating point operation. This value is defined as STATUS_FLOAT_STACK_CHECK." )
    .Add( EXCEPTION_FLT_UNDERFLOW,            "The exponent of a floating point operation is less than the magnitude allowed by the corresponding type. This value is defined as STATUS_FLOAT_UNDERFLOW." )
    .Add( EXCEPTION_GUARD_PAGE,               "The thread accessed memory allocated with the PAGE_GUARD modifier. This value is defined as STATUS_GUARD_PAGE_VIOLATION." )
    .Add( EXCEPTION_ILLEGAL_INSTRUCTION,      "The thread tries to execute an invalid instruction. This value is defined as STATUS_ILLEGAL_INSTRUCTION." )
    .Add( EXCEPTION_IN_PAGE_ERROR,            "The thread tries to access a page that is not present, and the system is unable to load the page. For example, this exception might occur if a network connection is lost while running a program over a network. This value is defined as STATUS_IN_PAGE_ERROR." )
    .Add( EXCEPTION_INT_DIVIDE_BY_ZERO,       "The thread attempts to divide an integer value by an integer divisor of 0 (zero). This value is defined as STATUS_INTEGER_DIVIDE_BY_ZERO." )
    .Add( EXCEPTION_INT_OVERFLOW,             "The result of an integer operation causes a carry out of the most significant bit of the result. This value is defined as STATUS_INTEGER_OVERFLOW." )
    .Add( EXCEPTION_INVALID_DISPOSITION,      "An exception handler returns an invalid disposition to the exception dispatcher. Programmers using a high-level language such as C should never encounter this exception. This value is defined as STATUS_INVALID_DISPOSITION." )
    .Add( EXCEPTION_INVALID_HANDLE,           "The thread used a handle to a kernel object that was invalid (probably because it had been closed.) This value is defined as STATUS_INVALID_HANDLE." )
    .Add( EXCEPTION_NONCONTINUABLE_EXCEPTION, "The thread attempts to continue execution after a non-continuable exception occurs. This value is defined as STATUS_NONCONTINUABLE_EXCEPTION." )
    .Add( EXCEPTION_PRIV_INSTRUCTION,         "The thread attempts to execute an instruction with an operation that is not allowed in the current computer mode. This value is defined as STATUS_PRIVILEGED_INSTRUCTION." )
    .Add( EXCEPTION_SINGLE_STEP,              "A trace trap or other single instruction mechanism signals that one instruction is executed. This value is defined as STATUS_SINGLE_STEP." )
    .Add( EXCEPTION_STACK_OVERFLOW,           "The thread uses up its stack. This value is defined as STATUS_STACK_OVERFLOW." );

Then in main or app init, I do this:

BOOL CMyApp::InitInstance()
{
    ExceptionHandlerInstaller ehi;
    // ...
}

Note that this translates structured exceptions to regular exceptions but handles terminate (which gets called, e.g., when a nothrow() function throws an exception) by simply printing an error message. It is highly unlikely that you want to use a single handler for all different types of errors, which is why they don't provide it.

mlimber
Remember that you need to call `_set_se_translator` from each thread...
Len Holgate
> It is highly unlikely that you want to use a single handler > for all different types of errorsBut in loop-oriented programs, I do want exactly that. Just as in Java, I handle any errors at the end of the loop (except for severe ones like out-of-memory) and let the request handling continue.
Joshua Fox
+4  A: 

I would caution against this.

Internal errors are not recoverable. If you divide by zero or whatever - the program is not recoverable.

If you turn a termination handler into something that continues the program running, you can have no assurances of the STATE of the program, and you can just crash and corrupt in different ways later. Imagine that the program was holding some locks or other resources at the time of the termination you diverted, for example!

Lets have a nasty example:

void log(const char* fmt,...) {
   lock(logfile);
   va_args...
   fvprintf(logfile,fmt,__...  <--- this line calls the terminator
   unlock(logfile);
}

What happens if you don't terminate the program? What happens to the program the next time someone tries to log something?

I cannot emphasise this enough - you should use hooking termination methods for extra logging and such, and nothing else. You should always continue to exit afterwards.


There is a completely different class of error, that can be intercepted:

A lot of APIs communicate with the caller using return codes to signal error conditions. It is appropriate to use macros or helper functions to check these return codes and translate them into exceptions. This is because that choice is in your code where you can see it.

If you override the _set_errno handler or something, you'd cause code you hadn't wrote that expected the setter to return normally to not return, and it might not have completed its cleanup.

Will
But you may simply want to translate the error (say, to a standard exception) rather than dealing with various other error handling mechanisms.
mlimber
Thanks, but some internal errors are recoverable, for the same reason that any error can be recoverable. If someone gives me a string and I use it as format string, and it has a % in it, then, to be sure, I should have checked that, but the program should not have to fail on printf/sprintf--what might just be the printing of a message. Many request-response oriented apps can fail on a single loop but then continue.As for error return codes -- the mentioned errors do not return error codes, but just terminate the program.
Joshua Fox
Joshua, you absolutely should have checked that; that's a classic security breach.
Will
@Joshua, in your example: it is definitely better to fix your program and not use a user-supplied string as a format string without validation. I agree with your general point though.
Alok
I'll clarify why its not recoverable in the post again
Will
Of course input should be checked; the point is simply that a printf failure should, in many cases, not be allowed to terminate the program.
Joshua Fox
Printf failures only call the handler a fraction of the time - the rest of the time, they just crash.
Will
"If you divide by zero or whatever - the program is not recoverable."In many cases, you are right. In others, it is recoverable.But more importantly, even where I don't want to truly recover, I do want to log the error and return an application-specific error code.
Joshua Fox
@mlimber -- Why would you want to translate divide by zero into a C++ exception, though? "Catching" this and attempting to do something meaningful with it is idiotic. If your program divides by zero, dereferences an invalid pointer, etc., that's a bug. It deserves to fail until you fix the bug. If your program's logic is predicated on the existence of bugs, masking bugs, etc., that's a really bad idea.
asveikau
@asveikau, Bugs are inevitable in any large program, and handling failure states in a consistent way can be a useful technique, particularly if one doesn't control all the code (e.g., relying on opaque third-party libraries, supporting user plug-ins, etc.).
mlimber
You can lead a horse to water but you cannot make it drink
Will