1 | /* |
2 | * Copyright 2012-present 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 <folly/experimental/exception_tracer/ExceptionTracer.h> |
18 | |
19 | #include <exception> |
20 | #include <iostream> |
21 | |
22 | #include <dlfcn.h> |
23 | |
24 | #include <glog/logging.h> |
25 | |
26 | #include <folly/String.h> |
27 | #include <folly/experimental/exception_tracer/ExceptionAbi.h> |
28 | #include <folly/experimental/exception_tracer/StackTrace.h> |
29 | #include <folly/experimental/symbolizer/Symbolizer.h> |
30 | |
31 | namespace { |
32 | |
33 | using namespace ::folly::exception_tracer; |
34 | using namespace ::folly::symbolizer; |
35 | using namespace __cxxabiv1; |
36 | |
37 | extern "C" { |
38 | StackTraceStack* getExceptionStackTraceStack(void) __attribute__((__weak__)); |
39 | typedef StackTraceStack* (*GetExceptionStackTraceStackType)(); |
40 | GetExceptionStackTraceStackType getExceptionStackTraceStackFn; |
41 | } |
42 | |
43 | } // namespace |
44 | |
45 | namespace folly { |
46 | namespace exception_tracer { |
47 | |
48 | std::ostream& operator<<(std::ostream& out, const ExceptionInfo& info) { |
49 | printExceptionInfo(out, info, SymbolizePrinter::COLOR_IF_TTY); |
50 | return out; |
51 | } |
52 | |
53 | void printExceptionInfo( |
54 | std::ostream& out, |
55 | const ExceptionInfo& info, |
56 | int options) { |
57 | out << "Exception type: " ; |
58 | if (info.type) { |
59 | out << folly::demangle(*info.type); |
60 | } else { |
61 | out << "(unknown type)" ; |
62 | } |
63 | out << " (" << info.frames.size() |
64 | << (info.frames.size() == 1 ? " frame" : " frames" ) << ")\n" ; |
65 | try { |
66 | size_t frameCount = info.frames.size(); |
67 | |
68 | // Skip our own internal frames |
69 | static constexpr size_t kInternalFramesNumber = 3; |
70 | if (frameCount > kInternalFramesNumber) { |
71 | auto addresses = info.frames.data() + kInternalFramesNumber; |
72 | frameCount -= kInternalFramesNumber; |
73 | |
74 | std::vector<SymbolizedFrame> frames; |
75 | frames.resize(frameCount); |
76 | |
77 | Symbolizer symbolizer( |
78 | (options & SymbolizePrinter::NO_FILE_AND_LINE) |
79 | ? Dwarf::LocationInfoMode::DISABLED |
80 | : Symbolizer::kDefaultLocationInfoMode); |
81 | symbolizer.symbolize(addresses, frames.data(), frameCount); |
82 | |
83 | OStreamSymbolizePrinter osp(out, options); |
84 | osp.println(addresses, frames.data(), frameCount); |
85 | } |
86 | } catch (const std::exception& e) { |
87 | out << "\n !! caught " << folly::exceptionStr(e) << "\n" ; |
88 | } catch (...) { |
89 | out << "\n !!! caught unexpected exception\n" ; |
90 | } |
91 | } |
92 | |
93 | namespace { |
94 | |
95 | /** |
96 | * Is this a standard C++ ABI exception? |
97 | * |
98 | * Dependent exceptions (thrown via std::rethrow_exception) aren't -- |
99 | * exc doesn't actually point to a __cxa_exception structure, but |
100 | * the offset of unwindHeader is correct, so exc->unwindHeader actually |
101 | * returns a _Unwind_Exception object. Yeah, it's ugly like that. |
102 | */ |
103 | bool isAbiCppException(const __cxa_exception* exc) { |
104 | // The least significant four bytes must be "C++\0" |
105 | static const uint64_t cppClass = |
106 | ((uint64_t)'C' << 24) | ((uint64_t)'+' << 16) | ((uint64_t)'+' << 8); |
107 | return (exc->unwindHeader.exception_class & 0xffffffff) == cppClass; |
108 | } |
109 | |
110 | } // namespace |
111 | |
112 | std::vector<ExceptionInfo> getCurrentExceptions() { |
113 | struct Once { |
114 | Once() { |
115 | // See if linked in with us (getExceptionStackTraceStack is weak) |
116 | getExceptionStackTraceStackFn = getExceptionStackTraceStack; |
117 | |
118 | if (!getExceptionStackTraceStackFn) { |
119 | // Nope, see if it's in a shared library |
120 | getExceptionStackTraceStackFn = (GetExceptionStackTraceStackType)dlsym( |
121 | RTLD_NEXT, "getExceptionStackTraceStack" ); |
122 | } |
123 | } |
124 | }; |
125 | static Once once; |
126 | |
127 | std::vector<ExceptionInfo> exceptions; |
128 | auto currentException = __cxa_get_globals()->caughtExceptions; |
129 | if (!currentException) { |
130 | return exceptions; |
131 | } |
132 | |
133 | StackTraceStack* traceStack = nullptr; |
134 | if (!getExceptionStackTraceStackFn) { |
135 | static bool logged = false; |
136 | if (!logged) { |
137 | LOG(WARNING) |
138 | << "Exception tracer library not linked, stack traces not available" ; |
139 | logged = true; |
140 | } |
141 | } else if ((traceStack = getExceptionStackTraceStackFn()) == nullptr) { |
142 | static bool logged = false; |
143 | if (!logged) { |
144 | LOG(WARNING) |
145 | << "Exception stack trace invalid, stack traces not available" ; |
146 | logged = true; |
147 | } |
148 | } |
149 | |
150 | StackTrace* trace = traceStack ? traceStack->top() : nullptr; |
151 | while (currentException) { |
152 | ExceptionInfo info; |
153 | // Dependent exceptions (thrown via std::rethrow_exception) aren't |
154 | // standard ABI __cxa_exception objects, and are correctly labeled as |
155 | // such in the exception_class field. We could try to extract the |
156 | // primary exception type in horribly hacky ways, but, for now, nullptr. |
157 | info.type = isAbiCppException(currentException) |
158 | ? currentException->exceptionType |
159 | : nullptr; |
160 | |
161 | if (traceStack) { |
162 | LOG_IF(DFATAL, !trace) |
163 | << "Invalid trace stack for exception of type: " |
164 | << (info.type ? folly::demangle(*info.type) : "null" ); |
165 | |
166 | if (!trace) { |
167 | return {}; |
168 | } |
169 | |
170 | info.frames.assign( |
171 | trace->addresses, trace->addresses + trace->frameCount); |
172 | trace = traceStack->next(trace); |
173 | } |
174 | currentException = currentException->nextException; |
175 | exceptions.push_back(std::move(info)); |
176 | } |
177 | |
178 | LOG_IF(DFATAL, trace) << "Invalid trace stack!" ; |
179 | |
180 | return exceptions; |
181 | } |
182 | |
183 | namespace { |
184 | |
185 | std::terminate_handler origTerminate = abort; |
186 | std::unexpected_handler origUnexpected = abort; |
187 | |
188 | void dumpExceptionStack(const char* prefix) { |
189 | auto exceptions = getCurrentExceptions(); |
190 | if (exceptions.empty()) { |
191 | return; |
192 | } |
193 | LOG(ERROR) << prefix << ", exception stack follows" ; |
194 | for (auto& exc : exceptions) { |
195 | LOG(ERROR) << exc << "\n" ; |
196 | } |
197 | LOG(ERROR) << "exception stack complete" ; |
198 | } |
199 | |
200 | void terminateHandler() { |
201 | dumpExceptionStack("terminate() called" ); |
202 | origTerminate(); |
203 | } |
204 | |
205 | void unexpectedHandler() { |
206 | dumpExceptionStack("Unexpected exception" ); |
207 | origUnexpected(); |
208 | } |
209 | |
210 | } // namespace |
211 | |
212 | void installHandlers() { |
213 | struct Once { |
214 | Once() { |
215 | origTerminate = std::set_terminate(terminateHandler); |
216 | origUnexpected = std::set_unexpected(unexpectedHandler); |
217 | } |
218 | }; |
219 | static Once once; |
220 | } |
221 | |
222 | } // namespace exception_tracer |
223 | } // namespace folly |
224 | |