Home > Blockchain >  How can I have stack-unwinding while using Structrured Exception Handling
How can I have stack-unwinding while using Structrured Exception Handling

Time:06-14

How can I have stack-unwinding while using Structrured Exception Handling ?

I'm gonna ask my question after that because I've found a way to do that at least at a syntactical level and I thought this might be useful for others.

CodePudding user response:

I found a way to have stack unwinding while using Structured Exception Handling.
The following code catches an I/O error while using a memory-mapped file:

#include <Windows.h>
#include <iostream>
#include <atomic>

using namespace std;

using XHANDLE = unique_ptr<void, decltype([]( void *h ) { h && h != INVALID_HANDLE_VALUE && CloseHandle( (HANDLE)h ); })>;
using XMAP_VIEW = unique_ptr<void, decltype([]( void *p ) { p && UnmapViewOfFile( p ); })>;

template<typename Fn, typename Filter, typename Handler>
    requires requires( Fn fn, Filter filter, EXCEPTION_POINTERS *pEp, Handler handler ) { { fn() }; { filter( pEp ) } -> same_as<LONG>; { handler() }; }
void seh_encapsulate( Fn fn, Filter filter, Handler handler );

int wmain( int argc, wchar_t **argv )
{
    if( argc < 2 )
        return EXIT_FAILURE;
    XHANDLE xhFile( CreateFileW( argv[1], GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ) );
    if( xhFile.get() == INVALID_HANDLE_VALUE )
        return EXIT_FAILURE;
    LARGE_INTEGER liFileSize;
    if( !GetFileSizeEx( xhFile.get(), &liFileSize ) || liFileSize.QuadPart > (size_t)-1 )
        return EXIT_FAILURE;
    XHANDLE xhMappging( CreateFileMapping( xhFile.get(), nullptr, PAGE_READONLY, 0, 0, nullptr ) );
    if( !xhMappging.get() )
        return EXIT_FAILURE;
    XMAP_VIEW mapView( MapViewOfFile( xhMappging.get(), FILE_MAP_READ, 0, 0, 0 ) );
    if( !mapView.get() )
        return EXIT_FAILURE;
    atomic_char
        *pa = (atomic_char *)mapView.get(),
        *paEnd = pa   (size_t)liFileSize.QuadPart;
    seh_encapsulate(
            [&]()
            {
                for( ; ; )
                    for( atomic_char *paScn = pa; paScn != paEnd;   paScn )
                        (void)paScn->load( memory_order_relaxed );
            },
            [&]( EXCEPTION_POINTERS *pEp ) -> LONG
            {
                if( pEp->ExceptionRecord->ExceptionCode != EXCEPTION_IN_PAGE_ERROR )
                    return EXCEPTION_CONTINUE_SEARCH;
                if( pEp->ExceptionRecord->NumberParameters < 2 )
                    return EXCEPTION_CONTINUE_SEARCH;
                void *where = (void *)pEp->ExceptionRecord->ExceptionInformation[1];
                if( where < pa || where >= paEnd )
                    return EXCEPTION_CONTINUE_SEARCH;
                return EXCEPTION_EXECUTE_HANDLER;
            },
            []()
            {
                cout << "I/O error" << endl;
            } );
}

template<typename Fn, typename Filter, typename Handler>
    requires requires( Fn fn, Filter filter, EXCEPTION_POINTERS *pEp, Handler handler ) { { fn() }; { filter( pEp ) } -> same_as<LONG>; { handler() }; }
void seh_encapsulate( Fn fn, Filter filter, Handler handler )
{
    __try
    {
        fn();
    }
    __except( filter( GetExceptionInformation() ) )
    {
        handler();
    }
}

The trick with that is to have the code with the SEH-exception to be called, the filter- and the handler-function to be function objects of seh_encapsulate. seh_encapsulate normally won't be inlined since for the compiler it needs to be code with a separate function body since it uses Structured Exception Handling. The code of the "Function" object mustn't use any stack unwinding since the thrown exception from inside that might bypass any object destruction. But the "Filter" object as well as the "Handler" object can have stack unwinding. And the surrounding code of seh_encapsulate can have stack unwinding as well.
So at least at a syntatical level it looks like you have Structured Exception Handling in a function that has object unwinding.

CodePudding user response:

IInspectable pointed out that operator<< could throw an exception. I argued that this won't happen and if this would happen for other reasons the lambda would be compiled into a separate function being called from my seh_encapsulate.
I just checked that with the following example code:

#include <Windows.h>
#include <iostream>
#include <string>

using namespace std;

int main()
{
    __try
    {
        char const *outer = "world";
        [&]()
        {
            string inner( "hello" );
            inner  = " ";
            inner  = outer;
            cout << inner << endl;
        }();
    }
    __except( EXCEPTION_EXECUTE_HANDLER )
    {
    }
}

If you run the code as a release-compile in the debugger you can see that the lambda gets called separately:

lea         rax,[outer]  
mov         qword ptr [rsp 20h],rax  
lea         rcx,[rsp 20h]  
call        `main'::`3'::<lambda_1>::operator()

The above code inside the lambda might throw bad_alloc and has object unwinding as well.
So this discussion lead to a sometimes simpler solution like that in my first answer and clarified some non-relevant issues.

  • Related