1/**
2 * Copyright (c) 2021-2022 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
29namespace panda {
30
31struct VmaEntry {
32    enum DebugInfoStatus { NOT_READ, VALID, BAD };
33
34    // NOLINTNEXTLINE(modernize-pass-by-value)
35    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
53class Tokenizer {
54public:
55    // NOLINTNEXTLINE(modernize-pass-by-value)
56    explicit Tokenizer(const std::string &str) : str_(str), pos_(0) {}
57
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
75private:
76    std::string str_;
77    size_t pos_;
78};
79
80class StackPrinter {
81public:
82    static StackPrinter &GetInstance()
83    {
84        static StackPrinter printer;
85        return printer;
86    }
87
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
101private:
102    explicit StackPrinter() = default;
103    ~StackPrinter() = default;
104
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
140    void PrintFrame(const std::string &function, const std::string &src_file, unsigned int line, std::ostream &out)
141    {
142        if (function.empty()) {
143            out << "??";
144        } else {
145            Demangle(function, out);
146        }
147        out << "\n     at ";
148        if (src_file.empty()) {
149            out << "??";
150        } else {
151            out << src_file;
152        }
153        out << ":";
154        if (line == 0) {
155            out << "??";
156        } else {
157            out << line;
158        }
159
160        out << "\n";
161    }
162
163    void PrintFrame(const std::string &function, uintptr_t offset, std::ostream &out)
164    {
165        std::ios_base::fmtflags f = out.flags();
166        Demangle(function, out);
167        out << std::hex << "+0x" << offset << "\n";
168        out.flags(f);
169    }
170
171    bool ReadSymbol(uintptr_t pc, std::string *function, uintptr_t *offset)
172    {
173        Dl_info info {};
174        if (dladdr(reinterpret_cast<void *>(pc), &info) != 0 && info.dli_sname != nullptr) {
175            *function = info.dli_sname;
176            *offset = pc - reinterpret_cast<uintptr_t>(info.dli_saddr);
177            return true;
178        }
179        return false;
180    }
181
182    void Demangle(const std::string &function, std::ostream &out)
183    {
184        size_t length = 0;
185        int status = 0;
186        char *demangled_function = abi::__cxa_demangle(function.c_str(), nullptr, &length, &status);
187        if (status == 0) {
188            out << demangled_function;
189            free(demangled_function);  // NOLINT(cppcoreguidelines-no-malloc)
190        } else {
191            out << function;
192        }
193    }
194
195    VmaEntry *FindVma(uintptr_t pc)
196    {
197        VmaEntry el(pc, pc, 0, "");
198        auto it = std::upper_bound(vmas_.begin(), vmas_.end(), el,
199                                   [](const VmaEntry &e1, const VmaEntry &e2) { return e1.end_addr < e2.end_addr; });
200        if (it != vmas_.end() && (it->start_addr <= pc && pc < it->end_addr)) {
201            return &(*it);
202        }
203        return nullptr;
204    }
205
206    bool ReadDebugInfo(VmaEntry *vma)
207    {
208        if (vma->status == VmaEntry::VALID) {
209            return true;
210        }
211        if (vma->status == VmaEntry::BAD) {
212            return false;
213        }
214        if (!vma->filename.empty() && vma->debug_info.ReadFromFile(vma->filename.c_str()) == DebugInfo::SUCCESS) {
215            vma->status = VmaEntry::VALID;
216            return true;
217        }
218        vma->status = VmaEntry::BAD;
219        return false;
220    }
221
222    void ScanVma()
223    {
224        static const int HEX_RADIX = 16;
225        static const size_t MODE_FIELD_LEN = 4;
226        static const size_t XMODE_POS = 2;
227
228        if (!vmas_.empty()) {
229            return;
230        }
231
232        std::stringstream fname;
233        fname << "/proc/self/maps";
234        std::string filename = fname.str();
235        std::ifstream maps(filename.c_str());
236
237        while (maps) {
238            std::string line;
239            std::getline(maps, line);
240            Tokenizer tokenizer(line);
241            std::string start_addr = tokenizer.Next('-');
242            std::string end_addr = tokenizer.Next();
243            std::string rights = tokenizer.Next();
244            if (rights.length() == MODE_FIELD_LEN && rights[XMODE_POS] == 'x') {
245                std::string offset = tokenizer.Next();
246                tokenizer.Next();
247                tokenizer.Next();
248                std::string obj_filename = tokenizer.Next();
249                vmas_.emplace_back(stoul(start_addr, nullptr, HEX_RADIX), stoul(end_addr, nullptr, HEX_RADIX),
250                                   stoul(offset, nullptr, HEX_RADIX), obj_filename);
251            }
252        }
253    }
254
255private:
256    std::vector<VmaEntry> vmas_;
257    os::memory::Mutex mutex_;
258};
259
260class Buf {
261public:
262    Buf(uintptr_t *buf, size_t skip, size_t capacity) : buf_(buf), skip_(skip), size_(0), capacity_(capacity) {}
263
264    void Append(uintptr_t pc)
265    {
266        if (skip_ > 0) {
267            // Skip the element
268            --skip_;
269            return;
270        }
271        if (size_ >= capacity_) {
272            return;
273        }
274        buf_[size_++] = pc;  // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
275    }
276
277    int Size() const
278    {
279        return size_;
280    }
281
282private:
283    uintptr_t *buf_;
284    size_t skip_;
285    size_t size_;
286    size_t capacity_;
287};
288
289static _Unwind_Reason_Code FrameHandler(struct _Unwind_Context *ctx, [[maybe_unused]] void *arg)
290{
291    Buf *buf = reinterpret_cast<Buf *>(arg);
292    uintptr_t pc = _Unwind_GetIP(ctx);
293    // _Unwind_GetIP returns 0 pc at the end of the stack. Ignore it
294    if (pc != 0) {
295        buf->Append(pc);
296    }
297    return _URC_NO_REASON;
298}
299
300std::vector<uintptr_t> GetStacktrace()
301{
302    static constexpr size_t BUF_SIZE = 100;
303    static constexpr int SKIP_FRAMES = 2;  // backtrace
304    std::vector<uintptr_t> buf;
305    buf.resize(BUF_SIZE);
306    Buf buf_wrapper(buf.data(), SKIP_FRAMES, buf.size());
307    _Unwind_Reason_Code res = _Unwind_Backtrace(FrameHandler, &buf_wrapper);
308    if (res != _URC_END_OF_STACK || buf_wrapper.Size() < 0) {
309        return std::vector<uintptr_t>();
310    }
311
312    buf.resize(buf_wrapper.Size());
313    return buf;
314}
315
316std::ostream &PrintStack(const std::vector<uintptr_t> &stacktrace, std::ostream &out)
317{
318    return StackPrinter::GetInstance().Print(stacktrace, out);
319}
320
321}  // namespace panda
322