cxx/lyra/cxa_throw.cpp (140 lines of code) (raw):

/* * Copyright (c) Facebook, Inc. and its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <atomic> #include <mutex> #include <stdexcept> #include <unordered_map> #include <cassert> #ifndef _WIN32 #include <unwind.h> #include <cxxabi.h> #endif #include <lyra/lyra_exceptions.h> namespace facebook { namespace lyra { using namespace detail; namespace { std::atomic<bool> enableBacktraces{true}; const ExceptionTraceHolder* getExceptionTraceHolderInException( std::exception_ptr ptr) { try { std::rethrow_exception(ptr); } catch (const ExceptionTraceHolder& holder) { return &holder; } catch (...) { return nullptr; } } } // namespace void enableCxaThrowHookBacktraces(bool enable) { enableBacktraces.store(enable, std::memory_order_relaxed); } [[gnu::noreturn]] void (*original_cxa_throw)(void*, const std::type_info*, void (*) (void *)); #if defined(_LIBCPP_VERSION) // We want to attach stack traces to C++ exceptions. Our API contract is that // calling lyra::getExceptionTrace on the exception_ptr for an exception should // return the stack trace for that exception. // // We accomplish this by providing a hook for __cxa_throw, which creates an // ExceptionTraceHolder object (which captures the stack trace for the // exception), and creates a mapping from the pointer to the exception object // (which is a parameter to __cxa_throw) to its ExceptionTraceHolder object. // This mapping can then be queried by lyra::getExceptionTrace to get the stack // trace for the exception. We have a custom exception destructor to destroy the // trace object and call the original destructor for the exception object, and // our __cxa_throw hook calls the original __cxa_throw to perform the actual // exception throwing and passes this custom destructor. // // This works because __cxa_throw is only called when creating a new exception // object (that has been freshly allocated via __cxa_allocate_exception), so at // that point, we're able to capture the original stack trace for the exception. // Even if that exception is later rethrown, we'll still maintain its original // stack trace, assuming that std::current_exception creates a reference to the // current exception instead of copying it (which is true for both libstdc++ and // libc++), such that we can still look up the exception object via pointer. // // We don't have to worry about any pointer adjustments for the exception object // (e.g. for converting to or from a base class subobject pointer), because a // std::exception_ptr will always capture the pointer to the exception object // itself and not any subobjects. // // Our map must be global, since exceptions can be transferred across threads. // Consequently, we must use a mutex to guard all map operations. typedef void (*destructor_type)(void*); namespace { struct ExceptionState { ExceptionTraceHolder trace; destructor_type destructor; }; // We create our map and mutex as function statics and leak them intentionally, // to ensure they've been initialized before any global constructors and are // also available to use inside any global destructors. std::unordered_map<void*, ExceptionState>* get_exception_state_map() { static auto* exception_state_map = new std::unordered_map<void*, ExceptionState>(); return exception_state_map; } std::mutex* get_exception_state_map_mutex() { static auto* exception_state_map_mutex = new std::mutex(); return exception_state_map_mutex; } void trace_destructor(void* exception_obj) { destructor_type original_destructor = nullptr; { std::lock_guard<std::mutex> lock(*get_exception_state_map_mutex()); auto* exception_state_map = get_exception_state_map(); auto it = exception_state_map->find(exception_obj); if (it == exception_state_map->end()) { // This really shouldn't happen, but if it does, just leaking the trace // and exception object seems better than crashing. return; } original_destructor = it->second.destructor; exception_state_map->erase(it); } if (original_destructor) { original_destructor(exception_obj); } } } // namespace [[noreturn]] void cxa_throw(void* obj, const std::type_info* type, destructor_type destructor) { if (enableBacktraces.load(std::memory_order_relaxed)) { std::lock_guard<std::mutex> lock(*get_exception_state_map_mutex()); get_exception_state_map()->emplace( obj, ExceptionState{ExceptionTraceHolder(), destructor}); } original_cxa_throw(obj, type, trace_destructor); } const ExceptionTraceHolder* detail::getExceptionTraceHolder( std::exception_ptr ptr) { { std::lock_guard<std::mutex> lock(*get_exception_state_map_mutex()); // The exception object pointer isn't a public member of std::exception_ptr, // and there isn't any public method to get it. However, for both libstdc++ // and libc++, it's the first pointer inside the exception_ptr, and we can // rely on the ABI of those libraries to remain stable, so we can just // access it directly. void* exception_obj = *reinterpret_cast<void**>(&ptr); auto* exception_state_map = get_exception_state_map(); auto it = exception_state_map->find(exception_obj); if (it != exception_state_map->end()) { return &it->second.trace; } } // Fall back to attempting to retrieve the ExceptionTraceHolder directly from // the exception (to support e.g. fbthrow). return getExceptionTraceHolderInException(ptr); } #elif !defined(_WIN32) namespace { const auto traceHolderType = static_cast<const abi::__class_type_info*>(&typeid(ExceptionTraceHolder)); // lyra's __cxa_throw attaches stack trace information to thrown exceptions. It basically does: // 1. capture stack trace // 2. construct a new type_info struct that: // a. holds the ExceptionTraceHolder // b. supports upcasting to lyra::ExceptionTraceHolder* (by just returning the holder member) // c. acts like the original exception type_info otherwise // 3. call original __cxa_throw() with original exception pointer, the // HijackedExceptionTypeInfo, and HijackedExceptionTypeInfo::destructor // (which will both delete the constructed type info and call the original // destructor). struct HijackedExceptionTypeInfo : public abi::__class_type_info { HijackedExceptionTypeInfo(void* obj, const std::type_info* base, void(*destructor)(void*)) : abi::__class_type_info{base->name()}, base_{base}, orig_dest_{destructor} { } bool __is_pointer_p() const override { return base_->__is_pointer_p(); } bool __is_function_p() const override { return base_->__is_function_p(); } bool __do_catch(const type_info *__thr_type, void **__thr_obj, unsigned __outer) const override { return base_->__do_catch(__thr_type, __thr_obj, __outer); } bool __do_upcast(const abi::__class_type_info *__target, void **__obj_ptr) const override { if (__target == traceHolderType) { *__obj_ptr = (void*)&stack_; return true; } return base_->__do_upcast(__target, __obj_ptr); } static void destructor(void* obj) { auto exc_ptr = reinterpret_cast<std::exception_ptr*>(&obj); auto info = reinterpret_cast<const::std::type_info*>(exc_ptr->__cxa_exception_type()); auto mutable_info = static_cast<HijackedExceptionTypeInfo*>(const_cast<std::type_info*>(info)); if (mutable_info->orig_dest_) { mutable_info->orig_dest_(obj); } delete mutable_info; } private: const std::type_info* base_; void (*orig_dest_)(void*); ExceptionTraceHolder stack_; }; } // namespace [[noreturn]] void cxa_throw(void* obj, const std::type_info* type, void (*destructor) (void *)) { if (enableBacktraces.load(std::memory_order_relaxed)) { if (!type->__do_upcast(traceHolderType, &obj)) { type = new HijackedExceptionTypeInfo(obj, type, destructor); destructor = HijackedExceptionTypeInfo::destructor; } } original_cxa_throw(obj, type, destructor); } const ExceptionTraceHolder* detail::getExceptionTraceHolder( std::exception_ptr ptr) { return getExceptionTraceHolderInException(ptr); } #endif // libc++/Windows } // namespace lyra } // namespace facebook