1*65c59e02SInna Palant /*
2*65c59e02SInna Palant * Copyright (c) Facebook, Inc. and its affiliates.
3*65c59e02SInna Palant *
4*65c59e02SInna Palant * Licensed under the Apache License, Version 2.0 (the "License");
5*65c59e02SInna Palant * you may not use this file except in compliance with the License.
6*65c59e02SInna Palant * You may obtain a copy of the License at
7*65c59e02SInna Palant *
8*65c59e02SInna Palant * http://www.apache.org/licenses/LICENSE-2.0
9*65c59e02SInna Palant *
10*65c59e02SInna Palant * Unless required by applicable law or agreed to in writing, software
11*65c59e02SInna Palant * distributed under the License is distributed on an "AS IS" BASIS,
12*65c59e02SInna Palant * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*65c59e02SInna Palant * See the License for the specific language governing permissions and
14*65c59e02SInna Palant * limitations under the License.
15*65c59e02SInna Palant */
16*65c59e02SInna Palant
17*65c59e02SInna Palant #include <atomic>
18*65c59e02SInna Palant #include <cassert>
19*65c59e02SInna Palant #include <mutex>
20*65c59e02SInna Palant #include <stdexcept>
21*65c59e02SInna Palant #include <unordered_map>
22*65c59e02SInna Palant #ifndef _WIN32
23*65c59e02SInna Palant #include <cxxabi.h>
24*65c59e02SInna Palant #endif
25*65c59e02SInna Palant
26*65c59e02SInna Palant #include <lyra/lyra_exceptions.h>
27*65c59e02SInna Palant
28*65c59e02SInna Palant namespace facebook {
29*65c59e02SInna Palant namespace lyra {
30*65c59e02SInna Palant
31*65c59e02SInna Palant using namespace detail;
32*65c59e02SInna Palant
33*65c59e02SInna Palant namespace {
34*65c59e02SInna Palant std::atomic<bool> enableBacktraces{true};
35*65c59e02SInna Palant
getExceptionTraceHolderInException(std::exception_ptr ptr)36*65c59e02SInna Palant const ExceptionTraceHolder* getExceptionTraceHolderInException(
37*65c59e02SInna Palant std::exception_ptr ptr) {
38*65c59e02SInna Palant try {
39*65c59e02SInna Palant std::rethrow_exception(ptr);
40*65c59e02SInna Palant } catch (const ExceptionTraceHolder& holder) {
41*65c59e02SInna Palant return &holder;
42*65c59e02SInna Palant } catch (...) {
43*65c59e02SInna Palant return nullptr;
44*65c59e02SInna Palant }
45*65c59e02SInna Palant }
46*65c59e02SInna Palant } // namespace
47*65c59e02SInna Palant
enableCxaThrowHookBacktraces(bool enable)48*65c59e02SInna Palant void enableCxaThrowHookBacktraces(bool enable) {
49*65c59e02SInna Palant enableBacktraces.store(enable, std::memory_order_relaxed);
50*65c59e02SInna Palant }
51*65c59e02SInna Palant
52*65c59e02SInna Palant // We want to attach stack traces to C++ exceptions. Our API contract is that
53*65c59e02SInna Palant // calling lyra::getExceptionTrace on the exception_ptr for an exception should
54*65c59e02SInna Palant // return the stack trace for that exception.
55*65c59e02SInna Palant //
56*65c59e02SInna Palant // We accomplish this by providing a hook for __cxa_init_primary_exception or
57*65c59e02SInna Palant // __cxa_throw (depending on the libc++ version), which creates an
58*65c59e02SInna Palant // ExceptionTraceHolder object (which captures the stack trace for the
59*65c59e02SInna Palant // exception), and creates a mapping from the pointer to the exception object
60*65c59e02SInna Palant // (which is a function parameter) to its ExceptionTraceHolder object. This
61*65c59e02SInna Palant // mapping can then be queried by lyra::getExceptionTrace to get the stack trace
62*65c59e02SInna Palant // for the exception. We have a custom exception destructor to destroy the trace
63*65c59e02SInna Palant // object and call the original destructor for the exception object, and our
64*65c59e02SInna Palant // hook calls the original function for the actual exception functionality and
65*65c59e02SInna Palant // passes this custom destructor.
66*65c59e02SInna Palant //
67*65c59e02SInna Palant // This works because the hooked function is only called when creating a new
68*65c59e02SInna Palant // exception object, so at that point, we're able to capture the original stack
69*65c59e02SInna Palant // trace for the exception. Even if that exception is later rethrown, we'll
70*65c59e02SInna Palant // still maintain its original stack trace, assuming that std::current_exception
71*65c59e02SInna Palant // creates a reference to the current exception instead of copying it (which is
72*65c59e02SInna Palant // true for both libstdc++ and libc++), such that we can still look up the
73*65c59e02SInna Palant // exception object via pointer.
74*65c59e02SInna Palant //
75*65c59e02SInna Palant // We don't have to worry about any pointer adjustments for the exception object
76*65c59e02SInna Palant // (e.g. for converting to or from a base class subobject pointer), because a
77*65c59e02SInna Palant // std::exception_ptr will always capture the pointer to the exception object
78*65c59e02SInna Palant // itself and not any subobjects.
79*65c59e02SInna Palant //
80*65c59e02SInna Palant // Our map must be global, since exceptions can be transferred across threads.
81*65c59e02SInna Palant // Consequently, we must use a mutex to guard all map operations.
82*65c59e02SInna Palant
83*65c59e02SInna Palant typedef void (*destructor_type)(void*);
84*65c59e02SInna Palant
85*65c59e02SInna Palant namespace {
86*65c59e02SInna Palant struct ExceptionState {
87*65c59e02SInna Palant ExceptionTraceHolder trace;
88*65c59e02SInna Palant destructor_type destructor;
89*65c59e02SInna Palant };
90*65c59e02SInna Palant
91*65c59e02SInna Palant // We create our map and mutex as function statics and leak them intentionally,
92*65c59e02SInna Palant // to ensure they've been initialized before any global constructors and are
93*65c59e02SInna Palant // also available to use inside any global destructors.
get_exception_state_map()94*65c59e02SInna Palant std::unordered_map<void*, ExceptionState>* get_exception_state_map() {
95*65c59e02SInna Palant static auto* exception_state_map =
96*65c59e02SInna Palant new std::unordered_map<void*, ExceptionState>();
97*65c59e02SInna Palant return exception_state_map;
98*65c59e02SInna Palant }
99*65c59e02SInna Palant
get_exception_state_map_mutex()100*65c59e02SInna Palant std::mutex* get_exception_state_map_mutex() {
101*65c59e02SInna Palant static auto* exception_state_map_mutex = new std::mutex();
102*65c59e02SInna Palant return exception_state_map_mutex;
103*65c59e02SInna Palant }
104*65c59e02SInna Palant
trace_destructor(void * exception_obj)105*65c59e02SInna Palant void trace_destructor(void* exception_obj) {
106*65c59e02SInna Palant destructor_type original_destructor = nullptr;
107*65c59e02SInna Palant
108*65c59e02SInna Palant {
109*65c59e02SInna Palant std::lock_guard<std::mutex> lock(*get_exception_state_map_mutex());
110*65c59e02SInna Palant auto* exception_state_map = get_exception_state_map();
111*65c59e02SInna Palant auto it = exception_state_map->find(exception_obj);
112*65c59e02SInna Palant if (it == exception_state_map->end()) {
113*65c59e02SInna Palant // This really shouldn't happen, but if it does, just leaking the trace
114*65c59e02SInna Palant // and exception object seems better than crashing.
115*65c59e02SInna Palant return;
116*65c59e02SInna Palant }
117*65c59e02SInna Palant
118*65c59e02SInna Palant original_destructor = it->second.destructor;
119*65c59e02SInna Palant exception_state_map->erase(it);
120*65c59e02SInna Palant }
121*65c59e02SInna Palant
122*65c59e02SInna Palant if (original_destructor) {
123*65c59e02SInna Palant original_destructor(exception_obj);
124*65c59e02SInna Palant }
125*65c59e02SInna Palant }
126*65c59e02SInna Palant
127*65c59e02SInna Palant // always_inline to avoid an unnecessary stack frame in the trace.
128*65c59e02SInna Palant [[gnu::always_inline]]
add_exception_trace(void * obj,destructor_type destructor)129*65c59e02SInna Palant void add_exception_trace(void* obj, destructor_type destructor) {
130*65c59e02SInna Palant if (enableBacktraces.load(std::memory_order_relaxed)) {
131*65c59e02SInna Palant std::lock_guard<std::mutex> lock(*get_exception_state_map_mutex());
132*65c59e02SInna Palant get_exception_state_map()->emplace(
133*65c59e02SInna Palant obj, ExceptionState{ExceptionTraceHolder(), destructor});
134*65c59e02SInna Palant }
135*65c59e02SInna Palant }
136*65c59e02SInna Palant } // namespace
137*65c59e02SInna Palant
138*65c59e02SInna Palant #ifndef _WIN32
139*65c59e02SInna Palant #if _LIBCPP_AVAILABILITY_HAS_INIT_PRIMARY_EXCEPTION
140*65c59e02SInna Palant __attribute__((annotate("dynamic_fn_ptr"))) static abi::__cxa_exception* (
141*65c59e02SInna Palant *original_cxa_init_primary_exception)(
142*65c59e02SInna Palant void*,
143*65c59e02SInna Palant std::type_info*,
144*65c59e02SInna Palant destructor_type) = &abi::__cxa_init_primary_exception;
145*65c59e02SInna Palant
cxa_init_primary_exception(void * obj,std::type_info * type,destructor_type destructor)146*65c59e02SInna Palant abi::__cxa_exception* cxa_init_primary_exception(
147*65c59e02SInna Palant void* obj,
148*65c59e02SInna Palant std::type_info* type,
149*65c59e02SInna Palant destructor_type destructor) {
150*65c59e02SInna Palant add_exception_trace(obj, destructor);
151*65c59e02SInna Palant return original_cxa_init_primary_exception(obj, type, trace_destructor);
152*65c59e02SInna Palant }
153*65c59e02SInna Palant
getHookInfo()154*65c59e02SInna Palant const HookInfo* getHookInfo() {
155*65c59e02SInna Palant static const HookInfo info = {
156*65c59e02SInna Palant .original =
157*65c59e02SInna Palant reinterpret_cast<void**>(&original_cxa_init_primary_exception),
158*65c59e02SInna Palant .replacement = reinterpret_cast<void*>(&cxa_init_primary_exception),
159*65c59e02SInna Palant };
160*65c59e02SInna Palant return &info;
161*65c59e02SInna Palant }
162*65c59e02SInna Palant #else
163*65c59e02SInna Palant [[gnu::noreturn]]
164*65c59e02SInna Palant __attribute__((annotate("dynamic_fn_ptr"))) static void (*original_cxa_throw)(
165*65c59e02SInna Palant void*,
166*65c59e02SInna Palant std::type_info*,
167*65c59e02SInna Palant destructor_type) = &abi::__cxa_throw;
168*65c59e02SInna Palant
169*65c59e02SInna Palant [[noreturn]] void
cxa_throw(void * obj,std::type_info * type,destructor_type destructor)170*65c59e02SInna Palant cxa_throw(void* obj, std::type_info* type, destructor_type destructor) {
171*65c59e02SInna Palant add_exception_trace(obj, destructor);
172*65c59e02SInna Palant original_cxa_throw(obj, type, trace_destructor);
173*65c59e02SInna Palant }
174*65c59e02SInna Palant
getHookInfo()175*65c59e02SInna Palant const HookInfo* getHookInfo() {
176*65c59e02SInna Palant static const HookInfo info = {
177*65c59e02SInna Palant .original = reinterpret_cast<void**>(&original_cxa_throw),
178*65c59e02SInna Palant .replacement = reinterpret_cast<void*>(&cxa_throw),
179*65c59e02SInna Palant };
180*65c59e02SInna Palant return &info;
181*65c59e02SInna Palant }
182*65c59e02SInna Palant #endif
183*65c59e02SInna Palant #endif
184*65c59e02SInna Palant
getExceptionTraceHolder(std::exception_ptr ptr)185*65c59e02SInna Palant const ExceptionTraceHolder* detail::getExceptionTraceHolder(
186*65c59e02SInna Palant std::exception_ptr ptr) {
187*65c59e02SInna Palant {
188*65c59e02SInna Palant std::lock_guard<std::mutex> lock(*get_exception_state_map_mutex());
189*65c59e02SInna Palant // The exception object pointer isn't a public member of std::exception_ptr,
190*65c59e02SInna Palant // and there isn't any public method to get it. However, for both libstdc++
191*65c59e02SInna Palant // and libc++, it's the first pointer inside the exception_ptr, and we can
192*65c59e02SInna Palant // rely on the ABI of those libraries to remain stable, so we can just
193*65c59e02SInna Palant // access it directly.
194*65c59e02SInna Palant void* exception_obj = *reinterpret_cast<void**>(&ptr);
195*65c59e02SInna Palant auto* exception_state_map = get_exception_state_map();
196*65c59e02SInna Palant auto it = exception_state_map->find(exception_obj);
197*65c59e02SInna Palant if (it != exception_state_map->end()) {
198*65c59e02SInna Palant return &it->second.trace;
199*65c59e02SInna Palant }
200*65c59e02SInna Palant }
201*65c59e02SInna Palant
202*65c59e02SInna Palant // Fall back to attempting to retrieve the ExceptionTraceHolder directly from
203*65c59e02SInna Palant // the exception (to support e.g. fbthrow).
204*65c59e02SInna Palant return getExceptionTraceHolderInException(ptr);
205*65c59e02SInna Palant }
206*65c59e02SInna Palant
207*65c59e02SInna Palant } // namespace lyra
208*65c59e02SInna Palant } // namespace facebook
209