diff options
Diffstat (limited to 'testing')
-rw-r--r-- | testing/BUILD.bazel | 10 | ||||
-rw-r--r-- | testing/trace.cc | 198 |
2 files changed, 206 insertions, 2 deletions
diff --git a/testing/BUILD.bazel b/testing/BUILD.bazel index 34f2d44c..5a2a0194 100644 --- a/testing/BUILD.bazel +++ b/testing/BUILD.bazel | |||
@@ -1,5 +1,4 @@ | |||
1 | load("@rules_cc//cc:defs.bzl", "cc_binary") | 1 | load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") |
2 | load("//tools:no_undefined.bzl", "cc_library") | ||
3 | 2 | ||
4 | cc_library( | 3 | cc_library( |
5 | name = "misc_tools", | 4 | name = "misc_tools", |
@@ -9,6 +8,13 @@ cc_library( | |||
9 | deps = ["//c-toxcore/toxcore"], | 8 | deps = ["//c-toxcore/toxcore"], |
10 | ) | 9 | ) |
11 | 10 | ||
11 | cc_library( | ||
12 | name = "trace", | ||
13 | srcs = ["trace.cc"], | ||
14 | visibility = ["//c-toxcore:__subpackages__"], | ||
15 | alwayslink = True, | ||
16 | ) | ||
17 | |||
12 | cc_binary( | 18 | cc_binary( |
13 | name = "DHT_test", | 19 | name = "DHT_test", |
14 | srcs = ["DHT_test.c"], | 20 | srcs = ["DHT_test.c"], |
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 | */ | ||
45 | namespace { | ||
46 | |||
47 | class 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 | |||
57 | template <std::size_t N> | ||
58 | bool contains(CString const (&array)[N], std::string const &str) { | ||
59 | return std::binary_search(array, array + N, str.c_str()); | ||
60 | } | ||
61 | |||
62 | struct 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 | */ | ||
70 | constexpr 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 | */ | ||
79 | constexpr CString filter_sources[] = { | ||
80 | "onion.c", | ||
81 | "onion_announce.c", | ||
82 | "onion_client.c", | ||
83 | }; | ||
84 | |||
85 | class 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. | ||
180 | static ExecutionTrace *trace; | ||
181 | |||
182 | } // namespace | ||
183 | |||
184 | extern "C" void __cyg_profile_func_enter(void *this_fn, void *call_site) | ||
185 | __attribute__((__no_instrument_function__)); | ||
186 | void __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 | |||
193 | extern "C" void __cyg_profile_func_exit(void *this_fn, void *call_site) | ||
194 | __attribute__((__no_instrument_function__)); | ||
195 | void __cyg_profile_func_exit(void *this_fn, void *call_site) { | ||
196 | assert(trace != nullptr); | ||
197 | trace->exit(this_fn); | ||
198 | } | ||