1 /*
2 * Copyright (c) 2016 GitHub, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #include <signal.h>
17 #include <sys/types.h>
18 #include <sys/wait.h>
19 #include <unistd.h>
20
21 #include "catch.hpp"
22 #include "usdt.h"
23 #include "api/BPF.h"
24
25 #ifdef HAVE_SDT_HEADER
26 /* required to insert USDT probes on this very executable --
27 * we're gonna be testing them live! */
28 #include <sys/sdt.h>
29
a_probed_function()30 static int a_probed_function() {
31 int an_int = 23 + getpid();
32 void *a_pointer = malloc(4);
33 DTRACE_PROBE2(libbcc_test, sample_probe_1, an_int, a_pointer);
34 free(a_pointer);
35 return an_int;
36 }
37
38 TEST_CASE("test finding a probe in our own process", "[usdt]") {
39 USDT::Context ctx(getpid());
40 REQUIRE(ctx.num_probes() >= 1);
41
42 SECTION("our test probe") {
43 auto probe = ctx.get("sample_probe_1");
44 REQUIRE(probe);
45
46 REQUIRE(probe->in_shared_object(probe->bin_path()) == false);
47 REQUIRE(probe->name() == "sample_probe_1");
48 REQUIRE(probe->provider() == "libbcc_test");
49 REQUIRE(probe->bin_path().find("/test_libbcc") != std::string::npos);
50
51 REQUIRE(probe->num_locations() == 1);
52 REQUIRE(probe->num_arguments() == 2);
53 REQUIRE(probe->need_enable() == false);
54
55 REQUIRE(a_probed_function() != 0);
56 }
57 }
58
59 TEST_CASE("test fine a probe in our own binary with C++ API", "[usdt]") {
60 ebpf::BPF bpf;
61 ebpf::USDT u("/proc/self/exe", "libbcc_test", "sample_probe_1", "on_event");
62
63 auto res = bpf.init("int on_event() { return 0; }", {}, {u});
64 REQUIRE(res.code() == 0);
65
66 res = bpf.attach_usdt(u);
67 REQUIRE(res.code() == 0);
68
69 res = bpf.detach_usdt(u);
70 REQUIRE(res.code() == 0);
71 }
72
73 TEST_CASE("test fine a probe in our Process with C++ API", "[usdt]") {
74 ebpf::BPF bpf;
75 ebpf::USDT u(::getpid(), "libbcc_test", "sample_probe_1", "on_event");
76
77 auto res = bpf.init("int on_event() { return 0; }", {}, {u});
78 REQUIRE(res.code() == 0);
79
80 res = bpf.attach_usdt(u);
81 REQUIRE(res.code() == 0);
82
83 res = bpf.detach_usdt(u);
84 REQUIRE(res.code() == 0);
85 }
86 #endif // HAVE_SDT_HEADER
87
88 class ChildProcess {
89 pid_t pid_;
90
91 public:
ChildProcess(const char * name,char * const argv[])92 ChildProcess(const char *name, char *const argv[]) {
93 pid_ = fork();
94 if (pid_ == 0) {
95 execvp(name, argv);
96 exit(0);
97 }
98 if (spawned()) {
99 usleep(250000);
100 if (kill(pid_, 0) < 0)
101 pid_ = -1;
102 }
103 }
104
~ChildProcess()105 ~ChildProcess() {
106 if (spawned()) {
107 int status;
108 kill(pid_, SIGKILL);
109 if (waitpid(pid_, &status, 0) != pid_)
110 abort();
111 }
112 }
113
spawned() const114 bool spawned() const { return pid_ > 0; }
pid() const115 pid_t pid() const { return pid_; }
116 };
117
118 extern int cmd_scanf(const char *cmd, const char *fmt, ...);
119
probe_num_locations(const char * bin_path,const char * func_name)120 static int probe_num_locations(const char *bin_path, const char *func_name) {
121 int num_locations;
122 char cmd[512];
123 const char *cmdfmt = "readelf -n %s | grep -c \"Name: %s$\"";
124
125 sprintf(cmd, cmdfmt, bin_path, func_name);
126 if (cmd_scanf(cmd, "%d", &num_locations) != 0) {
127 return -1;
128 }
129
130 return num_locations;
131 }
132
probe_num_arguments(const char * bin_path,const char * func_name)133 static int probe_num_arguments(const char *bin_path, const char *func_name) {
134 int num_arguments;
135 char cmd[512];
136 const char *cmdfmt = "readelf -n %s | grep -m 1 -A 2 \" %s$\" | " \
137 "tail -1 | cut -d \" \" -f 6- | wc -w";
138
139 sprintf(cmd, cmdfmt, bin_path, func_name);
140 if (cmd_scanf(cmd, "%d", &num_arguments) != 0) {
141 return -1;
142 }
143
144 return num_arguments;
145 }
146
147 TEST_CASE("test listing all USDT probes in Ruby/MRI", "[usdt]") {
148 size_t mri_probe_count = 0;
149
150 SECTION("without a running Ruby process") {
151 USDT::Context ctx("ruby");
152
153 if (!ctx.loaded())
154 return;
155
156 REQUIRE(ctx.num_probes() > 10);
157 mri_probe_count = ctx.num_probes();
158
159 SECTION("GC static probe") {
160 auto name = "gc__mark__begin";
161 auto probe = ctx.get(name);
162 REQUIRE(probe);
163
164 REQUIRE(probe->in_shared_object(probe->bin_path()) == true);
165 REQUIRE(probe->name() == name);
166 REQUIRE(probe->provider() == "ruby");
167
168 auto bin_path = probe->bin_path();
169 bool bin_path_match =
170 (bin_path.find("/ruby") != std::string::npos) ||
171 (bin_path.find("/libruby") != std::string::npos);
172 REQUIRE(bin_path_match);
173
174 int exp_locations, exp_arguments;
175 exp_locations = probe_num_locations(bin_path.c_str(), name);
176 exp_arguments = probe_num_arguments(bin_path.c_str(), name);
177 REQUIRE(probe->num_locations() == exp_locations);
178 REQUIRE(probe->num_arguments() == exp_arguments);
179 REQUIRE(probe->need_enable() == true);
180 }
181
182 SECTION("object creation probe") {
183 auto name = "object__create";
184 auto probe = ctx.get(name);
185 REQUIRE(probe);
186
187 REQUIRE(probe->in_shared_object(probe->bin_path()) == true);
188 REQUIRE(probe->name() == name);
189 REQUIRE(probe->provider() == "ruby");
190
191 auto bin_path = probe->bin_path();
192 bool bin_path_match =
193 (bin_path.find("/ruby") != std::string::npos) ||
194 (bin_path.find("/libruby") != std::string::npos);
195 REQUIRE(bin_path_match);
196
197 int exp_locations, exp_arguments;
198 exp_locations = probe_num_locations(bin_path.c_str(), name);
199 exp_arguments = probe_num_arguments(bin_path.c_str(), name);
200 REQUIRE(probe->num_locations() == exp_locations);
201 REQUIRE(probe->num_arguments() == exp_arguments);
202 REQUIRE(probe->need_enable() == true);
203 }
204
205 SECTION("array creation probe") {
206 auto name = "array__create";
207 auto probe = ctx.get(name);
208 REQUIRE(probe);
209 REQUIRE(probe->name() == name);
210
211 auto bin_path = probe->bin_path().c_str();
212 int exp_locations, exp_arguments;
213 exp_locations = probe_num_locations(bin_path, name);
214 exp_arguments = probe_num_arguments(bin_path, name);
215 REQUIRE(probe->num_locations() == exp_locations);
216 REQUIRE(probe->num_arguments() == exp_arguments);
217 REQUIRE(probe->need_enable() == true);
218 }
219 }
220
221 SECTION("with a running Ruby process") {
222 static char _ruby[] = "ruby";
223 char *const argv[2] = {_ruby, NULL};
224
225 ChildProcess ruby(argv[0], argv);
226 if (!ruby.spawned())
227 return;
228
229 USDT::Context ctx(ruby.pid());
230 REQUIRE(ctx.num_probes() >= mri_probe_count);
231
232 SECTION("get probe in running process") {
233 auto name = "gc__mark__begin";
234 auto probe = ctx.get(name);
235 REQUIRE(probe);
236
237 REQUIRE(probe->in_shared_object(probe->bin_path()) == true);
238 REQUIRE(probe->name() == name);
239 REQUIRE(probe->provider() == "ruby");
240
241 auto bin_path = probe->bin_path();
242 bool bin_path_match =
243 (bin_path.find("/ruby") != std::string::npos) ||
244 (bin_path.find("/libruby") != std::string::npos);
245 REQUIRE(bin_path_match);
246
247 int exp_locations, exp_arguments;
248 exp_locations = probe_num_locations(bin_path.c_str(), name);
249 exp_arguments = probe_num_arguments(bin_path.c_str(), name);
250 REQUIRE(probe->num_locations() == exp_locations);
251 REQUIRE(probe->num_arguments() == exp_arguments);
252 REQUIRE(probe->need_enable() == true);
253 }
254 }
255 }
256