Sign in to follow this  
tanzanite7

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

Recommended Posts

tanzanite7    1410

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.

Share this post


Link to post
Share on other sites
SmkViper    5396
__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).

Share this post


Link to post
Share on other sites
tanzanite7    1410

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. [b]So, of course all the destructors etc will be called[/b] - 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";
    }
}

Share this post


Link to post
Share on other sites
Bregma    9214


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.

Share this post


Link to post
Share on other sites
tanzanite7    1410

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. Edited by tanzanite7

Share this post


Link to post
Share on other sites
SmkViper    5396
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.

Share this post


Link to post
Share on other sites
tanzanite7    1410

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: "[b]ATTEMPT[/b] surviving broken code /.../ [b]something that is normally undesirable[/b]".

 

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 [b]chances[/b] 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.

Share this post


Link to post
Share on other sites
jbadams    25712

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?

[...]

PS. Since you like to ASS-U-ME stuff

[...]

 

Watch your attitude, you're close to getting your topic closed.

 

 

Everyone else, please stay on topic; I've hidden two off-topic posts from this thread and don't want to see any more.

Share this post


Link to post
Share on other sites
Hodgman    51324
As for attempted recovery from crashes... SEH is great for capturing a crash and performing a memory dump/etc before your app dies. But due to the lack of propper C++ unwinding, it's not great for recovery.

You can put a try/__try around your game loop function, catch crashes, and just move on to the next frame... But
* try will unwind C++ exceptions nicely as long as your code adheres to the strong exception safety guarantee, but won't catch actual crashes.
* __try will catch crashes, but won't unwind your C++ code properly, probably causing further damage to your game-state than the initial crash caused :(

To get some actual robust crash tolerance, you'll need to write C-style code in a functional style -- minimise mutable state, break processing into chains/graphs of input->process->output nodes, where each node is isolated and reproducable and deterministic.
Many functional languages like Erlang are error tolerant by design -- you can steal their techniques and use them in C/etc.

Share this post


Link to post
Share on other sites
SmkViper    5396

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).


To be honest I'd rather BSOD and look at a dump then have some kernel-mode code try to patch up unknown memory corruption that has already happened.

Most people here are game developers. Games never need to have kernel-mode code. Perhaps you'd have better luck in a more focused forum on kernel-mode, driver, or OS development?

At the very least, you should probably be writing your code in such a way that crashes won't even compile - either by better use of the C++ type system (i.e. references or a not_null wrapper for when you don't expect null, using containers with range-checks instead of raw arrays, etc), or by changing your coding paradigm as Hodgman suggested.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this