• 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 <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