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 <lyra/lyra.h>
18
19 #include <atomic>
20 #include <iomanip>
21 #include <ios>
22 #include <memory>
23 #include <ostream>
24 #include <vector>
25
26 #ifndef _WIN32
27 #include <dlfcn.h>
28 #include <unwind.h>
29 #endif
30
31 #include <fbjni/detail/Log.h>
32
33 using namespace std;
34
35 namespace facebook {
36 namespace lyra {
37
38 namespace {
39
40 class IosFlagsSaver {
41 ios_base& ios_;
42 ios_base::fmtflags flags_;
43
44 public:
IosFlagsSaver(ios_base & ios)45 IosFlagsSaver(ios_base& ios) : ios_(ios), flags_(ios.flags()) {}
46
~IosFlagsSaver()47 ~IosFlagsSaver() {
48 ios_.flags(flags_);
49 }
50 };
51
52 struct BacktraceState {
53 size_t skip;
54 vector<InstructionPointer>& stackTrace;
55 };
56
57 #ifndef _MSC_VER
unwindCallback(struct _Unwind_Context * context,void * arg)58 _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) {
59 BacktraceState* state = reinterpret_cast<BacktraceState*>(arg);
60 auto absoluteProgramCounter =
61 reinterpret_cast<InstructionPointer>(_Unwind_GetIP(context));
62
63 if (state->skip > 0) {
64 --state->skip;
65 return _URC_NO_REASON;
66 }
67
68 if (state->stackTrace.size() == state->stackTrace.capacity()) {
69 return _URC_END_OF_STACK;
70 }
71
72 state->stackTrace.push_back(absoluteProgramCounter);
73
74 return _URC_NO_REASON;
75 }
76 #endif
77
captureBacktrace(size_t skip,vector<InstructionPointer> & stackTrace)78 void captureBacktrace(size_t skip, vector<InstructionPointer>& stackTrace) {
79 // Beware of a bug on some platforms, which makes the trace loop until the
80 // buffer is full when it reaches a noexcept function. It seems to be fixed in
81 // newer versions of gcc. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56846
82 // TODO(t10738439): Investigate workaround for the stack trace bug
83 BacktraceState state = {skip, stackTrace};
84 #ifndef _WIN32
85 _Unwind_Backtrace(unwindCallback, &state);
86 #endif
87 }
88
89 // this is a pointer to a function
90 std::atomic<LibraryIdentifierFunctionType> gLibraryIdentifierFunction{nullptr};
91
92 } // namespace
93
setLibraryIdentifierFunction(LibraryIdentifierFunctionType func)94 void setLibraryIdentifierFunction(LibraryIdentifierFunctionType func) {
95 gLibraryIdentifierFunction.store(func, std::memory_order_relaxed);
96 }
97
buildId() const98 std::string StackTraceElement::buildId() const {
99 if (!hasBuildId_) {
100 auto getBuildId =
101 gLibraryIdentifierFunction.load(std::memory_order_relaxed);
102 if (getBuildId) {
103 buildId_ = getBuildId(libraryName());
104 } else {
105 buildId_ = "<unimplemented>";
106 }
107 hasBuildId_ = true;
108 }
109 return buildId_;
110 }
111
getStackTrace(vector<InstructionPointer> & stackTrace,size_t skip)112 void getStackTrace(vector<InstructionPointer>& stackTrace, size_t skip) {
113 stackTrace.clear();
114 captureBacktrace(skip + 1, stackTrace);
115 }
116
117 // TODO(t10737622): Improve on-device symbolification
getStackTraceSymbols(vector<StackTraceElement> & symbols,const vector<InstructionPointer> & trace)118 void getStackTraceSymbols(
119 vector<StackTraceElement>& symbols,
120 const vector<InstructionPointer>& trace) {
121 symbols.clear();
122 symbols.reserve(trace.size());
123
124 #ifndef _WIN32
125 for (size_t i = 0; i < trace.size(); ++i) {
126 Dl_info info;
127 if (dladdr(trace[i], &info)) {
128 symbols.emplace_back(
129 trace[i],
130 info.dli_fbase,
131 info.dli_saddr,
132 info.dli_fname ? info.dli_fname : "",
133 info.dli_sname ? info.dli_sname : "");
134 }
135 }
136 #endif
137 }
138
operator <<(ostream & out,const StackTraceElement & elm)139 ostream& operator<<(ostream& out, const StackTraceElement& elm) {
140 IosFlagsSaver flags{out};
141
142 out << "{dso=" << elm.libraryName() << " offset=" << hex << showbase
143 << elm.libraryOffset();
144
145 if (!elm.functionName().empty()) {
146 out << " func=" << elm.functionName() << "()+" << elm.functionOffset();
147 }
148
149 out << " build-id=" << hex << setw(8) << elm.buildId() << "}";
150
151 return out;
152 }
153
154 // TODO(t10737667): The implement a tool that parse the stack trace and
155 // symbolicate it
operator <<(ostream & out,const vector<StackTraceElement> & trace)156 ostream& operator<<(ostream& out, const vector<StackTraceElement>& trace) {
157 IosFlagsSaver flags{out};
158
159 auto i = 0;
160 out << "Backtrace:\n";
161 for (auto& elm : trace) {
162 out << " #" << dec << setfill('0') << setw(2) << i++ << " " << elm
163 << '\n';
164 }
165
166 return out;
167 }
168
logStackTrace(const vector<StackTraceElement> & trace)169 void logStackTrace(const vector<StackTraceElement>& trace) {
170 auto i = 0;
171 FBJNI_LOGE("Backtrace:");
172 for (auto& elm : trace) {
173 if (!elm.functionName().empty()) {
174 FBJNI_LOGE(
175 " #%02d |lyra|{dso=%s offset=%#tx func=%s+%#x build-id=%s}",
176 i++,
177 elm.libraryName().c_str(),
178 elm.libraryOffset(),
179 elm.functionName().c_str(),
180 elm.functionOffset(),
181 elm.buildId().c_str());
182 } else {
183 FBJNI_LOGE(
184 " #%02d |lyra|{dso=%s offset=%#tx build-id=%s}",
185 i++,
186 elm.libraryName().c_str(),
187 elm.libraryOffset(),
188 elm.buildId().c_str());
189 }
190 }
191 }
192
193 } // namespace lyra
194 } // namespace facebook
195