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,const char * mod,uint64_t addr,void * _)70 static void _test_ksym(const char *sym, const char *mod, 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_NAME) != 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 struct bcc_symbol lazy_sym;
204 static struct bcc_symbol_option lazy_opt{
205 .use_debug_file = 1,
206 .check_debug_file_crc = 1,
207 .lazy_symbolize = 1,
208 #if defined(__powerpc64__) && defined(_CALL_ELF) && _CALL_ELF == 2
209 .use_symbol_type = BCC_SYM_ALL_TYPES | (1 << STT_PPC64_ELFV2_SYM_LEP),
210 #else
211 .use_symbol_type = BCC_SYM_ALL_TYPES,
212 #endif
213 };
214 void *resolver = bcc_symcache_new(getpid(), nullptr);
215 void *lazy_resolver = bcc_symcache_new(getpid(), &lazy_opt);
216
217 REQUIRE(resolver);
218 REQUIRE(lazy_resolver);
219
220 SECTION("resolve in our own binary memory space") {
221 REQUIRE(bcc_symcache_resolve(resolver, (uint64_t)&_a_test_function, &sym) ==
222 0);
223
224 char *this_exe = realpath("/proc/self/exe", NULL);
225 REQUIRE(string(this_exe) == sym.module);
226 free(this_exe);
227
228 REQUIRE(string("_a_test_function") == sym.name);
229
230 REQUIRE(bcc_symcache_resolve(lazy_resolver, (uint64_t)&_a_test_function, &lazy_sym) ==
231 0);
232 REQUIRE(string(lazy_sym.name) == sym.name);
233 REQUIRE(string(lazy_sym.module) == sym.module);
234 }
235
236 SECTION("resolve in " LIBBCC_NAME) {
237 void *libbcc = dlopen(LIBBCC_NAME, RTLD_LAZY | RTLD_NOLOAD);
238 REQUIRE(libbcc);
239
240 void *libbcc_fptr = dlsym(libbcc, "bcc_resolve_symname");
241 REQUIRE(libbcc_fptr);
242
243 REQUIRE(bcc_symcache_resolve(resolver, (uint64_t)libbcc_fptr, &sym) == 0);
244 REQUIRE(string(sym.module).find(LIBBCC_NAME) != string::npos);
245 REQUIRE(string("bcc_resolve_symname") == sym.name);
246
247 REQUIRE(bcc_symcache_resolve(lazy_resolver, (uint64_t)libbcc_fptr, &lazy_sym) == 0);
248 REQUIRE(string(lazy_sym.module) == sym.module);
249 REQUIRE(string(lazy_sym.name) == sym.name);
250 }
251
252 SECTION("resolve in libc") {
253 void *libc_fptr = dlsym(NULL, "strtok");
254 REQUIRE(libc_fptr);
255
256 REQUIRE(bcc_symcache_resolve(resolver, (uint64_t)libc_fptr, &sym) == 0);
257 REQUIRE(sym.module);
258 REQUIRE(sym.module[0] == '/');
259 REQUIRE(string(sym.module).find("libc") != string::npos);
260
261 REQUIRE(bcc_symcache_resolve(lazy_resolver, (uint64_t)libc_fptr, &lazy_sym) == 0);
262 REQUIRE(string(lazy_sym.module) == sym.module);
263 REQUIRE(string(lazy_sym.name) == sym.name);
264
265 // In some cases, a symbol may have multiple aliases. Since
266 // bcc_symcache_resolve() returns only the first alias of a
267 // symbol, this may not always be "strtok" even if it points
268 // to the same address.
269 bool sym_match = (string("strtok") == sym.name);
270 if (!sym_match) {
271 uint64_t exp_addr, sym_addr;
272 char cmd[256];
273 const char *cmdfmt = "nm %s | grep \" %s$\" | cut -f 1 -d \" \"";
274
275 // Find address of symbol by the expected name
276 sprintf(cmd, cmdfmt, sym.module, "strtok");
277 REQUIRE(cmd_scanf(cmd, "%lx", &exp_addr) == 0);
278
279 // Find address of symbol by the name that was
280 // returned by bcc_symcache_resolve()
281 sprintf(cmd, cmdfmt, sym.module, sym.name);
282 REQUIRE(cmd_scanf(cmd, "%lx", &sym_addr) == 0);
283
284 // If both addresses match, they are definitely
285 // aliases of the same symbol
286 sym_match = (exp_addr == sym_addr);
287 }
288
289 REQUIRE(sym_match);
290 }
291
292 SECTION("resolve in separate mount namespace") {
293 pid_t child;
294 uint64_t addr = 0;
295 uint64_t lazy_addr = 0;
296
297 child = spawn_child(0, true, true, mntns_func);
298 REQUIRE(child > 0);
299
300 void *resolver = bcc_symcache_new(child, nullptr);
301 REQUIRE(resolver);
302
303 REQUIRE(bcc_symcache_resolve_name(resolver, "/tmp/libz.so.1", "zlibVersion",
304 &addr) == 0);
305 REQUIRE(addr != 0);
306
307 void *lazy_resolver = bcc_symcache_new(child, &lazy_opt);
308 REQUIRE(lazy_resolver);
309 REQUIRE(bcc_symcache_resolve_name(lazy_resolver, "/tmp/libz.so.1", "zlibVersion",
310 &lazy_addr) == 0);
311 REQUIRE(lazy_addr == addr);
312 }
313 }
314
315 #define STACK_SIZE (1024 * 1024)
316 static char child_stack[STACK_SIZE];
317
perf_map_path(pid_t pid)318 static string perf_map_path(pid_t pid) {
319 return tfm::format("/tmp/perf-%d.map", pid);
320 }
321
make_perf_map_file(string & path,unsigned long long map_addr)322 static int make_perf_map_file(string &path, unsigned long long map_addr) {
323 FILE *file = fopen(path.c_str(), "w");
324 if (file == NULL) {
325 return -1;
326 }
327 fprintf(file, "%llx 10 dummy_fn\n", map_addr);
328 fprintf(file, "%llx 10 right_next_door_fn\n", map_addr + 0x10);
329 fclose(file);
330
331 return 0;
332 }
333
perf_map_func(void * arg)334 static int perf_map_func(void *arg) {
335 string path = perf_map_path(getpid());
336 if (make_perf_map_file(path, (unsigned long long)arg) < 0)
337 return -1;
338
339 sleep(5);
340
341 unlink(path.c_str());
342 return 0;
343 }
344
perf_map_func_mntns(void * arg)345 static int perf_map_func_mntns(void *arg) {
346 string path = perf_map_path(getpid());
347
348 if (setup_tmp_mnts() < 0) {
349 return -1;
350 }
351
352 if (make_perf_map_file(path, (unsigned long long)arg) < 0)
353 return -1;
354
355 sleep(5);
356
357 unlink(path.c_str());
358 return 0;
359 }
360
perf_map_func_noop(void * arg)361 static int perf_map_func_noop(void *arg) {
362 if (setup_tmp_mnts() < 0) {
363 return -1;
364 }
365
366 sleep(5);
367
368 return 0;
369 }
370
spawn_child(void * map_addr,bool own_pidns,bool own_mntns,int (* child_func)(void *))371 static pid_t spawn_child(void *map_addr, bool own_pidns, bool own_mntns,
372 int (*child_func)(void *)) {
373 int flags = SIGCHLD;
374 if (own_pidns)
375 flags |= CLONE_NEWPID;
376 if (own_mntns)
377 flags |= CLONE_NEWNS;
378
379 pid_t child = clone(child_func,
380 /* stack grows down */ child_stack + STACK_SIZE, flags, (void*)map_addr);
381 if (child < 0)
382 return -1;
383
384 sleep(1); // let the child get set up
385 return child;
386 }
387
388 TEST_CASE("resolve symbols using /tmp/perf-pid.map", "[c_api]") {
389 const int map_sz = 4096;
390 void *map_addr = mmap(NULL, map_sz, PROT_READ | PROT_EXEC,
391 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
392 REQUIRE(map_addr != MAP_FAILED);
393
394 struct bcc_symbol sym;
395 pid_t child = -1;
396
397 SECTION("same namespace") {
398 child = spawn_child(map_addr, /* own_pidns */ false, false, perf_map_func);
399 REQUIRE(child > 0);
400
401 void *resolver = bcc_symcache_new(child, nullptr);
402 REQUIRE(resolver);
403
404 REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr,
405 &sym) == 0);
406 REQUIRE(sym.module);
407 REQUIRE(string(sym.module) == perf_map_path(child));
408 REQUIRE(string("dummy_fn") == sym.name);
409
410 REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr + 0x10,
411 &sym) == 0);
412 REQUIRE(sym.module);
413 REQUIRE(string(sym.module) == perf_map_path(child));
414 REQUIRE(string("right_next_door_fn") == sym.name);
415 }
416
417 SECTION("separate namespace") {
418 child = spawn_child(map_addr, /* own_pidns */ true, false, perf_map_func);
419 REQUIRE(child > 0);
420
421 void *resolver = bcc_symcache_new(child, nullptr);
422 REQUIRE(resolver);
423
424 REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr,
425 &sym) == 0);
426 REQUIRE(sym.module);
427 // child is PID 1 in its namespace
428 REQUIRE(string(sym.module) == perf_map_path(1));
429 REQUIRE(string("dummy_fn") == sym.name);
430 unlink("/tmp/perf-1.map");
431 }
432
433 SECTION("separate pid and mount namespace") {
434 child = spawn_child(map_addr, /* own_pidns */ true, true,
435 perf_map_func_mntns);
436 REQUIRE(child > 0);
437
438 void *resolver = bcc_symcache_new(child, nullptr);
439 REQUIRE(resolver);
440
441 REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr,
442 &sym) == 0);
443 REQUIRE(sym.module);
444 // child is PID 1 in its namespace
445 REQUIRE(string(sym.module) == perf_map_path(1));
446 REQUIRE(string("dummy_fn") == sym.name);
447 }
448
449 SECTION("separate pid and mount namespace, perf-map in host") {
450 child = spawn_child(map_addr, /* own_pidns */ true, true,
451 perf_map_func_noop);
452 REQUIRE(child > 0);
453
454 string path = perf_map_path(child);
455 REQUIRE(make_perf_map_file(path, (unsigned long long)map_addr) == 0);
456
457 void *resolver = bcc_symcache_new(child, nullptr);
458 REQUIRE(resolver);
459
460 REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr,
461 &sym) == 0);
462 REQUIRE(sym.module);
463 // child is PID 1 in its namespace
464 REQUIRE(string(sym.module) == perf_map_path(child));
465 REQUIRE(string("dummy_fn") == sym.name);
466
467 unlink(path.c_str());
468 }
469
470
471
472 munmap(map_addr, map_sz);
473 }
474
475 // must match exactly the defitinion of mod_search in bcc_syms.cc
476 struct mod_search {
477 const char *name;
478 uint64_t inode;
479 uint64_t dev_major;
480 uint64_t dev_minor;
481 uint64_t addr;
482 uint8_t inode_match_only;
483
484 uint64_t start;
485 uint64_t file_offset;
486 };
487
488 TEST_CASE("searching for modules in /proc/[pid]/maps", "[c_api][!mayfail]") {
489 FILE *dummy_maps = fopen("dummy_proc_map.txt", "r");
490 REQUIRE(dummy_maps != NULL);
491
492 SECTION("name match") {
493 fseek(dummy_maps, 0, SEEK_SET);
494
495 struct mod_search search;
496 memset(&search, 0, sizeof(struct mod_search));
497 search.name = "/some/other/path/tolibs/lib/libutil-2.26.so";
498 search.addr = 0x1;
499 int res = _procfs_maps_each_module(dummy_maps, 42, _bcc_syms_find_module,
500 &search);
501 REQUIRE(res == 0);
502 REQUIRE(search.start == 0x7f1515bad000);
503 }
504
505 SECTION("expected failure to match (name only search)") {
506 fseek(dummy_maps, 0, SEEK_SET);
507
508 struct mod_search search;
509 memset(&search, 0, sizeof(struct mod_search));
510 search.name = "/lib/that/isnt/in/maps/libdoesntexist.so";
511 search.addr = 0x1;
512 int res = _procfs_maps_each_module(dummy_maps, 42, _bcc_syms_find_module,
513 &search);
514 REQUIRE(res == -1);
515 }
516
517 SECTION("inode+dev match, names different") {
518 fseek(dummy_maps, 0, SEEK_SET);
519
520 struct mod_search search;
521 memset(&search, 0, sizeof(struct mod_search));
522 search.name = "/proc/5/root/some/other/path/tolibs/lib/libz.so.1.2.8";
523 search.inode = 72809538;
524 search.dev_major = 0x00;
525 search.dev_minor = 0x1b;
526 search.addr = 0x2;
527 int res = _procfs_maps_each_module(dummy_maps, 42, _bcc_syms_find_module,
528 &search);
529 REQUIRE(res == 0);
530 REQUIRE(search.start == 0x7f15164b5000);
531 }
532
533 SECTION("inode+dev don't match, names same") {
534 fseek(dummy_maps, 0, SEEK_SET);
535
536 struct mod_search search;
537 memset(&search, 0, sizeof(struct mod_search));
538 search.name = "/some/other/path/tolibs/lib/libutil-2.26.so";
539 search.inode = 9999999;
540 search.dev_major = 0x42;
541 search.dev_minor = 0x1b;
542 search.addr = 0x2;
543 int res = _procfs_maps_each_module(dummy_maps, 42, _bcc_syms_find_module,
544 &search);
545 REQUIRE(res == -1);
546 }
547
548 SECTION("inodes match, dev_major/minor don't, expected failure") {
549 fseek(dummy_maps, 0, SEEK_SET);
550
551 struct mod_search search;
552 memset(&search, 0, sizeof(struct mod_search));
553 search.name = "/some/other/path/tolibs/lib/libutil-2.26.so";
554 search.inode = 72809526;
555 search.dev_major = 0x11;
556 search.dev_minor = 0x11;
557 search.addr = 0x2;
558 int res = _procfs_maps_each_module(dummy_maps, 42, _bcc_syms_find_module,
559 &search);
560 REQUIRE(res == -1);
561 }
562
563 SECTION("inodes match, dev_major/minor don't, match inode only") {
564 fseek(dummy_maps, 0, SEEK_SET);
565
566 struct mod_search search;
567 memset(&search, 0, sizeof(struct mod_search));
568 search.name = "/some/other/path/tolibs/lib/libutil-2.26.so";
569 search.inode = 72809526;
570 search.dev_major = 0x11;
571 search.dev_minor = 0x11;
572 search.addr = 0x2;
573 search.inode_match_only = 1;
574 int res = _procfs_maps_each_module(dummy_maps, 42, _bcc_syms_find_module,
575 &search);
576 REQUIRE(res == 0);
577 REQUIRE(search.start == 0x7f1515bad000);
578 }
579
580 fclose(dummy_maps);
581 }
582
583 TEST_CASE("resolve global addr in libc in this process", "[c_api][!mayfail]") {
584 int pid = getpid();
585 char *sopath = bcc_procutils_which_so("c", pid);
586 uint64_t local_addr = 0x15;
587 uint64_t global_addr;
588
589 struct mod_search search;
590 memset(&search, 0, sizeof(struct mod_search));
591 search.name = sopath;
592
593 int res = bcc_procutils_each_module(pid, _bcc_syms_find_module,
594 &search);
595 REQUIRE(res == 0);
596 REQUIRE(search.start != 0);
597
598 res = bcc_resolve_global_addr(pid, sopath, local_addr, 0, &global_addr);
599 REQUIRE(res == 0);
600 REQUIRE(global_addr == (search.start + local_addr - search.file_offset));
601 }
602
603 TEST_CASE("get online CPUs", "[c_api]") {
604 std::vector<int> cpus = ebpf::get_online_cpus();
605 int num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
606 REQUIRE(cpus.size() == num_cpus);
607 }
608