/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2020 The TokTok team. */ #include #include #include #include #include #include #include #include #include #include #define LOG_TO_STDOUT 0 /** * This file contains the support code for using -finstrument-functions. * * If you link this into a program, e.g. a test program, that was compiled with * -finstrument-functions, it will print an indented execution trace. E.g.: * * @code * int g() { return 0; } * int f() { return g(); } * int main() { return f(); } * @endcode * * Compiling the above with instrumentation and linking it with this file will * result in the following output: * * @verbatim * -> main * -> f * -> g * <- g * <- f * <- main * @endverbatim * * There are a few filter options you can apply below. Note that you'll need * addr2line in your PATH for this to work. */ namespace { class CString { char const *str_; public: // cppcheck-suppress noExplicitConstructor constexpr CString(char const *str) : str_(str) {} friend bool operator<(CString lhs, CString rhs) { return strcmp(lhs.str_, rhs.str_) < 0; } }; template bool contains(CString const (&array)[N], std::string const &str) { return std::binary_search(array, array + N, str.c_str()); } struct Symbol { std::string const name; std::string const file; }; /** * This array lists functions that should *not* appear in the trace. */ constexpr CString exclusions[] = { "__bswap_32", "logger_callback_log", "make_family", "make_proto", "make_socktype", }; /** * This array lists source files that *should* appear in the trace. * * If this array is empty, all files are traced. */ constexpr CString filter_sources[] = { "onion.c", "onion_announce.c", "onion_client.c", }; class ExecutionTrace { using Process = std::unique_ptr; using File = std::unique_ptr; std::string const exe = []() { std::array result; ssize_t const count = readlink("/proc/self/exe", result.data(), result.size()); assert(count > 0); return count > 0 ? std::string(result.data(), count) : "/proc/self/exe"; }(); #if LOG_TO_STDOUT File const log_file{stdout, std::fclose}; #else File const log_file{std::fopen("trace.log", "w"), std::fclose}; #endif unsigned nesting = 0; std::map symbols; static std::string sh(std::string cmd) { std::string result; Process pipe(popen(cmd.c_str(), "r"), pclose); if (!pipe) { return ""; } std::array buffer; while (std::fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { result += buffer.data(); } return result; } Symbol const &resolve(void *fn) { // Already in the cache. auto const found = symbols.find(fn); if (found != symbols.end()) { return found->second; } // 0x<64 bit number>\0 std::array addr; std::snprintf(addr.data(), addr.size(), "0x%lx", static_cast(reinterpret_cast(fn))); std::string const output = sh("addr2line -fs -e " + exe + " " + addr.data()); std::size_t const newline = output.find_first_of('\n'); std::size_t const colon = output.find_first_of(':'); std::string const sym = output.substr(0, newline); std::string const file = output.substr(newline + 1, colon - (newline + 1)); auto const inserted = symbols.insert({fn, {sym, file}}); return inserted.first->second; } void indent() const { for (unsigned i = 0; i < nesting; i++) { std::fputc(' ', log_file.get()); } } void print(char const *prefix, Symbol const &sym) { indent(); std::fprintf(log_file.get(), "%s %s (%s)\n", prefix, sym.name.c_str(), sym.file.c_str()); } static bool excluded(Symbol const &sym) { if (sizeof(filter_sources) != 0) { if (!contains(filter_sources, sym.file)) { return true; } } return contains(exclusions, sym.name); } public: void enter(void *fn) { Symbol const &sym = resolve(fn); if (!excluded(sym)) { print("->", sym); ++nesting; } } void exit(void *fn) { Symbol const &sym = resolve(fn); if (!excluded(sym)) { --nesting; print("<-", sym); } } }; // We leak this one. static ExecutionTrace *trace; } // namespace extern "C" void __cyg_profile_func_enter(void *this_fn, void *call_site) __attribute__((__no_instrument_function__)); void __cyg_profile_func_enter(void *this_fn, void *call_site) { if (trace == nullptr) { trace = new ExecutionTrace(); } trace->enter(this_fn); } extern "C" void __cyg_profile_func_exit(void *this_fn, void *call_site) __attribute__((__no_instrument_function__)); void __cyg_profile_func_exit(void *this_fn, void *call_site) { assert(trace != nullptr); trace->exit(this_fn); }