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