• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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