File indexing completed on 2024-03-24 17:19:20

0001 /*
0002     SPDX-FileCopyrightText: 2014-2017 Milian Wolff <mail@milianw.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
0008 #include "3rdparty/doctest.h"
0009 
0010 #include "track/trace.h"
0011 #include "track/tracetree.h"
0012 
0013 #include "interpret/dwarfdiecache.h"
0014 
0015 #include <elfutils/libdwelf.h>
0016 
0017 #include <algorithm>
0018 #include <future>
0019 #include <thread>
0020 
0021 #include <link.h>
0022 
0023 using namespace std;
0024 
0025 namespace {
0026 bool __attribute__((noinline)) fill(Trace& trace, int depth, int skip)
0027 {
0028     if (!depth) {
0029         return trace.fill(skip);
0030     } else {
0031         return fill(trace, depth - 1, skip);
0032     }
0033 }
0034 
0035 bool bar(Trace& trace, int depth);
0036 
0037 inline bool __attribute__((always_inline)) asdf(Trace& trace, int depth)
0038 {
0039     return bar(trace, depth - 1);
0040 }
0041 
0042 inline bool __attribute__((always_inline)) foo(Trace& trace, int depth)
0043 {
0044     return asdf(trace, depth);
0045 }
0046 
0047 bool __attribute__((noinline)) bar(Trace& trace, int depth)
0048 {
0049     if (!depth) {
0050         return trace.fill(0);
0051     } else {
0052         return foo(trace, depth);
0053     }
0054 }
0055 
0056 void validateTrace(const Trace& trace, int expectedSize)
0057 {
0058     SUBCASE("validate the trace size")
0059     {
0060         REQUIRE(trace.size() == expectedSize);
0061         REQUIRE(distance(trace.begin(), trace.end()) == trace.size());
0062     }
0063     SUBCASE("validate trace contents")
0064     {
0065         REQUIRE(find(trace.begin(), trace.end(), Trace::ip_t(0)) == trace.end());
0066     }
0067 }
0068 }
0069 
0070 TEST_CASE ("getting backtrace traces") {
0071     Trace trace;
0072     validateTrace(trace, 0);
0073 
0074     SUBCASE("fill without skipping")
0075     {
0076         REQUIRE(trace.fill(0));
0077         const auto offset = trace.size();
0078         REQUIRE(offset > 1);
0079         validateTrace(trace, offset);
0080 
0081         SUBCASE("fill with skipping")
0082         {
0083             for (auto skip : {0, 1, 2}) {
0084                 for (int i = 0; i < 2 * Trace::MAX_SIZE; ++i) {
0085                     REQUIRE(fill(trace, i, skip));
0086                     const auto expectedSize = min(i + offset + 1 - skip, static_cast<int>(Trace::MAX_SIZE) - skip);
0087                     validateTrace(trace, expectedSize);
0088                 }
0089             }
0090         }
0091     }
0092 }
0093 
0094 TEST_CASE ("tracetree indexing") {
0095     TraceTree tree;
0096 
0097     std::mutex mutex;
0098     const auto numTasks = std::thread::hardware_concurrency();
0099     std::vector<std::future<void>> tasks(numTasks);
0100 
0101     struct IpToParent
0102     {
0103         uintptr_t ip;
0104         uint32_t parentIndex;
0105     };
0106     std::vector<IpToParent> ipsToParent;
0107 
0108     struct IndexedTrace
0109     {
0110         Trace trace;
0111         uint32_t index;
0112     };
0113     std::vector<IndexedTrace> traces;
0114 
0115     // fill the tree
0116     for (auto i = 0u; i < numTasks; ++i) {
0117         tasks[i] = std::async(std::launch::async, [&mutex, &tree, &ipsToParent, &traces, i]() {
0118             Trace trace;
0119 
0120             const auto leaf = uintptr_t((i + 1) * 100);
0121 
0122             for (int k = 0; k < 100; ++k) {
0123                 uint32_t lastIndex = 0;
0124                 for (uintptr_t j = 0; j < 32; ++j) {
0125                     trace.fillTestData(j, leaf);
0126                     REQUIRE(trace.size() == j + 1);
0127 
0128                     const std::lock_guard<std::mutex> guard(mutex);
0129                     uint32_t lastParent = 0;
0130                     auto index =
0131                         tree.index(trace, [k, j, leaf, &lastParent, &ipsToParent](uintptr_t ip, uint32_t parentIndex) {
0132                             // for larger k, the trace is known and thus we won't hit this branch
0133                             REQUIRE(k == 0);
0134 
0135                             REQUIRE(ip > 0);
0136                             REQUIRE((ip <= (j + 1) || ip == leaf));
0137                             REQUIRE(((!lastParent && !parentIndex) || parentIndex > lastParent));
0138                             REQUIRE(parentIndex <= ipsToParent.size());
0139                             lastParent = parentIndex;
0140 
0141                             ipsToParent.push_back({ip, parentIndex});
0142                             return true;
0143                         });
0144                     REQUIRE(index > lastIndex);
0145                     REQUIRE(index <= ipsToParent.size());
0146 
0147                     if (k == 0) {
0148                         traces.push_back({trace, index});
0149                     }
0150                 }
0151             }
0152         });
0153     }
0154 
0155     // wait for threads to finish
0156     for (auto& task : tasks) {
0157         task.get();
0158     }
0159 
0160     // verify that we can rebuild the traces
0161     for (const auto& trace : traces) {
0162         uint32_t index = trace.index;
0163         int i = 0;
0164         while (index) {
0165             REQUIRE(i < trace.trace.size());
0166             REQUIRE(index > 0);
0167             REQUIRE(index <= ipsToParent.size());
0168 
0169             auto map = ipsToParent[index - 1];
0170             REQUIRE(map.ip == reinterpret_cast<uintptr_t>(trace.trace[i]));
0171 
0172             index = map.parentIndex;
0173             ++i;
0174         }
0175     }
0176 }
0177 
0178 struct CallbackData
0179 {
0180     Dwfl* dwfl = nullptr;
0181     Dwfl_Module* mod = nullptr;
0182 };
0183 static int dl_iterate_phdr_dwfl_report_callback(struct dl_phdr_info* info, size_t /*size*/, void* data)
0184 {
0185     const char* fileName = info->dlpi_name;
0186     if (!fileName || !fileName[0]) {
0187         auto callbackData = reinterpret_cast<CallbackData*>(data);
0188         callbackData->mod = dwfl_report_elf(callbackData->dwfl, "tst_trace", "/proc/self/exe", -1, info->dlpi_addr, false);
0189         REQUIRE(callbackData->mod);
0190     }
0191 
0192     return 0;
0193 }
0194 
0195 TEST_CASE ("symbolizing") {
0196     Trace trace;
0197 
0198     REQUIRE(bar(trace, 5));
0199     REQUIRE(trace.size() >= 6);
0200 
0201     Dwfl_Callbacks callbacks = {
0202         &dwfl_build_id_find_elf,
0203         &dwfl_standard_find_debuginfo,
0204         &dwfl_offline_section_address,
0205         nullptr,
0206     };
0207 
0208     auto dwfl = std::unique_ptr<Dwfl, void (*)(Dwfl*)>(dwfl_begin(&callbacks), &dwfl_end);
0209     REQUIRE(dwfl);
0210 
0211     dwfl_report_begin(dwfl.get());
0212     CallbackData data = { dwfl.get(), nullptr };
0213     dl_iterate_phdr(&dl_iterate_phdr_dwfl_report_callback, &data);
0214     dwfl_report_end(dwfl.get(), nullptr, nullptr);
0215 
0216     REQUIRE(data.mod);
0217 
0218     DwarfDieCache cache(data.mod);
0219     uint j = 0;
0220     for (uint i = 0; i < 6 + j; ++i) {
0221         auto addr = reinterpret_cast<Dwarf_Addr>(trace[i]);
0222 
0223         auto cuDie = cache.findCuDie(addr);
0224         REQUIRE(cuDie);
0225 
0226         auto offset = addr - cuDie->bias();
0227         auto die = cuDie->findSubprogramDie(offset);
0228         REQUIRE(die);
0229 
0230         auto dieName = cuDie->dieName(die->die());
0231         auto isDebugBuild = i == 0 && dieName == "Trace::unwind(void**)";
0232         if (i == 0 + j) {
0233             if (!isDebugBuild)
0234                 REQUIRE(dieName == "Trace::fill(int)");
0235         } else {
0236             REQUIRE(dieName == "bar");
0237         }
0238 
0239         auto scopes = findInlineScopes(die->die(), offset);
0240         if (i <= 1 + j) {
0241             REQUIRE(scopes.size() == 0);
0242         } else {
0243             REQUIRE(scopes.size() == 2);
0244 
0245             Dwarf_Files* files = nullptr;
0246             dwarf_getsrcfiles(cuDie->cudie(), &files, nullptr);
0247             REQUIRE(files);
0248 
0249             REQUIRE(cuDie->dieName(&scopes[0]) == "foo");
0250             auto loc = callSourceLocation(&scopes[0], files, cuDie->cudie());
0251             // called from bar
0252             REQUIRE(loc.line == 52);
0253 
0254             REQUIRE(cuDie->dieName(&scopes[1]) == "asdf");
0255             loc = callSourceLocation(&scopes[1], files, cuDie->cudie());
0256             // called from foo
0257             REQUIRE(loc.line == 44);
0258         }
0259 
0260         if (isDebugBuild) {
0261             ++j;
0262         }
0263     }
0264 }