// Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // (C) Copyright 2008 Sebastian Redl // This beast has gotten so complicated that it needs a source file. #include #include #include #include #include #include #include #include //#include #include #include #include "exception_ptr_gcc.hpp" namespace { typedef std::size_t uintptr_t; void before_pseudo_cleanup() { } // This was supposed to do nothing, but __cxa_rethrow doesn't write the // 0 for the handlerCount into the header after deleting it - for obvious // reasons, but much to my detriment. So this will have to do it instead. void pseudo_cleanup(_Unwind_Reason_Code, _Unwind_Exception *p) { (reinterpret_cast(p + 1) - 1) ->handlerCount = 0; } void after_pseudo_cleanup() { } // A Boost.Pool UserAllocator that allocates rwx memory pages. struct xpage_allocator { typedef std::size_t size_type; typedef std::ptrdiff_t difference_type; static char *malloc(const size_type bytes) { std::cerr << __PRETTY_FUNCTION__ << std::endl; void *m = mmap(0, bytes, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if(m == MAP_FAILED) { return 0; } return static_cast(m); } static void free(char *block) { std::cerr << __PRETTY_FUNCTION__ << std::endl; // Passing 1 means only the page block is in is unmapped. munmap(block, 1); } }; typedef boost::pool xpage_pool; boost::scoped_ptr g_pool; boost::mutex g_pool_guard; //typedef boost::lock mutex_lock; typedef boost::mutex::scoped_lock mutex_lock; struct eh_globals { boost::detail::eh_info *headException; unsigned int uncaughtExceptions; // Note: more fields here, but we don't care. }; extern "C" { eh_globals *__cxa_get_globals() throw(); void __cxa_rethrow(); } inline eh_globals *get_eh_globals() throw() { return __cxa_get_globals(); } inline void rethrow() { __cxa_rethrow(); } struct my_eh_info { my_eh_info() : oldfn(0), refcount(0) {} _Unwind_Exception_Cleanup_Fn oldfn; boost::detail::atomic_count refcount; unsigned int uncaughts_at_construction; }; const uintptr_t pcleanup = reinterpret_cast(&pseudo_cleanup); const uintptr_t pafter = reinterpret_cast(&after_pseudo_cleanup); const uintptr_t pbefore = reinterpret_cast(&before_pseudo_cleanup); const uintptr_t cleanupsize = pafter > pcleanup ? pafter - pcleanup : // Sometimes GCC reverses the code order of the three. pbefore - pcleanup; // TODO: Compute alignment of my_eh_info and set info_offset correctly. const uintptr_t info_offset = 64; const uintptr_t thunk_size = info_offset + sizeof(my_eh_info); my_eh_info &myinfo(boost::detail::eh_info *p) { _Unwind_Exception_Cleanup_Fn fn = p->unwindHeader.exception_cleanup; uintptr_t fni = reinterpret_cast(fn); return *reinterpret_cast(fni + info_offset); } void create_exception_thunk(boost::detail::eh_info *p) { std::cerr << __PRETTY_FUNCTION__ << std::endl; std::cerr << "Thunk fn size: " << cleanupsize << std::endl; assert(cleanupsize <= info_offset); void *m; { // Lock the memory pool. mutex_lock lock(g_pool_guard); if(!g_pool) { // No memory pool yet, create it. try { g_pool.reset(new xpage_pool(thunk_size)); } catch(...) { // Totally screwed. std::terminate(); } } // Get memory for the thunk. m = g_pool->malloc(); if(!m) { // Totally screwed. std::terminate(); } } // Unlock the pool. // Copy the do-nothing code to the thunk. std::memcpy(m, reinterpret_cast(pcleanup), cleanupsize); // Initialize the my_eh_info part. void *infoloc = static_cast(m) + info_offset; my_eh_info *pinfo = new (infoloc) my_eh_info; pinfo->uncaughts_at_construction = get_eh_globals()->uncaughtExceptions; // Insert the thunk into the eh_info. uintptr_t m_as_i = reinterpret_cast(m); _Unwind_Exception_Cleanup_Fn m_as_fn = reinterpret_cast<_Unwind_Exception_Cleanup_Fn>(m_as_i); pinfo->oldfn = p->unwindHeader.exception_cleanup; p->unwindHeader.exception_cleanup = m_as_fn; } void cleanup_exception_thunk(boost::detail::eh_info *p) { std::cerr << __PRETTY_FUNCTION__ << std::endl; // 1. Restore old cleanup function. // 2. Perhaps delete exception object. // 3. Delete cleanup thunk. _Unwind_Exception_Cleanup_Fn thunk = p->unwindHeader.exception_cleanup; my_eh_info &info = myinfo(p); p->unwindHeader.exception_cleanup = info.oldfn; eh_globals *globals = get_eh_globals(); // Ref count has dropped to 0, which means there is no exception_ptr // referring to this exception. // However, if this exception is currently being handled or thrown, // it still cannot be deleted. // Being handled is easy - handlerCount != 0. // Being thrown is impossible. I can tell if *some* exception is being // thrown, but I can't tell which it is. As such, the only "safe" thing // is to leak the exception object in the case where it's a different // exception being thrown. There is nothing I can do about that. if(p->handlerCount == 0 && globals->uncaughtExceptions <= info.uncaughts_at_construction) { std::cerr << "Truly deleting exception.\n"; _Unwind_DeleteException(&p->unwindHeader); } else { std::cerr << "Not deleting exception ("; if(globals->uncaughtExceptions > info.uncaughts_at_construction) { std::cerr << "uncaught"; } else { std::cerr << p->handlerCount << " handlers"; } std::cerr << ").\n"; } mutex_lock lock(g_pool_guard); uintptr_t thunk_as_i = reinterpret_cast(thunk); g_pool->free(reinterpret_cast(thunk_as_i)); } } namespace boost { namespace detail { void intrusive_ptr_add_ref(eh_info *p) { ++myinfo(p).refcount; } void intrusive_ptr_release(eh_info *p) { if(--myinfo(p).refcount == 0) { cleanup_exception_thunk(p); } } } exception_ptr current_exception() { std::cerr << __PRETTY_FUNCTION__ << std::endl; eh_globals *globals = get_eh_globals(); detail::eh_info *head = globals->headException; if(!head || head->handlerCount < 0) { return exception_ptr(); } // This absurd trick is necessary to get at the non-exported cleanup // function. _Unwind_Exception_Cleanup_Fn real_cleanup = 0; try { throw 0; } catch(int) { real_cleanup = globals->headException->unwindHeader.exception_cleanup; } if(head->unwindHeader.exception_cleanup == real_cleanup) { create_exception_thunk(head); } return exception_ptr(head); } void rethrow_exception(exception_ptr p) { if(p) { eh_globals *globals = get_eh_globals(); detail::eh_info *e = p.get(); if(e->handlerCount != 0 && globals->headException == e) { // This still counts as a rethrow. rethrow(); } else { // Either we left the last handler, or the current caught // exception is not ours. Do the work manually, i.e. signal // the uncaught exception and continue the stack unwinding. // This seems to work, but I wonder if it doesn't leave the // exception stack weird. // FIXME: Does this work across threads? ++globals->uncaughtExceptions; _Unwind_RaiseException(&e->unwindHeader); } } else { throw "no exception stored"; } } }