Change exception tracer to use per-thread caching in libunwind
[folly.git] / folly / experimental / exception_tracer / ExceptionTracerLib.cpp
1 /*
2  * Copyright 2013 Facebook, Inc.
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 <dlfcn.h>
18 #include <pthread.h>
19 #include <stdlib.h>
20
21 #include <glog/logging.h>
22
23 #include "folly/Portability.h"
24 #include "folly/experimental/exception_tracer/StackTrace.h"
25 #include "folly/experimental/exception_tracer/ExceptionAbi.h"
26 #include "folly/experimental/exception_tracer/ExceptionTracer.h"
27
28 namespace __cxxabiv1 {
29
30 extern "C" {
31 void __cxa_throw(void* thrownException, std::type_info* type,
32                  void (*destructor)(void)) FOLLY_NORETURN;
33 void* __cxa_begin_catch(void* excObj);
34 void __cxa_rethrow(void) FOLLY_NORETURN;
35 void __cxa_end_catch(void);
36 }
37
38 }  // namespace __cxxabiv1
39
40 namespace {
41
42 __thread bool invalid;
43 __thread StackTraceStack* activeExceptions;
44 __thread StackTraceStack* caughtExceptions;
45 pthread_once_t initialized = PTHREAD_ONCE_INIT;
46
47 extern "C" {
48 typedef void (*CxaThrowType)(void*, std::type_info*, void (*)(void))
49   FOLLY_NORETURN;
50 typedef void* (*CxaBeginCatchType)(void*);
51 typedef void (*CxaRethrowType)(void)
52   FOLLY_NORETURN;
53 typedef void (*CxaEndCatchType)(void);
54
55 CxaThrowType orig_cxa_throw;
56 CxaBeginCatchType orig_cxa_begin_catch;
57 CxaRethrowType orig_cxa_rethrow;
58 CxaEndCatchType orig_cxa_end_catch;
59 }  // extern "C"
60
61 typedef void (*RethrowExceptionType)(std::exception_ptr)
62   FOLLY_NORETURN;
63 RethrowExceptionType orig_rethrow_exception;
64
65 void initialize() {
66   orig_cxa_throw = (CxaThrowType)dlsym(RTLD_NEXT, "__cxa_throw");
67   orig_cxa_begin_catch =
68     (CxaBeginCatchType)dlsym(RTLD_NEXT, "__cxa_begin_catch");
69   orig_cxa_rethrow =
70     (CxaRethrowType)dlsym(RTLD_NEXT, "__cxa_rethrow");
71   orig_cxa_end_catch = (CxaEndCatchType)dlsym(RTLD_NEXT, "__cxa_end_catch");
72   // Mangled name for std::rethrow_exception
73   // TODO(tudorb): Dicey, as it relies on the fact that std::exception_ptr
74   // is typedef'ed to a type in namespace __exception_ptr
75   orig_rethrow_exception =
76     (RethrowExceptionType)dlsym(
77         RTLD_NEXT,
78         "_ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE");
79
80   if (!orig_cxa_throw || !orig_cxa_begin_catch || !orig_cxa_rethrow ||
81       !orig_cxa_end_catch || !orig_rethrow_exception) {
82     abort();  // what else can we do?
83   }
84
85   initStackTrace();
86 }
87
88 }  // namespace
89
90 // This function is exported and may be found via dlsym(RTLD_NEXT, ...)
91 extern "C" const StackTraceStack* getExceptionStackTraceStack() {
92   return caughtExceptions;
93 }
94
95 namespace {
96 // Make sure we're counting stack frames correctly for the "skip" argument to
97 // pushCurrentStackTrace, don't inline.
98 void addActiveException() __attribute__((noinline));
99
100 void addActiveException() {
101   pthread_once(&initialized, initialize);
102   // Capture stack trace
103   if (!invalid) {
104     if (pushCurrentStackTrace(3, &activeExceptions) != 0) {
105       clearStack(&activeExceptions);
106       clearStack(&caughtExceptions);
107       invalid = true;
108     }
109   }
110 }
111
112 void moveTopException(StackTraceStack** from, StackTraceStack** to) {
113   if (invalid) {
114     return;
115   }
116   if (moveTop(from, to) != 0) {
117     clearStack(from);
118     clearStack(to);
119     invalid = true;
120   }
121 }
122
123 }  // namespace
124
125 namespace __cxxabiv1 {
126
127 void __cxa_throw(void* thrownException, std::type_info* type,
128                  void (*destructor)(void)) {
129   addActiveException();
130   orig_cxa_throw(thrownException, type, destructor);
131 }
132
133 void __cxa_rethrow() {
134   // __cxa_rethrow leaves the current exception on the caught stack,
135   // and __cxa_begin_catch recognizes that case.  We could do the same, but
136   // we'll implement something simpler (and slower): we pop the exception from
137   // the caught stack, and push it back onto the active stack; this way, our
138   // implementation of __cxa_begin_catch doesn't have to do anything special.
139   moveTopException(&caughtExceptions, &activeExceptions);
140   orig_cxa_rethrow();
141 }
142
143 void* __cxa_begin_catch(void *excObj) {
144   // excObj is a pointer to the unwindHeader in __cxa_exception
145   moveTopException(&activeExceptions, &caughtExceptions);
146   return orig_cxa_begin_catch(excObj);
147 }
148
149 void __cxa_end_catch() {
150   if (!invalid) {
151     __cxa_exception* top = __cxa_get_globals_fast()->caughtExceptions;
152     // This is gcc specific and not specified in the ABI:
153     // abs(handlerCount) is the number of active handlers, it's negative
154     // for rethrown exceptions and positive (always 1) for regular exceptions.
155     // In the rethrow case, we've already popped the exception off the
156     // caught stack, so we don't do anything here.
157     if (top->handlerCount == 1) {
158       popStackTrace(&caughtExceptions);
159     }
160   }
161   orig_cxa_end_catch();
162 }
163
164 }  // namespace __cxxabiv1
165
166 namespace std {
167
168 void rethrow_exception(std::exception_ptr ep) {
169   addActiveException();
170   orig_rethrow_exception(ep);
171 }
172
173 }  // namespace std
174
175
176 namespace {
177
178 struct Initializer {
179   Initializer() {
180     try {
181       ::folly::exception_tracer::installHandlers();
182     } catch (...) {
183     }
184   }
185 };
186
187 Initializer initializer;
188
189 }  // namespace