xref: /aosp_15_r20/external/bcc/tests/cc/test_usdt_probes.cc (revision 387f9dfdfa2baef462e92476d413c7bc2470293e)
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 <linux/version.h>
17 #include <signal.h>
18 #include <sys/types.h>
19 #include <sys/wait.h>
20 #include <unistd.h>
21 
22 #include "catch.hpp"
23 #include "usdt.h"
24 #include "api/BPF.h"
25 
26 /* required to insert USDT probes on this very executable --
27  * we're gonna be testing them live! */
28 #include "folly/tracing/StaticTracepoint.h"
29 
a_probed_function()30 static int a_probed_function() {
31   int an_int = 23 + getpid();
32   void *a_pointer = malloc(4);
33   FOLLY_SDT(libbcc_test, sample_probe_1, an_int, a_pointer);
34   free(a_pointer);
35   return an_int;
36 }
37 
38 #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
FOLLY_SDT_DEFINE_SEMAPHORE(libbcc_test,sample_probe_2)39 FOLLY_SDT_DEFINE_SEMAPHORE(libbcc_test, sample_probe_2)
40 static int a_probed_function_with_sem() {
41   int an_int = 23 + getpid();
42   void *a_pointer = malloc(4);
43   FOLLY_SDT_WITH_SEMAPHORE(libbcc_test, sample_probe_2, an_int, a_pointer);
44   free(a_pointer);
45   return an_int;
46 }
47 #endif // linux version  >= 4.20
48 
49 extern "C" int lib_probed_function();
50 
call_shared_lib_func()51 int call_shared_lib_func() {
52   return lib_probed_function();
53 }
54 
55 TEST_CASE("test finding a probe in our own process", "[usdt]") {
56   USDT::Context ctx(getpid());
57   REQUIRE(ctx.num_probes() >= 1);
58 
59   SECTION("our test probe") {
60     auto probe = ctx.get("sample_probe_1");
61     REQUIRE(probe);
62 
63     if(probe->in_shared_object(probe->bin_path()))
64         return;
65     REQUIRE(probe->name() == "sample_probe_1");
66     REQUIRE(probe->provider() == "libbcc_test");
67     REQUIRE(probe->bin_path().find("/test_libbcc") != std::string::npos);
68 
69     REQUIRE(probe->num_locations() == 1);
70     REQUIRE(probe->num_arguments() == 2);
71     REQUIRE(probe->need_enable() == false);
72 
73     REQUIRE(a_probed_function() != 0);
74   }
75 }
76 
77 TEST_CASE("test probe's attributes with C++ API", "[usdt]") {
78     const ebpf::USDT u("/proc/self/exe", "libbcc_test", "sample_probe_1", "on_event");
79 
80     REQUIRE(u.binary_path() == "/proc/self/exe");
81     REQUIRE(u.pid() == -1);
82     REQUIRE(u.provider() == "libbcc_test");
83     REQUIRE(u.name() == "sample_probe_1");
84     REQUIRE(u.probe_func() == "on_event");
85 }
86 
87 TEST_CASE("test fine a probe in our own binary with C++ API", "[usdt]") {
88     ebpf::BPF bpf;
89     ebpf::USDT u("/proc/self/exe", "libbcc_test", "sample_probe_1", "on_event");
90 
91     auto res = bpf.init("int on_event() { return 0; }", {}, {u});
92     REQUIRE(res.ok());
93 
94     res = bpf.attach_usdt(u);
95     REQUIRE(res.ok());
96 
97     res = bpf.detach_usdt(u);
98     REQUIRE(res.ok());
99 }
100 
101 TEST_CASE("test fine probes in our own binary with C++ API", "[usdt]") {
102     ebpf::BPF bpf;
103     ebpf::USDT u("/proc/self/exe", "libbcc_test", "sample_probe_1", "on_event");
104 
105     auto res = bpf.init("int on_event() { return 0; }", {}, {u});
106     REQUIRE(res.ok());
107 
108     res = bpf.attach_usdt_all();
109     REQUIRE(res.ok());
110 
111     res = bpf.detach_usdt_all();
112     REQUIRE(res.ok());
113 }
114 
115 TEST_CASE("test fine a probe in our Process with C++ API", "[usdt]") {
116     ebpf::BPF bpf;
117     ebpf::USDT u(::getpid(), "libbcc_test", "sample_probe_1", "on_event");
118 
119     auto res = bpf.init("int on_event() { return 0; }", {}, {u});
120     REQUIRE(res.ok());
121 
122     res = bpf.attach_usdt(u);
123     REQUIRE(res.ok());
124 
125     res = bpf.detach_usdt(u);
126     REQUIRE(res.ok());
127 }
128 
129 TEST_CASE("test find a probe in our process' shared libs with c++ API", "[usdt]") {
130   ebpf::BPF bpf;
131   ebpf::USDT u(::getpid(), "libbcc_test", "sample_lib_probe_1", "on_event");
132 
133   auto res = bpf.init("int on_event() { return 0; }", {}, {u});
134   REQUIRE(res.msg() == "");
135   REQUIRE(res.ok());
136 }
137 
138 TEST_CASE("test usdt partial init w/ fail init_usdt", "[usdt]") {
139   ebpf::BPF bpf;
140   ebpf::USDT u(::getpid(), "libbcc_test", "sample_lib_probe_nonexistent", "on_event");
141   ebpf::USDT p(::getpid(), "libbcc_test", "sample_lib_probe_1", "on_event");
142 
143   // We should be able to fail initialization and subsequently do bpf.init w/o USDT
144   // successfully
145   auto res = bpf.init_usdt(u);
146   REQUIRE(res.msg() != "");
147   REQUIRE(!res.ok());
148 
149   // Shouldn't be necessary to re-init bpf object either after failure to init w/
150   // bad USDT
151   res = bpf.init("int on_event() { return 0; }", {}, {u});
152   REQUIRE(res.msg() != "");
153   REQUIRE(!res.ok());
154 
155   res = bpf.init_usdt(p);
156   REQUIRE(res.msg() == "");
157   REQUIRE(res.ok());
158 
159   res = bpf.init("int on_event() { return 0; }", {}, {});
160   REQUIRE(res.msg() == "");
161   REQUIRE(res.ok());
162 }
163 
164 class ChildProcess {
165   pid_t pid_;
166 
167 public:
ChildProcess(const char * name,char * const argv[])168   ChildProcess(const char *name, char *const argv[]) {
169     pid_ = fork();
170     if (pid_ == 0) {
171       execvp(name, argv);
172       exit(0);
173     }
174     if (spawned()) {
175       usleep(250000);
176       if (kill(pid_, 0) < 0)
177         pid_ = -1;
178     }
179   }
180 
~ChildProcess()181   ~ChildProcess() {
182     if (spawned()) {
183       int status;
184       kill(pid_, SIGKILL);
185       if (waitpid(pid_, &status, 0) != pid_)
186         abort();
187     }
188   }
189 
spawned() const190   bool spawned() const { return pid_ > 0; }
pid() const191   pid_t pid() const { return pid_; }
192 };
193 
194 extern int cmd_scanf(const char *cmd, const char *fmt, ...);
195 
probe_num_locations(const char * bin_path,const char * func_name)196 static int probe_num_locations(const char *bin_path, const char *func_name) {
197   int num_locations;
198   char cmd[512];
199   const char *cmdfmt = "readelf -n %s | grep -c \"Name: %s$\"";
200 
201   sprintf(cmd, cmdfmt, bin_path, func_name);
202   if (cmd_scanf(cmd, "%d", &num_locations) != 0) {
203     return -1;
204   }
205 
206   return num_locations;
207 }
208 
probe_num_arguments(const char * bin_path,const char * func_name)209 static int probe_num_arguments(const char *bin_path, const char *func_name) {
210   int num_arguments;
211   char cmd[512];
212   const char *cmdfmt = "readelf -n %s | grep -m 1 -A 2 \" %s$\" | " \
213                        "tail -1 | cut -d \" \" -f 6- | wc -w";
214 
215   sprintf(cmd, cmdfmt, bin_path, func_name);
216   if (cmd_scanf(cmd, "%d", &num_arguments) != 0) {
217     return -1;
218   }
219 
220   return num_arguments;
221 }
222 
223 // Unsharing pid namespace requires forking
224 // this uses pgrep to find the child process, by searching for a process
225 // that has the unshare as its parent
unshared_child_pid(const int ppid)226 static int unshared_child_pid(const int ppid) {
227   int child_pid;
228   char cmd[512];
229   const char *cmdfmt = "pgrep -P %d";
230 
231   sprintf(cmd, cmdfmt, ppid);
232   if (cmd_scanf(cmd, "%d", &child_pid) != 0) {
233     return -1;
234   }
235   return child_pid;
236 }
237 
238 // FIXME This seems like a legitimate bug with probing ruby where the
239 // ruby symbols are in libruby.so?
240 TEST_CASE("test listing all USDT probes in Ruby/MRI", "[usdt][!mayfail]") {
241   size_t mri_probe_count = 0;
242 
243   SECTION("without a running Ruby process") {
244     USDT::Context ctx("ruby");
245 
246     if (!ctx.loaded())
247       return;
248 
249     REQUIRE(ctx.num_probes() > 10);
250     mri_probe_count = ctx.num_probes();
251 
252     SECTION("GC static probe") {
253       auto name = "gc__mark__begin";
254       auto probe = ctx.get(name);
255       REQUIRE(probe);
256 
257       REQUIRE(probe->in_shared_object(probe->bin_path()) == true);
258       REQUIRE(probe->name() == name);
259       REQUIRE(probe->provider() == "ruby");
260 
261       auto bin_path = probe->bin_path();
262       bool bin_path_match =
263             (bin_path.find("/ruby") != std::string::npos) ||
264             (bin_path.find("/libruby") != std::string::npos);
265       REQUIRE(bin_path_match);
266 
267       int exp_locations, exp_arguments;
268       exp_locations = probe_num_locations(bin_path.c_str(), name);
269       exp_arguments = probe_num_arguments(bin_path.c_str(), name);
270       REQUIRE(probe->num_locations() == exp_locations);
271       REQUIRE(probe->num_arguments() == exp_arguments);
272       REQUIRE(probe->need_enable() == true);
273     }
274 
275     SECTION("object creation probe") {
276       auto name = "object__create";
277       auto probe = ctx.get(name);
278       REQUIRE(probe);
279 
280       REQUIRE(probe->in_shared_object(probe->bin_path()) == true);
281       REQUIRE(probe->name() == name);
282       REQUIRE(probe->provider() == "ruby");
283 
284       auto bin_path = probe->bin_path();
285       bool bin_path_match =
286             (bin_path.find("/ruby") != std::string::npos) ||
287             (bin_path.find("/libruby") != std::string::npos);
288       REQUIRE(bin_path_match);
289 
290       int exp_locations, exp_arguments;
291       exp_locations = probe_num_locations(bin_path.c_str(), name);
292       exp_arguments = probe_num_arguments(bin_path.c_str(), name);
293       REQUIRE(probe->num_locations() == exp_locations);
294       REQUIRE(probe->num_arguments() == exp_arguments);
295       REQUIRE(probe->need_enable() == true);
296     }
297 
298     SECTION("array creation probe") {
299       auto name = "array__create";
300       auto probe = ctx.get(name);
301       REQUIRE(probe);
302       REQUIRE(probe->name() == name);
303 
304       auto bin_path = probe->bin_path().c_str();
305       int exp_locations, exp_arguments;
306       exp_locations = probe_num_locations(bin_path, name);
307       exp_arguments = probe_num_arguments(bin_path, name);
308       REQUIRE(probe->num_locations() == exp_locations);
309       REQUIRE(probe->num_arguments() == exp_arguments);
310       REQUIRE(probe->need_enable() == true);
311     }
312   }
313 
314   SECTION("with a running Ruby process") {
315     static char _ruby[] = "ruby";
316     char *const argv[2] = {_ruby, NULL};
317 
318     ChildProcess ruby(argv[0], argv);
319     if (!ruby.spawned())
320       return;
321 
322     USDT::Context ctx(ruby.pid());
323     REQUIRE(ctx.num_probes() >= mri_probe_count);
324 
325     SECTION("get probe in running process") {
326       auto name = "gc__mark__begin";
327       auto probe = ctx.get(name);
328       REQUIRE(probe);
329 
330       REQUIRE(probe->in_shared_object(probe->bin_path()) == true);
331       REQUIRE(probe->name() == name);
332       REQUIRE(probe->provider() == "ruby");
333 
334       auto bin_path = probe->bin_path();
335       bool bin_path_match =
336             (bin_path.find("/ruby") != std::string::npos) ||
337             (bin_path.find("/libruby") != std::string::npos);
338       REQUIRE(bin_path_match);
339 
340       int exp_locations, exp_arguments;
341       exp_locations = probe_num_locations(bin_path.c_str(), name);
342       exp_arguments = probe_num_arguments(bin_path.c_str(), name);
343       REQUIRE(probe->num_locations() == exp_locations);
344       REQUIRE(probe->num_arguments() == exp_arguments);
345       REQUIRE(probe->need_enable() == true);
346     }
347   }
348 }
349 
350 // These tests are expected to fail if there is no Ruby with dtrace probes
351 TEST_CASE("test probing running Ruby process in namespaces",
352           "[usdt][!mayfail]") {
353   SECTION("in separate mount namespace") {
354     static char _unshare[] = "unshare";
355     const char *const argv[4] = {_unshare, "--mount", "ruby", NULL};
356 
357     ChildProcess unshare(argv[0], (char **const)argv);
358     if (!unshare.spawned())
359       return;
360     int ruby_pid = unshare.pid();
361 
362     ebpf::BPF bpf;
363     ebpf::USDT u(ruby_pid, "ruby", "gc__mark__begin", "on_event");
364     u.set_probe_matching_kludge(1);  // Also required for overlayfs...
365 
366     auto res = bpf.init("int on_event() { return 0; }", {}, {u});
367     REQUIRE(res.msg() == "");
368     REQUIRE(res.ok());
369 
370     res = bpf.attach_usdt(u, ruby_pid);
371     REQUIRE(res.ok());
372 
373     res = bpf.detach_usdt(u, ruby_pid);
374     REQUIRE(res.ok());
375   }
376 
377   SECTION("in separate mount namespace and separate PID namespace") {
378     static char _unshare[] = "unshare";
379     const char *const argv[8] = {_unshare,  "--fork",
380                                  "--mount", "--pid",  "--mount-proc",
381                                  "ruby",    NULL};
382 
383     ChildProcess unshare(argv[0], (char **const)argv);
384     if (!unshare.spawned())
385       return;
386     int ruby_pid = unshared_child_pid(unshare.pid());
387 
388     ebpf::BPF bpf;
389     ebpf::USDT u(ruby_pid, "ruby", "gc__mark__begin", "on_event");
390     u.set_probe_matching_kludge(1);  // Also required for overlayfs...
391 
392     auto res = bpf.init("int on_event() { return 0; }", {}, {u});
393     REQUIRE(res.msg() == "");
394     REQUIRE(res.ok());
395 
396     res = bpf.attach_usdt(u, ruby_pid);
397     REQUIRE(res.ok());
398 
399     res = bpf.detach_usdt(u, ruby_pid);
400     REQUIRE(res.ok());
401 
402     struct bcc_symbol sym;
403     std::string pid_root= "/proc/" + std::to_string(ruby_pid) + "/root/";
404     std::string module = pid_root + "usr/local/bin/ruby";
405     REQUIRE(bcc_resolve_symname(module.c_str(), "rb_gc_mark", 0x0, ruby_pid, nullptr, &sym) == 0);
406     REQUIRE(std::string(sym.module).find(pid_root, 1) == std::string::npos);
407     kill(ruby_pid, SIGKILL);
408     bcc_procutils_free(sym.module);
409   }
410 }
411 
412 #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
413 TEST_CASE("Test uprobe refcnt semaphore activation", "[usdt]") {
414     ebpf::BPF bpf;
415 
416     REQUIRE(!FOLLY_SDT_IS_ENABLED(libbcc_test, sample_probe_2));
417 
418     ebpf::USDT u("/proc/self/exe", "libbcc_test", "sample_probe_2", "on_event");
419 
420     auto res = bpf.init("int on_event() { return 0; }", {}, {u});
421     REQUIRE(res.ok());
422 
423     res = bpf.attach_usdt(u);
424     REQUIRE(res.ok());
425 
426     REQUIRE(FOLLY_SDT_IS_ENABLED(libbcc_test, sample_probe_2));
427 
428     res = bpf.detach_usdt(u);
429     REQUIRE(res.ok());
430 
431     REQUIRE(a_probed_function_with_sem() != 0);
432 }
433 #endif // linux version  >= 4.20
434