summaryrefslogtreecommitdiff
path: root/testing/trace.cc
diff options
context:
space:
mode:
Diffstat (limited to 'testing/trace.cc')
-rw-r--r--testing/trace.cc198
1 files changed, 198 insertions, 0 deletions
diff --git a/testing/trace.cc b/testing/trace.cc
new file mode 100644
index 00000000..ecd31726
--- /dev/null
+++ b/testing/trace.cc
@@ -0,0 +1,198 @@
1/* SPDX-License-Identifier: GPL-3.0-or-later
2 * Copyright © 2020 The TokTok team.
3 */
4#include <sys/param.h>
5#include <unistd.h>
6
7#include <algorithm>
8#include <array>
9#include <cassert>
10#include <cstdio>
11#include <cstring>
12#include <map>
13#include <memory>
14#include <string>
15
16#define LOG_TO_STDOUT 0
17
18/**
19 * This file contains the support code for using -finstrument-functions.
20 *
21 * If you link this into a program, e.g. a test program, that was compiled with
22 * -finstrument-functions, it will print an indented execution trace. E.g.:
23 *
24 * @code
25 * int g() { return 0; }
26 * int f() { return g(); }
27 * int main() { return f(); }
28 * @endcode
29 *
30 * Compiling the above with instrumentation and linking it with this file will
31 * result in the following output:
32 *
33 * @verbatim
34 * -> main
35 * -> f
36 * -> g
37 * <- g
38 * <- f
39 * <- main
40 * @endverbatim
41 *
42 * There are a few filter options you can apply below. Note that you'll need
43 * addr2line in your PATH for this to work.
44 */
45namespace {
46
47class CString {
48 char const *str_;
49
50 public:
51 // cppcheck-suppress noExplicitConstructor
52 constexpr CString(char const *str) : str_(str) {}
53
54 friend bool operator<(CString lhs, CString rhs) { return strcmp(lhs.str_, rhs.str_) < 0; }
55};
56
57template <std::size_t N>
58bool contains(CString const (&array)[N], std::string const &str) {
59 return std::binary_search(array, array + N, str.c_str());
60}
61
62struct Symbol {
63 std::string const name;
64 std::string const file;
65};
66
67/**
68 * This array lists functions that should *not* appear in the trace.
69 */
70constexpr CString exclusions[] = {
71 "__bswap_32", "logger_callback_log", "make_family", "make_proto", "make_socktype",
72};
73
74/**
75 * This array lists source files that *should* appear in the trace.
76 *
77 * If this array is empty, all files are traced.
78 */
79constexpr CString filter_sources[] = {
80 "onion.c",
81 "onion_announce.c",
82 "onion_client.c",
83};
84
85class ExecutionTrace {
86 using Process = std::unique_ptr<FILE, decltype(&pclose)>;
87 using File = std::unique_ptr<FILE, decltype(&fclose)>;
88
89 std::string const exe = []() {
90 std::array<char, PATH_MAX> result;
91 ssize_t const count = readlink("/proc/self/exe", result.data(), result.size());
92 assert(count > 0);
93 return count > 0 ? std::string(result.data(), count) : "/proc/self/exe";
94 }();
95
96#if LOG_TO_STDOUT
97 File const log_file{stdout, std::fclose};
98#else
99 File const log_file{std::fopen("trace.log", "w"), std::fclose};
100#endif
101 unsigned nesting = 0;
102 std::map<void *, Symbol> symbols;
103
104 static std::string sh(std::string cmd) {
105 std::string result;
106 Process pipe(popen(cmd.c_str(), "r"), pclose);
107 if (!pipe) {
108 return "<popen-failed>";
109 }
110 std::array<char, 128> buffer;
111 while (std::fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
112 result += buffer.data();
113 }
114 return result;
115 }
116
117 Symbol const &resolve(void *fn) {
118 // Already in the cache.
119 auto const found = symbols.find(fn);
120 if (found != symbols.end()) {
121 return found->second;
122 }
123
124 // 0x<64 bit number>\0
125 std::array<char, 16 + 3> addr;
126 std::snprintf(addr.data(), addr.size(), "0x%lx",
127 static_cast<unsigned long>(reinterpret_cast<uintptr_t>(fn)));
128
129 std::string const output = sh("addr2line -fs -e " + exe + " " + addr.data());
130
131 std::size_t const newline = output.find_first_of('\n');
132 std::size_t const colon = output.find_first_of(':');
133
134 std::string const sym = output.substr(0, newline);
135 std::string const file = output.substr(newline + 1, colon - (newline + 1));
136
137 auto const inserted = symbols.insert({fn, {sym, file}});
138 return inserted.first->second;
139 }
140
141 void indent() const {
142 for (unsigned i = 0; i < nesting; i++) {
143 std::fputc(' ', log_file.get());
144 }
145 }
146
147 void print(char const *prefix, Symbol const &sym) {
148 indent();
149 std::fprintf(log_file.get(), "%s %s (%s)\n", prefix, sym.name.c_str(), sym.file.c_str());
150 }
151
152 static bool excluded(Symbol const &sym) {
153 if (sizeof(filter_sources) != 0) {
154 if (!contains(filter_sources, sym.file)) {
155 return true;
156 }
157 }
158 return contains(exclusions, sym.name);
159 }
160
161 public:
162 void enter(void *fn) {
163 Symbol const &sym = resolve(fn);
164 if (!excluded(sym)) {
165 print("->", sym);
166 ++nesting;
167 }
168 }
169
170 void exit(void *fn) {
171 Symbol const &sym = resolve(fn);
172 if (!excluded(sym)) {
173 --nesting;
174 print("<-", sym);
175 }
176 }
177};
178
179// We leak this one.
180static ExecutionTrace *trace;
181
182} // namespace
183
184extern "C" void __cyg_profile_func_enter(void *this_fn, void *call_site)
185 __attribute__((__no_instrument_function__));
186void __cyg_profile_func_enter(void *this_fn, void *call_site) {
187 if (trace == nullptr) {
188 trace = new ExecutionTrace();
189 }
190 trace->enter(this_fn);
191}
192
193extern "C" void __cyg_profile_func_exit(void *this_fn, void *call_site)
194 __attribute__((__no_instrument_function__));
195void __cyg_profile_func_exit(void *this_fn, void *call_site) {
196 assert(trace != nullptr);
197 trace->exit(this_fn);
198}