xref: /aosp_15_r20/external/fbjni/cxx/lyra/cxa_throw.cpp (revision 65c59e023c5336bbd4a23be7af78407e3d80e7e7)
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