Difference between a C++ exception and Structured Exception
Asked Answered
B

6

25

Can someone explain the difference between a C++ exception and a structured exception in MFC?

Basidiospore answered 24/9, 2010 at 11:35 Comment(0)
C
23

You actually have three mechanisms:

  • C++ exceptions, implemented by the compiler (try/catch)
  • Structured Exception Handling (SEH), provided by Windows (__try / __except)
  • MFC exception macros (TRY, CATCH - built on top of SEH / C++ exceptions - see also TheUndeadFish's comment)

C++ exceptions usually guarantee automatic cleanup during stack unwinding (i.e. destructors of local objects run), the other mechanisms don't.

C++ exceptions only occur when they are explicitly thrown. Structured Exceptions may occur for many operations, e.g. due to undefined behavior, passing invalid pointers to APIs, unmounting the backing store of a memory mapped file, and many more.

MFC did introduce the exception macros to support exceptions even if compilers didn't implement them.

Canny answered 24/9, 2010 at 11:52 Comment(8)
Just for the sake of curiosity, MFC were ever intended to work on any compiler other than VC++?Teatime
C++ exceptions are not required to guarantee cleanup. Compile with /EHa.Abdias
MFC did work on Watcom C/C++ 10. Somehow. Without the wizards and stuff.Canny
@Hans - umm, really? /EHa vs. /EHs does only affect whether structured exceptions are caught by a C++ exception handler. I'm pretty sure the C++ standard guarantees cleanup for C++ exceptions (leaving this implementaiton-defined makes no sense, as the necessary code would be fundamentally different).Canny
Its interesting @Canny has seen MFC work with a non VC compiler. MFC, when its all boiled down, is a set of classes and macros that build upon the Win32 C API so it should have been compilable by non-VC compilers but I doubt this was intended. Aside: If you dig deep enough into the MFC source code you can learn a lot about how to wrap the windows message loop to meet custom needs.Quiles
Wile MFC's exceptions used different methods in the past (before VC supported C++ exceptions, I think), MFC's exceptions are now built on top of C++ exceptions. It's in fact quite possible to convert from the MFC macros to C++ try/catch, as explained here: msdn.microsoft.com/en-us/library/19z28s5c.aspx So underneath it all, there are really just 2 distinct types: C++ exceptions and SEH.Indictable
Thanks to all who have contributed. :)Basidiospore
@TheUndeadFish: Thanks for the upate, I still want to keep it listed because it's a 3rd set of syntax and rules how to handle exceptions.Canny
A
14

It is a heavy MSVC++ implementation detail, but on Windows a C++ exception is also a SEH exception. The exception code is 0xE04D5343 (last three bytes = 'MSC'). And all of the regular SEH support is used to unwind the stack, get automatic cleanup code to run and to filter the exception so the proper catch clause is selected. As demonstrated here.

Getting the thrown exception object in the filter expression is plumbing that's added by the CRT beyond what SEH provides, necessarily so since it is C++ specific.

A further implementation detail is the /EH compiler setting. The default (/EHsc) allows the compiler to optimize the code that's generated and suppress the exception filters that are needed to run automatic cleanup. If it can see that none of the emitted C++ code can throw an exception. To get automatic cleanup for SEH exceptions you have to compile with /EHa so that this optimization is suppressed.

One strategy to marry C++ exceptions with SEH is to use _set_se_translator() so you can translate an SEH exception to a C++ exception. Albeit that it isn't often wise to catch SEH exceptions, they are almost always nasty. You'd normally favor using __try/__catch, as shown in the linked answer.

Abdias answered 24/9, 2010 at 14:38 Comment(5)
Will __try/__except blocks catch all SEH exceptions as well as all C++ exceptions?Incision
Also important to note is that _set_se_translator() is only called once for each function invocation on the stack that has try blocks, according to MSDN, and must be set on a per-thread basis.Incision
This and doug's answer are the only acceptable answers; the others do not make clear that C++ exceptions are merely added functionality and a C++ abstraction that runs ontop of the underlying exception handling mechanism in the windows kernel, which is fundamentally SEH.Demoss
What's key to know is that C++ exceptions only catch thrown objects and do not catch runtime errors like a segmentation fault, only SEH exception filters can be used to capture these, and there will ultimately be a SEH UnhandledExceptionFilter at the base function of the thread stackDemoss
Actually there is another code for C++ exception in SEH: 0xE06D7363 (lowercase 'msc'), I just caught one with my custom SEH handler. Here is more info about it. And here is Raymond Chen's article.Gaskin
H
8

A C++ exception is a feature of the programming language C++. A structured exception is a different concept of the Windows operating system. These two use similar syntax, but are technically different. Windows structured exceptions are not only usable with C++ but also e.g. with C.

Sometimes a solution to unify the handling of both: In a Windows application you can provide a handler function, which catches all structured exceptions and throws a C++ exception (defined by you).

Hans answered 24/9, 2010 at 11:44 Comment(0)
C
6

Both provide mechanisms for stack unwinding when errors occur.

Structured exceptions are provided by Windows, with support from the kernel. They are raised by Windows if you do things like access an invalid memory location. They also are used to support features like automatic stack growth. They're used fairly rarely by themselves, but language exceptions in C++, .NET, and similar languages are often built on top of them. You use special keywords like __try and __catch to deal with these exceptions. However, dealing with them is comparatively difficult and error-prone, because you can break features like automatic stack expansion, as well as potentially breaking C++ language exceptions.

C++ exceptions are specified by the C++ language. The data types that is thrown and caught are C++ objects (including the possibility of primitive types). The compiler and runtime implements these on top of the underlying structured exception mechanism. This is what you get if you use the try, catch and throw keywords of the C++ language.

SEH exceptions have more features than C++ exceptions, like supporting resumption, and so-called "vectored" handlers (which receive notifications of exceptions, but don't necessarily prevent stack unwinding), but unless you specifically know you want to use them, I'd avoid them. Probably the most common use of them is to write a crash dump using MiniDumpWriteDump if your program does something illegal or undefined.

Calceiform answered 24/9, 2010 at 11:51 Comment(0)
P
2

C++ exceptions will work cross platform. Unfortunately SEH will severly restrict portability (except may be across different Windows versions).

Also SEH seems to capture lots of native Windows exceptions (like Access Violation, An Invalid handle was specified) etc.

Peafowl answered 24/9, 2010 at 13:54 Comment(0)
G
1

On Windows C++ exceptions and SEH are basically the same thing. C++ exception is just one of many possible SEH exceptions that can be thrown. Some are thrown from hardware, some from software. C++ ones are software triggered exception. Here is very cool blog post from Raymond Chen that will help you dig a little bit deeper into the rabbit hole of SEH exceptions and how C++ exceptions are built upon them.

Here is my practical implementation of the method from above article. I'm actually shocked than after 12 years and:

Note that this information falls under the category of implementation detail. There is no guarantee that this method will continue to work in the future, so don’t write code that relies on it. It’s just a debugging tip.

it's still working the same on the latest Windows 10 in 2022! My solution also features full info extracted from the exception, so you won't even need a debugger from now on, just catch exception with __try\__except and use this function to print info for any exception:

#define EXCEPTION_CPP_LOWERCASE 0xE06D7363
#define EXCEPTION_CPP_UPPERCASE 0xE04D5343

void pexcept(DWORD c, _EXCEPTION_RECORD *er)
{
    cout << "--------------------------------------------------------------------" << endl;
    switch(c)
    {
    case STILL_ACTIVE:
        cout << "STILL_ACTIVE" << endl;
        break;
    case EXCEPTION_ACCESS_VIOLATION:
        if(er->ExceptionInformation[0] == 0)
        {
            cout << "READ ";
        }
        else if(er->ExceptionInformation[0] == 1)
        {
            cout << "WRITE ";
        }
        else if(er->ExceptionInformation[0] == 8)
        {
            cout << "DEP "; // Data Execution Prevention
        }
        cout << "ACCESS_VIOLATION" << endl;
        break;
    case EXCEPTION_DATATYPE_MISALIGNMENT:
        cout << "DATATYPE_MISALIGNMENT" << endl;
        break;
    case EXCEPTION_BREAKPOINT:
        cout << "BREAKPOINT" << endl;
        break;
    case EXCEPTION_SINGLE_STEP:
        cout << "SINGLE_STEP" << endl;
        break;
    case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
        cout << "ARRAY_BOUNDS_EXCEEDED" << endl;
        break;
    case EXCEPTION_FLT_DENORMAL_OPERAND:
        cout << "FLOAT_DENORMAL_OPERAND" << endl;
        break;
    case EXCEPTION_FLT_DIVIDE_BY_ZERO:
        cout << "FLOAT_DIVIDE_BY_ZERO" << endl;
        break;
    case EXCEPTION_FLT_INEXACT_RESULT:
        cout << "FLOAT_INEXACT_RESULT" << endl;
        break;
    case EXCEPTION_FLT_INVALID_OPERATION:
        cout << "FLOAT_INVALID_OPERATION" << endl;
        break;
    case EXCEPTION_FLT_OVERFLOW:
        cout << "FLOAT_OVERFLOW" << endl;
        break;
    case EXCEPTION_FLT_STACK_CHECK:
        cout << "FLOAT_STACK_CHECK" << endl;
        break;
    case EXCEPTION_FLT_UNDERFLOW:
        cout << "FLOAT_UNDERFLOW" << endl;
        break;
    case EXCEPTION_INT_DIVIDE_BY_ZERO:
        cout << "INTEGER_DIVIDE_BY_ZERO" << endl;
        break;
    case EXCEPTION_INT_OVERFLOW:
        cout << "INTEGER_OVERFLOW" << endl;
        break;
    case EXCEPTION_PRIV_INSTRUCTION:
        cout << "PRIVILEGED_INSTRUCTION" << endl;
        break;
    case EXCEPTION_IN_PAGE_ERROR:
        if(er->ExceptionInformation[0] == 0)
        {
            cout << "READ ";
        }
        else if(er->ExceptionInformation[0] == 1)
        {
            cout << "WRITE ";
        }
        else if(er->ExceptionInformation[0] == 8)
        {
            cout << "DEP "; // Data Execution Prevention
        }
        cout << "IN_PAGE_ERROR" << endl;
        cout << "DATA VADDRESS:       " << er->ExceptionInformation[1] << endl;
        cout << "NTSTATUS CAUSE:      " << er->ExceptionInformation[2] << endl;
        break;
    case EXCEPTION_ILLEGAL_INSTRUCTION:
        cout << "ILLEGAL_INSTRUCTION" << endl;
        break;
    case EXCEPTION_NONCONTINUABLE_EXCEPTION:
        cout << "NONCONTINUABLE_EXCEPTION" << endl;
        break;
    case EXCEPTION_STACK_OVERFLOW:
        cout << "STACK_OVERFLOW" << endl;
        break;
    case EXCEPTION_INVALID_DISPOSITION:
        cout << "INVALID_DISPOSITION" << endl;
        break;
    case EXCEPTION_GUARD_PAGE:
        cout << "GUARD_PAGE_VIOLATION" << endl;
        break;
    case EXCEPTION_INVALID_HANDLE:
        cout << "INVALID_HANDLE" << endl;
        break;
    // case EXCEPTION_POSSIBLE_DEADLOCK:
        // cout << "POSSIBLE_DEADLOCK" << endl;
        // break;
    case CONTROL_C_EXIT:
        cout << "CONTROL_C_EXIT" << endl;
        break;
    case EXCEPTION_CPP_LOWERCASE:
    case EXCEPTION_CPP_UPPERCASE:
        cout << "CPP_EXCEPTION" << endl;
        cout << "PARAMS NUM: " << er->NumberParameters << endl;
        for(ui64 i = 0; i < er->NumberParameters; ++i)
        {
            cout << dec << "PARAM" << setw(2) << left << i << " ";
            switch(i)
            {
            case 0:
                cout << "      SOME INTERNAL VALUE";
                break;
            case 1:
                cout << "    POINTER TO THROWN OBJ";
                break;
            case 2:
                cout << "    POINTER TO OBJECT INF";
                break;
            case 3:
                cout << "DLL/EXE THROWER HINSTANCE";
                break;
            }
            
            cout << ": 0x" << hex
                << uppercase << er->ExceptionInformation[i] << endl;
        }
    {
        ui64 hinst = er->ExceptionInformation[3];
        DWORD *obj_inf = (DWORD *)er->ExceptionInformation[2];
        cout << hex << uppercase << obj_inf[0] << endl;
        cout << obj_inf[1] << endl;
        cout << obj_inf[2] << endl;
        cout << obj_inf[3] << endl;
        cout << "-----------------------------" << endl;
        obj_inf = (DWORD *)(hinst + obj_inf[3]);
        cout << obj_inf[0] << endl;
        cout << obj_inf[1] << endl;
        cout << "-----------------------------" << endl;
        obj_inf = (DWORD *)(hinst + obj_inf[1]);
        cout << obj_inf[0] << endl;
        cout << obj_inf[1] << endl;
        cout << "-----------------------------" << endl;
        ui64 *class_inf = (ui64 *)(hinst + obj_inf[1]);
        cout << class_inf[0] << endl;
        cout << class_inf[1] << endl;
        cout << class_inf[2] << endl;
        char *class_name = (char *)(class_inf + 2);
        
        cout << class_name << endl;
    }   
        
        cout << "CURRENT EXE HIN:  " << hex << uppercase << "0x" << GetModuleHandle(NULL) << endl;
        cout << "UCRTBASE HIN:     " << hex << uppercase << "0x" << LoadLibrary(L"ucrtbase.dll") << endl;
        cout << "VCRUNTIME140 HIN: " << hex << uppercase << "0x" << LoadLibrary(L"vcruntime140.dll") << endl;
        cout << "STD MSVCP140 HIN: " << hex << uppercase << "0x" << LoadLibrary(L"msvcp140.dll") << endl;
        break;
    default:
        cout << "UNKNOWN_EXCEPTION [" << hex << uppercase << c << "]" << endl;
    }
    
    cout << "CONTINUE EXECUTION:  "
        << (er->ExceptionFlags == EXCEPTION_NONCONTINUABLE ? "NOT " : "" )
        << "POSSIBLE" << endl;
    
    cout << "INSTRUCTION ADDRESS: 0x" << hex << uppercase
        << er->ExceptionAddress << endl;
        
    if(er->ExceptionRecord != NULL)
    {
        cout << "CHAINED EXCEPTION!" << endl;
        pexcept(er->ExceptionRecord->ExceptionCode,
            er->ExceptionRecord);
    }
    else
    {
        cout << "--------------------------------------------------------------------" << endl;
    }
}

Here is how your output might look (ASCII drawing formed by hand):

--------------------------------------------------------------------
CPP_EXCEPTION
PARAMS NUM: 4
PARAM0        SOME INTERNAL VALUE: 0x19930520
PARAM1      POINTER TO THROWN OBJ: 0x14FA30
PARAM2      POINTER TO OBJECT INF: 0x7FFE68B246B0 ---->
PARAM3  DLL/EXE THROWER HINSTANCE: 0x7FFE68AC0000
     +-----+
 --> |0    |
     +-----+
     |4D0C |
     +-----+
     |0    |
     +-----+     +-----+
     |646D0|---->|3    |
     +-----+     +-----+     +-----+
                 |646F0|---->|0    |
                 +-----+     +-----+     +----------------+
                             |84578|---->|0               |
                             +-----+     +----------------+
                                         |7FFE68B1C218    |
                                         +----------------+
                                         |5F74756F56413F2E|
                                         +----------------+
                                    ..... _ t u o V A ? .
                                    
.?AVout_of_range@std@@
CURRENT EXE HIN:  0x0000000140000000
UCRTBASE HIN:     0x00007FFE99D50000
VCRUNTIME140 HIN: 0x00007FFE95040000
STD MSVCP140 HIN: 0x00007FFE68AC0000
CONTINUE EXECUTION:  NOT POSSIBLE
INSTRUCTION ADDRESS: 0x00007FFE99F54FD9
--------------------------------------------------------------------

As you can see, lust number is not a pointer but actual class name of the exception (interpreted as number in Little Endian). The HINSTANCE parameter of the exception is the same as Standard C++ Library (msvcp140.dll in my case), so it's obvious where this exception originated.

Gaskin answered 31/7, 2022 at 18:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.