I'm sleepy and confused: __try, __except, /EHa, C2712 (unwinding)

Started by
10 comments, last by SmkViper 8 years, 6 months ago

I do not get it, why does code that needs unwinding inside __try block give me errors/warnings:

* warning C4509: nonstandard extension used: 'someFunctionScope' uses SEH and 'someVariable' has destructor

* error C2712: Cannot use __try in functions that require object unwinding

When /EHa (VC++ 2013) option is selected?

Isn't /EHa supposed to force the compiler to assume exceptions can occur anywhere (aka. asynchronous) and it hence must record all the destructors (ie. cannot optimize out the non-explicit ones like access violations etc).

What? I don't even ... what is my sleepy head missing? I could have sworn that i have done this before without problems.

PS. Since it is bound to come up: i really-really need to attempt (in my case, the chances are actually very good) surviving memory trashing / broken code (something that is normally undesirable), so SEH is a must as is unwinding.

Advertisement
SEH is orthogonal to C++ exceptions in principle. Under the hood, C++ exceptions on Microsoft compilers might use SEH, but they are not safe to blend in general.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

__try/__except is "Structured exception handling". It is Windows-specific, not part of the C++ standard, and (I believe) supported in C using MS's tools.

Because these keywords have nothing to do with C++ exceptions, they do not perform stack unwinding like C++ exceptions do, and so the compiler is warning you that your destructors won't be called (because the structured exception system doesn't even know they're a thing).

If you want to use exceptions, then use C++ try/except (note, no underscores).

Erm, you are confused or not expressing yourself clearly enough for me.

C++ exceptions are built on SEH. It will quite happily execute any C++ handlers along the way when unwinding. So, of course all the destructors etc will be called - as long as compiler did add them to the unwind stack to begin with. VC++ compiler is free to optimize out adding thous when it sees that there can not be any exceptions (default compile options). To do that it only considers explicit exceptions (throw) by default. That is where "/EHa" comes in - it disallows such optimizations and hence the entries will be there for even non-explicit exceptions (integer division by zero, access violation, etc).

My findings, based on and sanity-confirmed in practice:

https://msdn.microsoft.com/en-us/library/swezty51.aspx

https://msdn.microsoft.com/en-us/library/1deeycx5.aspx

My problem was caused by using "__try" in function scope that needs to unwind objects - which it cannot compile for some unknown reason i was not able to precisely pin down (might be some limitation of the function level handler that "__try" installs - ie. it can not add/remove unwind entries on the fly as functions with "try" can or more likely, the compiler is just not programmed to do so).

Which makes the solution obvious - move "__try" to a function scope that does not have objects to unwind. Unfortunately, in my case that would wreck readability - so, have to use "try catch(...)".

Examples for the benefit of passers by:

struct Test() {
    Test() { out << L"new Test"; }
    ~Test() { out << L"del Test"; }
    void crash() { out << *(int*)(0); }
}
 
void func() {
    Test foo; // Perfectly fine to unwind this object (Needs /EHa of couse as otherwise the code needed for unwind would be optimized out as "foo.crash" has no explicit "throw")
    foo.crash();
}
 
void test() {
    // Test notOkay; // Cannot compile this. Function uses __try and needs object unwinding.
    __try {
        func();
    } __except(EXCEPTION_EXECUTE_HANDLER) {
        out << L"crashed";
    }
}


C++ exceptions are built on SEH. It will quite happily execute any C++ handlers along the way when unwinding.

Really, this is your misunderstanding.

/EHa causes the compiler to emit code such that SEH exceptions are handled like C++ exception, meaning they'll be caught by catch(...). Plain and simple. An SEH will not otherwise cause destructors to get invoked during unwinding, because the SEH mechanism knows nothing aout the C++ runtime model.

SEH are not C++ exceptions, unless you use /EHa. Only C++ exceptions invoke C++ destructors. You need to use try/catch (and not __try/__except) to use C++ exceptions.

I suspect the compiler is just trying to save you from the consequences of your misunderstanding.

Stephen M. Webb
Professional Free Software Developer

C++ exceptions are built on SEH. It will quite happily execute any C++ handlers along the way when unwinding.

Really, this is your misunderstanding.

I was stating a fact (it has been this way at least a decade). C++ exceptions are implemented using SEH functionality in VC++.

/EHa causes the compiler to emit code such that SEH exceptions are handled like C++ exception, meaning they'll be caught by catch(...). Plain and simple.

Superficially true. To be specific:
* SEH exception filter for catch(...) does not select only C++ exceptions anymore (which it normally would).
* Optimizer can not rely on throws anymore and needs to emit all the unwind code it normally would omit.

An SEH will not otherwise cause destructors to get invoked during unwinding, because the SEH mechanism knows nothing aout the C++ runtime model.

That is messed up. SEH does not need to know anything about the unwind payload - C++ exceptions or otherwise.

SEH are not C++ exceptions, unless you use /EHa.

This is completely backwards.

Only C++ exceptions invoke C++ destructors.

Incorrect.

You need to use try/catch (and not __try/__except) to use C++ exceptions.

Incorrect in principle. C++ exception catching is done via SEH exception records where C++ exceptions have the exception code 0xe06d7363. You are free to handle C++ exceptions in your __except - which, granted, is quite silly.

I suspect the compiler is just trying to save you from the consequences of your misunderstanding.

Incorrect. Compiler just can not do object unwinding in a function scope that also has __try ... which overloaded my sleepy brain - i just was not aware of that limitation (have Googled around since then and it is a well known limitation, but like i said - could not pinpoint any specific reason for it).

PS. You are well advised to assume i am not totally talking out of my ass. Example code:

struct Test {
    Test() { out << L"new Test"; }
    ~Test() { out << L"del Test"; }
    void panic() {
        throw 42;
    }
};

void snafu() {
    out << L"shit's gonna hit the fan ...";
    Test obj;
    obj.panic();
}

int filter(LPEXCEPTION_POINTERS e) {
    out << L"exception code: " << std::hex << e->ExceptionRecord->ExceptionCode;
    // second parameter in c++ exceptions is a pointer to thrown object (plain int in this case)
    out << L"C++ exception payload object: " << std::dec << *(int*)e->ExceptionRecord->ExceptionInformation[1];
    // yeah, we will take anything - including the C++ exception.
    return EXCEPTION_EXECUTE_HANDLER;
}

void test() {
    __try {
        snafu();
    } __except(filter(GetExceptionInformation())) {
        out << L"panic averted";
    }
}
Output (compiled with the default /EHsc option) as one would expect:
shit's gonna hit the fan ...
new Test
exception code: e06d7363
C++ exception payload object: 42
del Test
panic averted
Cheers.
Going back to OP (since I apparently missed it the first read through):

PS. Since it is bound to come up: i really-really need to attempt (in my case, the chances are actually very good) surviving memory trashing / broken code (something that is normally undesirable), so SEH is a must as is unwinding.


You can't.

Crashing is the best case scenario in those cases - and the only thing you can safely do. If memory is trashed you aren't even guaranteed a crash or even an exception. At least with a crash the OS can clean up your malfunctioning program and prevent it from causing even more damage.

Forget about SEH. Use C++ exceptions for error handling.

No shit sherlock *massive-facepalm*. Why did you even write that? Was it not crystal clear that discussions (at least without a lot more details) in that direction is undesired waste of everyone's time?

As i wrote: "ATTEMPT surviving broken code /.../ something that is normally undesirable".

PS. Since you like to ASS-U-ME stuff about what is or is not sensible for me to do in my specific case: the code runs in kernel land and some in user land via callbacks - there is no way for the OS to survive or cleanup anything when the program is broken (the process cannot even be terminated because it is in essence part of the kernel at that point - so, you get kernel panic ... BSOD). However i can send a message and initiate a proper teardown and hope the corruption has not already wrecked the OS (the chances happen to be actually pretty good that the OS will remain unharmed [ie. all internal structures in valid state]). An approach that is highly desirable, in my case, while in development phase.

So, no, you could not be more wrong. Don't assume you know the necessary context and details of what i am doing to ignore my PS'note in OP. It is exceedingly unhelpful.

This topic is closed to new replies.

Advertisement