[context] Crash in case of exception from a context

I use boost::context to implement cooperative multitasking. At the moment I use boost 1.57 on Windows 7 with VisualStudio 2013. Everything works fine until I throw an exception from a context created by "boost::context::make_fcontext(.......)". I have seen someone had the same problem and it was caused by the Windows OS, that checks the exception hierarchy in the stack and if it finds that the starting point is not the one that should come from the OS then it terminates the process. It suspects some malitious code that corrupted the stack. http://www.blinnov.com/en/2013/04/11/english-boostcontext-and-seh/ This guy metioned in his blogpost, that this problem was solved in boost 1.53 ("Boost 1.53 has updated and fixed version of boost::context library that addresses exactly this problem. "), but somehow I still have the same thing in 1.57. Is there some special preprocessor flag (#define) that I have to activate to get this fix? Thanks! Tamas

On Tue, Mar 10, 2015 at 10:32 AM, <Tamas.Ruszkai@leica-geosystems.com> wrote:
I use boost::context to implement cooperative multitasking. At the moment I use boost 1.57 on Windows 7 with VisualStudio 2013.
Everything works fine until I throw an exception from a context created by "boost::context::make_fcontext(.......)".
This guy metioned in his blogpost, that this problem was solved in boost 1.53 ("Boost 1.53 has updated and fixed version of boost::context library that addresses exactly this problem. "), but somehow I still have the same thing in 1.57. Is there some special preprocessor flag (#define) that I have to activate to get this fix?
That contradicts what the Boost 1.57 documentation itself says [0]: "Exceptions in context-function If the context-function emits an exception, the behaviour is undefined. (!) Important context-function should wrap the code in a try/catch block." [0] http://www.boost.org/doc/libs/1_57_0/libs/context/doc/html/context/context.h...

I use boost::context to implement cooperative multitasking. At the moment I use boost 1.57 on Windows 7 with VisualStudio 2013.
Everything works fine until I throw an exception from a context created by "boost::context::make_fcontext(.......)".
This guy metioned in his blogpost, that this problem was solved in boost 1.53 ("Boost 1.53 has updated and fixed version of boost::context
Actually the exception never leaves the stack frame of the context, because I have put a try catch block inside the context-function as it was recommended. - If I got the docs right Here is a small demo. This crashes from V2013 standard C++ unit test. When I step on the exception throwing line (the exception type does not matter) then I get the "First-chance exception at 0x71E026A2 (clr.dll) in vstest.executionengine.x86.exe: 0x80000001: Not implemented (parameters: 0x00000001, 0x0644AC84)." exception and when I pass it to the system then a lot of "0xC0000005: Access violation" exceptions. I have also played around with the stack size, but it does not seem to have any effect. void fException() { std::cout << "I am still ok" << std::endl; throw int(42); // The crash occurs here } void f1(intptr_t) { try { std::cout << "Greetings from context f1" << std::endl; fException(); std::cout << "Never reach this" << std::endl; } catch (...) { std::cout << "Exception caught" << std::endl; } } void test() { std::size_t size(512*1024); char* stack1((char*)std::malloc(size)); void* stackPointer1(stack1 + size); boost::context::fcontext_t fcm, fc1; fc1 = boost::context::make_fcontext(stackPointer1, size, f1); std::cout << "Greetings from main thread stack" << std::endl; boost::context::jump_fcontext(&fcm, fc1, 0); } From: Nat Goodspeed <nat@lindenlab.com> To: "boost-users@lists.boost.org" <boost-users@lists.boost.org>, Date: 10.03.2015 17:40 Subject: Re: [Boost-users] [context] Crash in case of exception from a context Sent by: "Boost-users" <boost-users-bounces@lists.boost.org> On Tue, Mar 10, 2015 at 10:32 AM, <Tamas.Ruszkai@leica-geosystems.com> wrote: library
that addresses exactly this problem. "), but somehow I still have the same thing in 1.57. Is there some special preprocessor flag (#define) that I have to activate to get this fix?
That contradicts what the Boost 1.57 documentation itself says [0]: "Exceptions in context-function If the context-function emits an exception, the behaviour is undefined. (!) Important context-function should wrap the code in a try/catch block." [0] http://www.boost.org/doc/libs/1_57_0/libs/context/doc/html/context/context.h... _______________________________________________ Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users

The problem here is most likely due to Visual C++ using SEH to implement C++ exceptions. SEH uses a linked list of function pointers on the stack to process exceptions. SEH requires this linked list be on the stack, and will kill the process if it encounters one that is not. SEH is also used for normally fatal program errors (divide by zero, null pointer dereference, invalid instruction, etc) no matter what compiler is used, so even MinGW or any other C++ compiler will need this change for everything to work as it should. The solution is actually pretty simple - push the current SEH list's head onto the stack before the context switch, and restore it when resuming execution, with each context initializing its own list with UnhandledExceptionFilter during either construction or first run (like a new thread would). But maybe such a fix is out of scope for the context library?l

2015-03-14 6:37 GMT+01:00 Nathaniel Fries <nfries88@gmail.com>:
The solution is actually pretty simple - push the current SEH list's head onto the stack before the context switch, and restore it when resuming execution, with each context initializing its own list with UnhandledExceptionFilter during either construction or first run (like a new thread would). But maybe such a fix is out of scope for the context library?l
boost.context already installs SEH structures on the stack - the unit-tests of boost.context already check throwing/catching exceptions -> http://www.boost.org/development/tests/master/developer/context.html http://www.boost.org/development/tests/develop/developer/context.html

I have copied the essence from the unit test and put it into my Visual Studio (VS2013 Version 12.31101.00 Update4) environment, but still crashes. The project is a natvie C++ project (not mixed mode .NET) and in "Configuration Properties/C/C++/Code Generation/Enable C++ Exceptions" is set to "Yes(/EHsc)". However there is another option : "Yes with SEH Exceptions (/EHa)", but does not seem to fix the issue. Meanwhile I have implemented a workaround (see below): for each context I create a thread. As soon as this new thread is started I switch it to another context (created by boost::context::make_fcontext) and block it there on a condition variable. Then I use the original stack of the thread. This "hijacked" stack can cope with exceptions, just as I would expect. When I want to release the context then I just simply release the blocked thread and switch it back to its original context. This way I can make sure that the stack of the thread is also correctly unwound. #include "stdafx.h" #include "boost/context/all.hpp" #include <boost/assert.hpp> #include "CppUnitTest.h" #include <iostream> using namespace Microsoft::VisualStudio::CppUnitTestFramework; template< std::size_t Max, std::size_t Default, std::size_t Min > class simple_stack_allocator { public: static std::size_t maximum_stacksize() { return Max; } static std::size_t default_stacksize() { return Default; } static std::size_t minimum_stacksize() { return Min; } void * allocate(std::size_t size) const { BOOST_ASSERT(minimum_stacksize() <= size); BOOST_ASSERT(maximum_stacksize() >= size); void * limit = std::malloc(size); if (!limit) throw std::bad_alloc(); return static_cast< char * >(limit)+size; } void deallocate(void * vp, std::size_t size) const { BOOST_ASSERT(vp); BOOST_ASSERT(minimum_stacksize() <= size); BOOST_ASSERT(maximum_stacksize() >= size); void * limit = static_cast< char * >(vp)-size; std::free(limit); } }; void fException() { std::cout << "I am still ok" << std::endl; // Executes well throw std::runtime_error("crash!"); // Execution crashes at this point } boost::context::fcontext_t fcm, fc1; void f1(intptr_t) { try { std::cout << "Greetings from context f1" << std::endl; // Executes well fException(); std::cout << "Never reach this" << std::endl; // Break point here does not hit } catch (std::runtime_error const& e) { std::cout << "runtime_error exception caught" << std::endl; // Break point here does not hit } catch (...) { std::cout << "General exception caught" << std::endl; // Break point here does not hit } boost::context::jump_fcontext(&fc1, fcm, 0); // Break point here does not hit } namespace ctx = boost::context; typedef simple_stack_allocator< 8 * 1024 * 1024, // 8MB 64 * 1024, // 64kB 8 * 1024 // 8kB
stack_allocator;
void test() { stack_allocator alloc; void * stackPointer = alloc.allocate(stack_allocator ::default_stacksize()); fc1 = boost::context::make_fcontext(stackPointer, stack_allocator ::minimum_stacksize(), f1); std::cout << "Greetings from main thread stack" << std::endl; boost::context::jump_fcontext(&fcm, fc1, 0); } TEST_CLASS(BoostContextTest) { public: TEST_METHOD(ExceptionContextTest) { test(); } }; // ====================================================================================================================================================================================== // This class starts a thread and switches that thread to a context where it is parking. // Meanwhile we can use the original stack of the thread. This way the stack of the thread temporarily hijacked // The reason why the stack of a real thread is needed for the context is the exception handling. // The context that boost::context makes does not work with exceptions class HijackedThread { public: HijackedThread(std::size_t stackSize, boost::context::fcontext_t& contextHandle) : _threadParkingContextStack(new ContextStack(64 * 1024)) , _threadContextHandle(contextHandle) , _threadParkingContextHandle(nullptr) , _threadInParkingContext(false) , _releaseParkingThread(false) { CreateAndParkThread(stackSize); WaitForThreadToPark(); } ~HijackedThread() { ReleaseParkingThread(); } bool IsMethodSet() { auto isMethodSet = (bool)_method; return isMethodSet; } void SetMethod(std::function<void()> method) { _method = method; } private: void CreateAndParkThread(std::size_t stackSize) { _threadParkingContextHandle = boost::context::make_fcontext(_threadParkingContextStack->Bottom(), _threadParkingContextStack->Size(), ParkingContextStartup); boost::thread::attributes attrs; attrs.set_stack_size(stackSize); _thread = new boost::thread(attrs, [=] { boost::context::jump_fcontext(&_threadContextHandle, _threadParkingContextHandle, reinterpret_cast<intptr_t>(this)); if (_threadInParkingContext) { _method(); } }); } static void ParkingContextStartup(intptr_t instance) { HijackedThread* hijackedThread = reinterpret_cast< HijackedThread*>(instance); hijackedThread->ParkThread(); // Wind up the context stack from the thread boost::context::jump_fcontext(&(hijackedThread->_threadParkingContextHandle), hijackedThread->_threadContextHandle, 0); } void ParkThread() { boost::mutex::scoped_lock lock(_mutex); _threadInParkingContext = true; _monitor.notify_all(); while (!_releaseParkingThread) { _monitor.wait(lock); } _threadInParkingContext = false; } void WaitForThreadToPark() { boost::mutex::scoped_lock lock(_mutex); while (!_threadInParkingContext) { _monitor.wait(lock); } } void UnParkThread() { boost::mutex::scoped_lock lock(_mutex); _releaseParkingThread = true; _monitor.notify_all(); } void ReleaseParkingThread() { UnParkThread(); _thread->join(); } private: boost::mutex _mutex; boost::condition_variable _monitor; boost::thread* _thread; boost::context::fcontext_t& _threadContextHandle; boost::context::fcontext_t _threadParkingContextHandle; std::unique_ptr<ContextStack> _threadParkingContextStack; std::function<void()> _method; bool _threadInParkingContext; bool _releaseParkingThread; }; // FIXME: Only with shared_ptr? Why not with move constructor? class Context : public std::enable_shared_from_this<Context> { public: ~Context() { UnwindStack(); } static std::shared_ptr<Context> CreateNew(std::size_t stackSize) { auto newContext = std::shared_ptr<Context>(new Context( stackSize)); return newContext; } static std::shared_ptr<Context> CreateFromCurrentStack() { auto newContext = std::shared_ptr<Context>(new Context()); return newContext; } void SetName(const std::string& name) { _name = name; } void SetMethod(std::function<void(Context*)> method) { if (!_hijackedThread) { throw ContextChainException("Cannot set a Startup method for a thread"); } if (_hijackedThread->IsMethodSet()) { throw ContextChainException("Startup method can be set only once"); } _hijackedThread->SetMethod([=] { method(this); _methodFinished = true; // When the method is done, then yield back Return(); }); } void SwitchSafe(const std::shared_ptr<Context>& newContext) { if (_unwinding) return; if (_hijackedThread && !(_hijackedThread->IsMethodSet())) { throw ContextChainException("No startup method defined"); } // The destination context shall be not involved in any switching chain // otherwise the way back to the thread stack will be messed up if (newContext->_returnContext) { throw ContextChainException("Recursion in context is not allowed"); } // In the newContext the method must be not finished, otherwise it makes no // sense to switch to it if (newContext->_methodFinished) { throw ContextFinishedException("Context method has already finished"); } Switch(newContext); } void Switch(const std::shared_ptr<Context>& newContext) { if (_unwinding) return; newContext->_returnContext = shared_from_this(); boost::context::jump_fcontext(&_contextHandle, newContext ->_contextHandle, 0); } void ReturnSafe() { if (_unwinding) return; // There must be a place to return if (!_returnContext) { throw ContextChainException("No place to return"); } Return(); } void Return() { if (_unwinding) return; auto returnContextHandle = _returnContext->_contextHandle; _returnContext.reset(); boost::context::jump_fcontext(&_contextHandle, returnContextHandle, 0); } protected: Context() : _hijackedThread(nullptr) , _contextHandle(nullptr) , _returnContext(nullptr) , _methodFinished(false) , _unwinding(false) { } Context(std::size_t stackSize) : _hijackedThread(nullptr) , _contextHandle(nullptr) , _returnContext(nullptr) , _methodFinished(false) , _unwinding(false) { _hijackedThread.reset(new HijackedThread(stackSize, _contextHandle)); } void UnwindStack() { _unwinding = true; // Let the hijacked thread unwind the context _hijackedThread.reset(); } protected: std::unique_ptr<HijackedThread> _hijackedThread; boost::context::fcontext_t _contextHandle; std::shared_ptr<Context> _returnContext; bool _methodFinished; bool _unwinding; std::string _name; }; From: Oliver Kowalke <oliver.kowalke@gmail.com> To: boost-users <boost-users@lists.boost.org>, Date: 14.03.2015 10:12 Subject: Re: [Boost-users] [context] Crash in case of exception from a context Sent by: "Boost-users" <boost-users-bounces@lists.boost.org> 2015-03-14 6:37 GMT+01:00 Nathaniel Fries <nfries88@gmail.com>: The solution is actually pretty simple - push the current SEH list's head onto the stack before the context switch, and restore it when resuming execution, with each context initializing its own list with UnhandledExceptionFilter during either construction or first run (like a new thread would). But maybe such a fix is out of scope for the context library?l boost.context already installs SEH structures on the stack - the unit-tests of boost.context already check throwing/catching exceptions -> http://www.boost.org/development/tests/master/developer/context.html http://www.boost.org/development/tests/develop/developer/context.html _______________________________________________ Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users
participants (4)
-
Nat Goodspeed
-
Nathaniel Fries
-
Oliver Kowalke
-
Tamas.Ruszkai@leica-geosystems.com