15
15
*/
16
16
17
17
#include < atomic>
18
+ #include < mutex>
18
19
#include < stdexcept>
20
+ #include < unordered_map>
19
21
#include < cxxabi.h>
20
22
#include < unwind.h>
21
23
#include < cassert>
25
27
namespace facebook {
26
28
namespace lyra {
27
29
30
+ using namespace detail ;
31
+
28
32
namespace {
29
33
std::atomic<bool > enableBacktraces{true };
34
+
35
+ const ExceptionTraceHolder* getExceptionTraceHolderInException (
36
+ std::exception_ptr ptr) {
37
+ try {
38
+ std::rethrow_exception (ptr);
39
+ } catch (const ExceptionTraceHolder& holder) {
40
+ return &holder;
41
+ } catch (...) {
42
+ return nullptr ;
43
+ }
30
44
}
45
+ } // namespace
31
46
32
47
void enableCxaThrowHookBacktraces (bool enable) {
33
48
enableBacktraces.store (enable, std::memory_order_relaxed);
@@ -36,13 +51,115 @@ void enableCxaThrowHookBacktraces(bool enable) {
36
51
[[gnu::noreturn]] void (*original_cxa_throw)(void *, const std::type_info*, void (*) (void *));
37
52
38
53
#if defined(_LIBCPP_VERSION)
39
- [[noreturn]] void cxa_throw (void * obj, const std::type_info* type, void (*destructor) (void *)) {
40
- // lyra doesn't have support yet for libc++.
41
- original_cxa_throw (obj, type, destructor);
54
+
55
+ // We want to attach stack traces to C++ exceptions. Our API contract is that
56
+ // calling lyra::getExceptionTrace on the exception_ptr for an exception should
57
+ // return the stack trace for that exception.
58
+ //
59
+ // We accomplish this by providing a hook for __cxa_throw, which creates an
60
+ // ExceptionTraceHolder object (which captures the stack trace for the
61
+ // exception), and creates a mapping from the pointer to the exception object
62
+ // (which is a parameter to __cxa_throw) to its ExceptionTraceHolder object.
63
+ // This mapping can then be queried by lyra::getExceptionTrace to get the stack
64
+ // trace for the exception. We have a custom exception destructor to destroy the
65
+ // trace object and call the original destructor for the exception object, and
66
+ // our __cxa_throw hook calls the original __cxa_throw to perform the actual
67
+ // exception throwing and passes this custom destructor.
68
+ //
69
+ // This works because __cxa_throw is only called when creating a new exception
70
+ // object (that has been freshly allocated via __cxa_allocate_exception), so at
71
+ // that point, we're able to capture the original stack trace for the exception.
72
+ // Even if that exception is later rethrown, we'll still maintain its original
73
+ // stack trace, assuming that std::current_exception creates a reference to the
74
+ // current exception instead of copying it (which is true for both libstdc++ and
75
+ // libc++), such that we can still look up the exception object via pointer.
76
+ //
77
+ // We don't have to worry about any pointer adjustments for the exception object
78
+ // (e.g. for converting to or from a base class subobject pointer), because a
79
+ // std::exception_ptr will always capture the pointer to the exception object
80
+ // itself and not any subobjects.
81
+ //
82
+ // Our map must be global, since exceptions can be transferred across threads.
83
+ // Consequently, we must use a mutex to guard all map operations.
84
+
85
+ typedef void (*destructor_type)(void *);
86
+
87
+ namespace {
88
+ struct ExceptionState {
89
+ ExceptionTraceHolder trace;
90
+ destructor_type destructor;
91
+ };
92
+
93
+ // We create our map and mutex as function statics and leak them intentionally,
94
+ // to ensure they've been initialized before any global constructors and are
95
+ // also available to use inside any global destructors.
96
+ std::unordered_map<void *, ExceptionState>* get_exception_state_map () {
97
+ static auto * exception_state_map =
98
+ new std::unordered_map<void *, ExceptionState>();
99
+ return exception_state_map;
42
100
}
43
- #else
44
101
45
- using namespace detail ;
102
+ std::mutex* get_exception_state_map_mutex () {
103
+ static auto * exception_state_map_mutex = new std::mutex ();
104
+ return exception_state_map_mutex;
105
+ }
106
+
107
+ void trace_destructor (void * exception_obj) {
108
+ destructor_type original_destructor = nullptr ;
109
+
110
+ {
111
+ std::lock_guard<std::mutex> lock (*get_exception_state_map_mutex ());
112
+ auto * exception_state_map = get_exception_state_map ();
113
+ auto it = exception_state_map->find (exception_obj);
114
+ if (it == exception_state_map->end ()) {
115
+ // This really shouldn't happen, but if it does, just leaking the trace
116
+ // and exception object seems better than crashing.
117
+ return ;
118
+ }
119
+
120
+ original_destructor = it->second .destructor ;
121
+ exception_state_map->erase (it);
122
+ }
123
+
124
+ if (original_destructor) {
125
+ original_destructor (exception_obj);
126
+ }
127
+ }
128
+ } // namespace
129
+
130
+ [[noreturn]] void
131
+ cxa_throw (void * obj, const std::type_info* type, destructor_type destructor) {
132
+ if (enableBacktraces.load (std::memory_order_relaxed)) {
133
+ std::lock_guard<std::mutex> lock (*get_exception_state_map_mutex ());
134
+ get_exception_state_map ()->emplace (
135
+ obj, ExceptionState{ExceptionTraceHolder (), destructor});
136
+ }
137
+
138
+ original_cxa_throw (obj, type, trace_destructor);
139
+ }
140
+
141
+ const ExceptionTraceHolder* detail::getExceptionTraceHolder (
142
+ std::exception_ptr ptr) {
143
+ {
144
+ std::lock_guard<std::mutex> lock (*get_exception_state_map_mutex ());
145
+ // The exception object pointer isn't a public member of std::exception_ptr,
146
+ // and there isn't any public method to get it. However, for both libstdc++
147
+ // and libc++, it's the first pointer inside the exception_ptr, and we can
148
+ // rely on the ABI of those libraries to remain stable, so we can just
149
+ // access it directly.
150
+ void * exception_obj = *reinterpret_cast <void **>(&ptr);
151
+ auto * exception_state_map = get_exception_state_map ();
152
+ auto it = exception_state_map->find (exception_obj);
153
+ if (it != exception_state_map->end ()) {
154
+ return &it->second .trace ;
155
+ }
156
+ }
157
+
158
+ // Fall back to attempting to retrieve the ExceptionTraceHolder directly from
159
+ // the exception (to support e.g. fbthrow).
160
+ return getExceptionTraceHolderInException (ptr);
161
+ }
162
+ #else
46
163
47
164
namespace {
48
165
@@ -110,6 +227,11 @@ struct HijackedExceptionTypeInfo : public abi::__class_type_info {
110
227
original_cxa_throw (obj, type, destructor);
111
228
}
112
229
230
+ const ExceptionTraceHolder* detail::getExceptionTraceHolder (
231
+ std::exception_ptr ptr) {
232
+ return getExceptionTraceHolderInException (ptr);
233
+ }
234
+
113
235
#endif // libc++
114
236
115
237
} // namespace lyra
0 commit comments