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 <fcntl.h>
17 #include <dlfcn.h>
18 #include <stdint.h>
19 #include <string.h>
20 #include <link.h>
21 #include <sys/mman.h>
22 #include <sys/mount.h>
23 #include <sys/stat.h>
24 #include <sys/types.h>
25 #include <sys/wait.h>
26 #include <unistd.h>
27
28 #include "bcc_elf.h"
29 #include "bcc_perf_map.h"
30 #include "bcc_proc.h"
31 #include "bcc_syms.h"
32 #include "common.h"
33 #include "vendor/tinyformat.hpp"
34
35 #include "catch.hpp"
36
37 using namespace std;
38
39 static pid_t spawn_child(void *, bool, bool, int (*)(void *));
40
41 TEST_CASE("language detection", "[c_api]") {
42 const char *c = bcc_procutils_language(getpid());
43 REQUIRE(c);
44 REQUIRE(string(c).compare("c") == 0);
45 }
46
47 TEST_CASE("shared object resolution", "[c_api]") {
48 char *libm = bcc_procutils_which_so("m", 0);
49 REQUIRE(libm);
50 REQUIRE(libm[0] == '/');
51 REQUIRE(string(libm).find("libm.so") != string::npos);
52 free(libm);
53 }
54
55 TEST_CASE("shared object resolution using loaded libraries", "[c_api]") {
56 char *libelf = bcc_procutils_which_so("elf", getpid());
57 REQUIRE(libelf);
58 REQUIRE(libelf[0] == '/');
59 REQUIRE(string(libelf).find("libelf") != string::npos);
60 free(libelf);
61 }
62
63 TEST_CASE("binary resolution with `which`", "[c_api]") {
64 char *ld = bcc_procutils_which("ld");
65 REQUIRE(ld);
66 REQUIRE(ld[0] == '/');
67 free(ld);
68 }
69
_test_ksym(const char * sym,uint64_t addr,void * _)70 static void _test_ksym(const char *sym, uint64_t addr, void *_) {
71 if (!strcmp(sym, "startup_64"))
72 REQUIRE(addr != 0x0ull);
73 }
74
75 TEST_CASE("list all kernel symbols", "[c_api]") {
76 if (geteuid() != 0)
77 return;
78 bcc_procutils_each_ksym(_test_ksym, NULL);
79 }
80
81 TEST_CASE("file-backed mapping identification") {
82 CHECK(bcc_mapping_is_file_backed("/bin/ls") == 1);
83 CHECK(bcc_mapping_is_file_backed("") == 0);
84 CHECK(bcc_mapping_is_file_backed("//anon") == 0);
85 CHECK(bcc_mapping_is_file_backed("/dev/zero") == 0);
86 CHECK(bcc_mapping_is_file_backed("/anon_hugepage") == 0);
87 CHECK(bcc_mapping_is_file_backed("/anon_hugepage (deleted)") == 0);
88 CHECK(bcc_mapping_is_file_backed("[stack") == 0);
89 CHECK(bcc_mapping_is_file_backed("/SYSV") == 0);
90 CHECK(bcc_mapping_is_file_backed("[heap]") == 0);
91 }
92
93 TEST_CASE("resolve symbol name in external library", "[c_api]") {
94 struct bcc_symbol sym;
95
96 REQUIRE(bcc_resolve_symname("c", "malloc", 0x0, 0, nullptr, &sym) == 0);
97 REQUIRE(string(sym.module).find("libc.so") != string::npos);
98 REQUIRE(sym.module[0] == '/');
99 REQUIRE(sym.offset != 0);
100 bcc_procutils_free(sym.module);
101 }
102
103 TEST_CASE("resolve symbol name in external library using loaded libraries", "[c_api]") {
104 struct bcc_symbol sym;
105
106 REQUIRE(bcc_resolve_symname("bcc", "bcc_procutils_which", 0x0, getpid(), nullptr, &sym) == 0);
107 REQUIRE(string(sym.module).find("libbcc.so") != string::npos);
108 REQUIRE(sym.module[0] == '/');
109 REQUIRE(sym.offset != 0);
110 bcc_procutils_free(sym.module);
111 }
112
_a_test_function(const char * a_string)113 extern "C" int _a_test_function(const char *a_string) {
114 int i;
115 for (i = 0; a_string[i]; ++i)
116 ;
117 return i;
118 }
119
setup_tmp_mnts(void)120 static int setup_tmp_mnts(void) {
121 // Disconnect this mount namespace from its parent
122 if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0) {
123 fprintf(stderr, "unable to mark / PRIVATE: %s\n", strerror(errno));
124 return -1;
125 }
126 // create a new tmpfs mounted on /tmp
127 if (mount("tmpfs", "/tmp", "tmpfs", 0, NULL) < 0) {
128 fprintf(stderr, "unable to mount /tmp in mntns: %s\n", strerror(errno));
129 return -1;
130 }
131
132 return 0;
133 }
134
mntns_func(void * arg)135 static int mntns_func(void *arg) {
136 int in_fd, out_fd;
137 char buf[4096];
138 char libpath[1024];
139 ssize_t rb;
140 void *dlhdl;
141 struct link_map *lm;
142
143 if (setup_tmp_mnts() < 0) {
144 return -1;
145 }
146
147 // Find libz.so.1, if it's installed
148 dlhdl = dlopen("libz.so.1", RTLD_LAZY);
149 if (dlhdl == NULL) {
150 fprintf(stderr, "Unable to dlopen libz.so.1: %s\n", dlerror());
151 return -1;
152 }
153
154 if (dlinfo(dlhdl, RTLD_DI_LINKMAP, &lm) < 0) {
155 fprintf(stderr, "Unable to find origin of libz.so.1: %s\n", dlerror());
156 return -1;
157 }
158
159 strncpy(libpath, lm->l_name, 1024);
160 dlclose(dlhdl);
161 dlhdl = NULL;
162
163 // Copy a shared library from shared mntns to private /tmp
164 snprintf(buf, 4096, "%s", libpath);
165 in_fd = open(buf, O_RDONLY);
166 if (in_fd < 0) {
167 fprintf(stderr, "Unable to open %s: %s\n", buf, strerror(errno));
168 return -1;
169 }
170
171 out_fd = open("/tmp/libz.so.1", O_RDWR|O_CREAT|O_EXCL,
172 S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
173 if (out_fd < 0) {
174 fprintf(stderr, "Unable to open /tmp/libz.so.1: %s\n", strerror(errno));
175 return -1;
176 }
177 memset(buf, 0, sizeof (buf));
178 while ((rb = read(in_fd, buf, sizeof (buf))) > 0) {
179 if (write(out_fd, buf, rb) < 0) {
180 fprintf(stderr, "Write error: %s\n", strerror(errno));
181 return -1;
182 }
183 }
184 close(in_fd);
185 close(out_fd);
186
187 dlhdl = dlopen("/tmp/libz.so.1", RTLD_NOW);
188 if (dlhdl == NULL) {
189 fprintf(stderr, "dlopen error: %s\n", dlerror());
190 return -1;
191 }
192
193 sleep(5);
194 dlclose(dlhdl);
195
196 return 0;
197 }
198
199 extern int cmd_scanf(const char *cmd, const char *fmt, ...);
200
201 TEST_CASE("resolve symbol addresses for a given PID", "[c_api]") {
202 struct bcc_symbol sym;
203 void *resolver = bcc_symcache_new(getpid(), nullptr);
204
205 REQUIRE(resolver);
206
207 SECTION("resolve in our own binary memory space") {
208 REQUIRE(bcc_symcache_resolve(resolver, (uint64_t)&_a_test_function, &sym) ==
209 0);
210
211 char *this_exe = realpath("/proc/self/exe", NULL);
212 REQUIRE(string(this_exe) == sym.module);
213 free(this_exe);
214
215 REQUIRE(string("_a_test_function") == sym.name);
216 }
217
218 SECTION("resolve in libbcc.so") {
219 void *libbcc = dlopen("libbcc.so", RTLD_LAZY | RTLD_NOLOAD);
220 REQUIRE(libbcc);
221
222 void *libbcc_fptr = dlsym(libbcc, "bcc_resolve_symname");
223 REQUIRE(libbcc_fptr);
224
225 REQUIRE(bcc_symcache_resolve(resolver, (uint64_t)libbcc_fptr, &sym) == 0);
226 REQUIRE(string(sym.module).find("libbcc.so") != string::npos);
227 REQUIRE(string("bcc_resolve_symname") == sym.name);
228 }
229
230 SECTION("resolve in libc") {
231 void *libc_fptr = dlsym(NULL, "strtok");
232 REQUIRE(libc_fptr);
233
234 REQUIRE(bcc_symcache_resolve(resolver, (uint64_t)libc_fptr, &sym) == 0);
235 REQUIRE(sym.module);
236 REQUIRE(sym.module[0] == '/');
237 REQUIRE(string(sym.module).find("libc") != string::npos);
238
239 // In some cases, a symbol may have multiple aliases. Since
240 // bcc_symcache_resolve() returns only the first alias of a
241 // symbol, this may not always be "strtok" even if it points
242 // to the same address.
243 bool sym_match = (string("strtok") == sym.name);
244 if (!sym_match) {
245 uint64_t exp_addr, sym_addr;
246 char cmd[256];
247 const char *cmdfmt = "nm %s | grep \" %s$\" | cut -f 1 -d \" \"";
248
249 // Find address of symbol by the expected name
250 sprintf(cmd, cmdfmt, sym.module, "strtok");
251 REQUIRE(cmd_scanf(cmd, "%lx", &exp_addr) == 0);
252
253 // Find address of symbol by the name that was
254 // returned by bcc_symcache_resolve()
255 sprintf(cmd, cmdfmt, sym.module, sym.name);
256 REQUIRE(cmd_scanf(cmd, "%lx", &sym_addr) == 0);
257
258 // If both addresses match, they are definitely
259 // aliases of the same symbol
260 sym_match = (exp_addr == sym_addr);
261 }
262
263 REQUIRE(sym_match);
264 }
265
266 SECTION("resolve in separate mount namespace") {
267 pid_t child;
268 uint64_t addr = 0;
269
270 child = spawn_child(0, true, true, mntns_func);
271 REQUIRE(child > 0);
272
273 void *resolver = bcc_symcache_new(child, nullptr);
274 REQUIRE(resolver);
275
276 REQUIRE(bcc_symcache_resolve_name(resolver, "/tmp/libz.so.1", "zlibVersion",
277 &addr) == 0);
278 REQUIRE(addr != 0);
279 }
280 }
281
282 #define STACK_SIZE (1024 * 1024)
283 static char child_stack[STACK_SIZE];
284
perf_map_path(pid_t pid)285 static string perf_map_path(pid_t pid) {
286 return tfm::format("/tmp/perf-%d.map", pid);
287 }
288
make_perf_map_file(string & path,unsigned long long map_addr)289 static int make_perf_map_file(string &path, unsigned long long map_addr) {
290 FILE *file = fopen(path.c_str(), "w");
291 if (file == NULL) {
292 return -1;
293 }
294 fprintf(file, "%llx 10 dummy_fn\n", map_addr);
295 fprintf(file, "%llx 10 right_next_door_fn\n", map_addr + 0x10);
296 fclose(file);
297
298 return 0;
299 }
300
perf_map_func(void * arg)301 static int perf_map_func(void *arg) {
302 string path = perf_map_path(getpid());
303 if (make_perf_map_file(path, (unsigned long long)arg) < 0)
304 return -1;
305
306 sleep(5);
307
308 unlink(path.c_str());
309 return 0;
310 }
311
perf_map_func_mntns(void * arg)312 static int perf_map_func_mntns(void *arg) {
313 string path = perf_map_path(getpid());
314
315 if (setup_tmp_mnts() < 0) {
316 return -1;
317 }
318
319 if (make_perf_map_file(path, (unsigned long long)arg) < 0)
320 return -1;
321
322 sleep(5);
323
324 unlink(path.c_str());
325 return 0;
326 }
327
perf_map_func_noop(void * arg)328 static int perf_map_func_noop(void *arg) {
329 if (setup_tmp_mnts() < 0) {
330 return -1;
331 }
332
333 sleep(5);
334
335 return 0;
336 }
337
spawn_child(void * map_addr,bool own_pidns,bool own_mntns,int (* child_func)(void *))338 static pid_t spawn_child(void *map_addr, bool own_pidns, bool own_mntns,
339 int (*child_func)(void *)) {
340 int flags = SIGCHLD;
341 if (own_pidns)
342 flags |= CLONE_NEWPID;
343 if (own_mntns)
344 flags |= CLONE_NEWNS;
345
346 pid_t child = clone(child_func,
347 /* stack grows down */ child_stack + STACK_SIZE, flags, (void*)map_addr);
348 if (child < 0)
349 return -1;
350
351 sleep(1); // let the child get set up
352 return child;
353 }
354
355 TEST_CASE("resolve symbols using /tmp/perf-pid.map", "[c_api]") {
356 const int map_sz = 4096;
357 void *map_addr = mmap(NULL, map_sz, PROT_READ | PROT_EXEC,
358 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
359 REQUIRE(map_addr != MAP_FAILED);
360
361 struct bcc_symbol sym;
362 pid_t child = -1;
363
364 SECTION("same namespace") {
365 child = spawn_child(map_addr, /* own_pidns */ false, false, perf_map_func);
366 REQUIRE(child > 0);
367
368 void *resolver = bcc_symcache_new(child, nullptr);
369 REQUIRE(resolver);
370
371 REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr,
372 &sym) == 0);
373 REQUIRE(sym.module);
374 REQUIRE(string(sym.module) == perf_map_path(child));
375 REQUIRE(string("dummy_fn") == sym.name);
376
377 REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr + 0x10,
378 &sym) == 0);
379 REQUIRE(sym.module);
380 REQUIRE(string(sym.module) == perf_map_path(child));
381 REQUIRE(string("right_next_door_fn") == sym.name);
382 }
383
384 SECTION("separate namespace") {
385 child = spawn_child(map_addr, /* own_pidns */ true, false, perf_map_func);
386 REQUIRE(child > 0);
387
388 void *resolver = bcc_symcache_new(child, nullptr);
389 REQUIRE(resolver);
390
391 REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr,
392 &sym) == 0);
393 REQUIRE(sym.module);
394 // child is PID 1 in its namespace
395 REQUIRE(string(sym.module) == perf_map_path(1));
396 REQUIRE(string("dummy_fn") == sym.name);
397 unlink("/tmp/perf-1.map");
398 }
399
400 SECTION("separate pid and mount namespace") {
401 child = spawn_child(map_addr, /* own_pidns */ true, true,
402 perf_map_func_mntns);
403 REQUIRE(child > 0);
404
405 void *resolver = bcc_symcache_new(child, nullptr);
406 REQUIRE(resolver);
407
408 REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr,
409 &sym) == 0);
410 REQUIRE(sym.module);
411 // child is PID 1 in its namespace
412 REQUIRE(string(sym.module) == perf_map_path(1));
413 REQUIRE(string("dummy_fn") == sym.name);
414 }
415
416 SECTION("separate pid and mount namespace, perf-map in host") {
417 child = spawn_child(map_addr, /* own_pidns */ true, true,
418 perf_map_func_noop);
419 REQUIRE(child > 0);
420
421 string path = perf_map_path(child);
422 REQUIRE(make_perf_map_file(path, (unsigned long long)map_addr) == 0);
423
424 void *resolver = bcc_symcache_new(child, nullptr);
425 REQUIRE(resolver);
426
427 REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr,
428 &sym) == 0);
429 REQUIRE(sym.module);
430 // child is PID 1 in its namespace
431 REQUIRE(string(sym.module) == perf_map_path(child));
432 REQUIRE(string("dummy_fn") == sym.name);
433
434 unlink(path.c_str());
435 }
436
437
438
439 munmap(map_addr, map_sz);
440 }
441
442
443 TEST_CASE("get online CPUs", "[c_api]") {
444 std::vector<int> cpus = ebpf::get_online_cpus();
445 int num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
446 REQUIRE(cpus.size() == num_cpus);
447 }
448