1 /*
2 * Copyright (c) 2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 #include <fstream>
16 #include <sstream>
17 #include <iomanip>
18 #include <memory>
19 #include <algorithm>
20 #include <unwind.h>
21
22 #include "stacktrace.h"
23 #include "os/mutex.h"
24
25 #include <cxxabi.h>
26 #include <dlfcn.h>
27 #include "debug_info.h"
28
29 namespace panda {
30
31 struct VmaEntry {
32 enum DebugInfoStatus { NOT_READ, VALID, BAD };
33
34 // NOLINTNEXTLINE(modernize-pass-by-value)
VmaEntrypanda::VmaEntry35 VmaEntry(uintptr_t param_start_addr, uintptr_t param_end_addr, uintptr_t param_offset, const std::string &fname)
36 : start_addr(param_start_addr), end_addr(param_end_addr), offset(param_offset), filename(fname)
37 {
38 }
39
40 ~VmaEntry() = default;
41
42 uintptr_t start_addr; // NOLINT(misc-non-private-member-variables-in-classes)
43 uintptr_t end_addr; // NOLINT(misc-non-private-member-variables-in-classes)
44 uintptr_t offset; // NOLINT(misc-non-private-member-variables-in-classes)
45 std::string filename; // NOLINT(misc-non-private-member-variables-in-classes)
46 DebugInfoStatus status {NOT_READ}; // NOLINT(misc-non-private-member-variables-in-classes)
47 DebugInfo debug_info; // NOLINT(misc-non-private-member-variables-in-classes)
48
49 DEFAULT_MOVE_SEMANTIC(VmaEntry);
50 NO_COPY_SEMANTIC(VmaEntry);
51 };
52
53 class Tokenizer {
54 public:
55 // NOLINTNEXTLINE(modernize-pass-by-value)
Tokenizer(const std::string & str)56 explicit Tokenizer(const std::string &str) : str_(str), pos_(0) {}
57
Next(char delim=' ')58 std::string Next(char delim = ' ')
59 {
60 while (pos_ < str_.length() && str_[pos_] == ' ') {
61 ++pos_;
62 }
63 size_t pos = str_.find(delim, pos_);
64 std::string token;
65 if (pos == std::string::npos) {
66 token = str_.substr(pos_);
67 pos_ = str_.length();
68 } else {
69 token = str_.substr(pos_, pos - pos_);
70 pos_ = pos + 1; // skip delimiter
71 }
72 return token;
73 }
74
75 private:
76 std::string str_;
77 size_t pos_;
78 };
79
80 class StackPrinter {
81 public:
GetInstance()82 static StackPrinter &GetInstance()
83 {
84 static StackPrinter printer;
85 return printer;
86 }
87
Print(const std::vector<uintptr_t> & stacktrace,std::ostream & out)88 std::ostream &Print(const std::vector<uintptr_t> &stacktrace, std::ostream &out)
89 {
90 os::memory::LockHolder lock(mutex_);
91 ScanVma();
92 for (size_t frame_num = 0; frame_num < stacktrace.size(); ++frame_num) {
93 PrintFrame(frame_num, stacktrace[frame_num], out);
94 }
95 return out;
96 }
97
98 NO_MOVE_SEMANTIC(StackPrinter);
99 NO_COPY_SEMANTIC(StackPrinter);
100
101 private:
102 explicit StackPrinter() = default;
103 ~StackPrinter() = default;
104
PrintFrame(size_t frame_num,uintptr_t pc,std::ostream & out)105 void PrintFrame(size_t frame_num, uintptr_t pc, std::ostream &out)
106 {
107 std::ios_base::fmtflags f = out.flags();
108 auto w = out.width();
109 out << "#" << std::setw(2U) << std::left << frame_num << ": 0x" << std::hex << pc << " ";
110 out.flags(f);
111 out.width(w);
112
113 VmaEntry *vma = FindVma(pc);
114 if (vma == nullptr) {
115 vmas_.clear();
116 ScanVma();
117 vma = FindVma(pc);
118 }
119 if (vma != nullptr) {
120 uintptr_t pc_offset = pc - vma->start_addr + vma->offset;
121 // pc points to the instruction after the call
122 // Decrement pc to get source line number pointing to the function call
123 --pc_offset;
124 std::string function;
125 std::string src_file;
126 unsigned int line = 0;
127 if (ReadDebugInfo(vma) && vma->debug_info.GetSrcLocation(pc_offset, &function, &src_file, &line)) {
128 PrintFrame(function, src_file, line, out);
129 return;
130 }
131 uintptr_t offset = 0;
132 if (ReadSymbol(pc, &function, &offset)) {
133 PrintFrame(function, offset, out);
134 return;
135 }
136 }
137 out << "??:??\n";
138 }
139
PrintFrame(const std::string & function,const std::string & src_file,unsigned int line,std::ostream & out) const140 void PrintFrame(const std::string &function, const std::string &src_file, unsigned int line,
141 std::ostream &out) const
142 {
143 if (function.empty()) {
144 out << "??";
145 } else {
146 Demangle(function, out);
147 }
148 out << "\n at ";
149 if (src_file.empty()) {
150 out << "??";
151 } else {
152 out << src_file;
153 }
154 out << ":";
155 if (line == 0) {
156 out << "??";
157 } else {
158 out << line;
159 }
160
161 out << "\n";
162 }
163
PrintFrame(const std::string & function,uintptr_t offset,std::ostream & out) const164 void PrintFrame(const std::string &function, uintptr_t offset, std::ostream &out) const
165 {
166 std::ios_base::fmtflags f = out.flags();
167 Demangle(function, out);
168 out << std::hex << "+0x" << offset << "\n";
169 out.flags(f);
170 }
171
ReadSymbol(uintptr_t pc,std::string * function,uintptr_t * offset) const172 bool ReadSymbol(uintptr_t pc, std::string *function, uintptr_t *offset) const
173 {
174 Dl_info info {};
175 if (dladdr(reinterpret_cast<void *>(pc), &info) != 0 && info.dli_sname != nullptr) {
176 *function = info.dli_sname;
177 *offset = pc - reinterpret_cast<uintptr_t>(info.dli_saddr);
178 return true;
179 }
180 return false;
181 }
182
Demangle(const std::string & function,std::ostream & out) const183 void Demangle(const std::string &function, std::ostream &out) const
184 {
185 size_t length = 0;
186 int status = 0;
187 char *demangled_function = abi::__cxa_demangle(function.c_str(), nullptr, &length, &status);
188 if (status == 0) {
189 out << demangled_function;
190 free(demangled_function); // NOLINT(cppcoreguidelines-no-malloc)
191 } else {
192 out << function;
193 }
194 }
195
FindVma(uintptr_t pc)196 VmaEntry *FindVma(uintptr_t pc)
197 {
198 VmaEntry el(pc, pc, 0, "");
199 auto it = std::upper_bound(vmas_.begin(), vmas_.end(), el,
200 [](const VmaEntry &e1, const VmaEntry &e2) { return e1.end_addr < e2.end_addr; });
201 if (it != vmas_.end() && (it->start_addr <= pc && pc < it->end_addr)) {
202 return &(*it);
203 }
204 return nullptr;
205 }
206
ReadDebugInfo(VmaEntry * vma) const207 bool ReadDebugInfo(VmaEntry *vma) const
208 {
209 if (vma->status == VmaEntry::VALID) {
210 return true;
211 }
212 if (vma->status == VmaEntry::BAD) {
213 return false;
214 }
215 if (!vma->filename.empty() && vma->debug_info.ReadFromFile(vma->filename.c_str()) == DebugInfo::SUCCESS) {
216 vma->status = VmaEntry::VALID;
217 return true;
218 }
219 vma->status = VmaEntry::BAD;
220 return false;
221 }
222
ScanVma()223 void ScanVma()
224 {
225 static const int HEX_RADIX = 16;
226 static const size_t MODE_FIELD_LEN = 4;
227 static const size_t XMODE_POS = 2;
228
229 if (!vmas_.empty()) {
230 return;
231 }
232
233 std::stringstream fname;
234 fname << "/proc/self/maps";
235 std::string filename = fname.str();
236 std::ifstream maps(filename.c_str());
237
238 while (maps) {
239 std::string line;
240 std::getline(maps, line);
241 Tokenizer tokenizer(line);
242 std::string start_addr = tokenizer.Next('-');
243 std::string end_addr = tokenizer.Next();
244 std::string rights = tokenizer.Next();
245 if (rights.length() == MODE_FIELD_LEN && rights[XMODE_POS] == 'x') {
246 std::string offset = tokenizer.Next();
247 tokenizer.Next();
248 tokenizer.Next();
249 std::string obj_filename = tokenizer.Next();
250 vmas_.emplace_back(stoul(start_addr, nullptr, HEX_RADIX), stoul(end_addr, nullptr, HEX_RADIX),
251 stoul(offset, nullptr, HEX_RADIX), obj_filename);
252 }
253 }
254 }
255
256 private:
257 std::vector<VmaEntry> vmas_;
258 os::memory::Mutex mutex_;
259 };
260
261 class Buf {
262 public:
Buf(uintptr_t * buf,size_t skip,size_t capacity)263 Buf(uintptr_t *buf, size_t skip, size_t capacity) : buf_(buf), skip_(skip), size_(0), capacity_(capacity) {}
264 ~Buf() = default;
265 DEFAULT_MOVE_SEMANTIC(Buf);
266 DEFAULT_COPY_SEMANTIC(Buf);
Append(uintptr_t pc)267 void Append(uintptr_t pc)
268 {
269 if (skip_ > 0) {
270 // Skip the element
271 --skip_;
272 return;
273 }
274 if (size_ >= capacity_) {
275 return;
276 }
277 buf_[size_++] = pc; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
278 }
279
Size() const280 int Size() const
281 {
282 return size_;
283 }
284
285 private:
286 uintptr_t *buf_;
287 size_t skip_;
288 size_t size_;
289 size_t capacity_;
290 };
291
FrameHandler(struct _Unwind_Context * ctx,void * arg)292 static _Unwind_Reason_Code FrameHandler(struct _Unwind_Context *ctx, [[maybe_unused]] void *arg)
293 {
294 Buf *buf = reinterpret_cast<Buf *>(arg);
295 uintptr_t pc = _Unwind_GetIP(ctx);
296 // _Unwind_GetIP returns 0 pc at the end of the stack. Ignore it
297 if (pc != 0) {
298 buf->Append(pc);
299 }
300 return _URC_NO_REASON;
301 }
302
GetStacktrace()303 std::vector<uintptr_t> GetStacktrace()
304 {
305 static constexpr size_t BUF_SIZE = 100;
306 static constexpr int SKIP_FRAMES = 2; // backtrace
307 std::vector<uintptr_t> buf;
308 buf.resize(BUF_SIZE);
309 Buf buf_wrapper(buf.data(), SKIP_FRAMES, buf.size());
310 _Unwind_Reason_Code res = _Unwind_Backtrace(FrameHandler, &buf_wrapper);
311 if (res != _URC_END_OF_STACK || buf_wrapper.Size() < 0) {
312 return std::vector<uintptr_t>();
313 }
314
315 buf.resize(buf_wrapper.Size());
316 return buf;
317 }
318
PrintStack(const std::vector<uintptr_t> & stacktrace,std::ostream & out)319 std::ostream &PrintStack(const std::vector<uintptr_t> &stacktrace, std::ostream &out)
320 {
321 return StackPrinter::GetInstance().Print(stacktrace, out);
322 }
323
324 } // namespace panda
325