xref: /aosp_15_r20/external/bcc/tests/cc/test_c_api.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 <dlfcn.h>
17 #include <fcntl.h>
18 #include <link.h>
19 #include <stdint.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <sys/mman.h>
23 #include <sys/mount.h>
24 #include <sys/stat.h>
25 #include <sys/types.h>
26 #include <sys/wait.h>
27 #include <unistd.h>
28 
29 #include <cstdlib>
30 
31 #include "bcc_elf.h"
32 #include "bcc_perf_map.h"
33 #include "bcc_proc.h"
34 #include "bcc_syms.h"
35 #include "catch.hpp"
36 #include "common.h"
37 #include "vendor/tinyformat.hpp"
38 
39 using namespace std;
40 
41 static pid_t spawn_child(void *, bool, bool, int (*)(void *));
42 
43 TEST_CASE("language detection", "[c_api]") {
44   const char *c = bcc_procutils_language(getpid());
45   REQUIRE(c);
46   REQUIRE(string(c).compare("c") == 0);
47 }
48 
49 TEST_CASE("shared object resolution", "[c_api]") {
50   char *libm = bcc_procutils_which_so("m", 0);
51   REQUIRE(libm);
52   REQUIRE(libm[0] == '/');
53   REQUIRE(string(libm).find("libm.so") != string::npos);
54   free(libm);
55 }
56 
57 TEST_CASE("shared object resolution using loaded libraries", "[c_api]") {
58   char *libelf = bcc_procutils_which_so("elf", getpid());
59   REQUIRE(libelf);
60   REQUIRE(libelf[0] == '/');
61   REQUIRE(string(libelf).find("libelf") != string::npos);
62   free(libelf);
63 }
64 
65 TEST_CASE("binary resolution with `which`", "[c_api]") {
66   char *ld = bcc_procutils_which("ld");
67   REQUIRE(ld);
68   REQUIRE(ld[0] == '/');
69   free(ld);
70 }
71 
_test_ksym(const char * sym,const char * mod,uint64_t addr,void * _)72 static void _test_ksym(const char *sym, const char *mod, uint64_t addr, void *_) {
73   if (!strcmp(sym, "startup_64"))
74     REQUIRE(addr != 0x0ull);
75 }
76 
77 TEST_CASE("list all kernel symbols", "[c_api]") {
78   if (geteuid() != 0)
79     return;
80   bcc_procutils_each_ksym(_test_ksym, NULL);
81 }
82 
83 TEST_CASE("file-backed mapping identification") {
84   CHECK(bcc_mapping_is_file_backed("/bin/ls") == 1);
85   CHECK(bcc_mapping_is_file_backed("") == 0);
86   CHECK(bcc_mapping_is_file_backed("//anon") == 0);
87   CHECK(bcc_mapping_is_file_backed("/dev/zero") == 0);
88   CHECK(bcc_mapping_is_file_backed("/anon_hugepage") == 0);
89   CHECK(bcc_mapping_is_file_backed("/anon_hugepage (deleted)") == 0);
90   CHECK(bcc_mapping_is_file_backed("[stack") == 0);
91   CHECK(bcc_mapping_is_file_backed("/SYSV") == 0);
92   CHECK(bcc_mapping_is_file_backed("[heap]") == 0);
93 }
94 
95 TEST_CASE("resolve symbol name in external library", "[c_api]") {
96   struct bcc_symbol sym;
97 
98   REQUIRE(bcc_resolve_symname("c", "malloc", 0x0, 0, nullptr, &sym) == 0);
99   REQUIRE(string(sym.module).find("libc.so") != string::npos);
100   REQUIRE(sym.module[0] == '/');
101   REQUIRE(sym.offset != 0);
102   bcc_procutils_free(sym.module);
103 }
104 
105 TEST_CASE("resolve symbol name in external library using loaded libraries", "[c_api]") {
106   struct bcc_symbol sym;
107 
108   REQUIRE(bcc_resolve_symname("bcc", "bcc_procutils_which", 0x0, getpid(), nullptr, &sym) == 0);
109   REQUIRE(string(sym.module).find(LIBBCC_NAME) != string::npos);
110   REQUIRE(sym.module[0] == '/');
111   REQUIRE(sym.offset != 0);
112   bcc_procutils_free(sym.module);
113 }
114 
115 namespace {
116 
zipped_lib_path()117 static std::string zipped_lib_path() {
118   return CMAKE_CURRENT_BINARY_DIR "/archive.zip!/libdebuginfo_test_lib.so";
119 }
120 
121 }  // namespace
122 
123 TEST_CASE("resolve symbol name in external zipped library", "[c_api]") {
124   struct bcc_symbol sym;
125   REQUIRE(bcc_resolve_symname(zipped_lib_path().c_str(), "symbol", 0x0, 0,
126                               nullptr, &sym) == 0);
127   REQUIRE(sym.module == zipped_lib_path());
128   REQUIRE(sym.offset != 0);
129   bcc_procutils_free(sym.module);
130 }
131 
132 namespace {
133 
system(const std::string & command)134 void system(const std::string &command) {
135   if (::system(command.c_str())) {
136     abort();
137   }
138 }
139 
140 class TmpDir {
141  public:
TmpDir()142   TmpDir() : path_("/tmp/bcc-test-XXXXXX") {
143     if (::mkdtemp(&path_[0]) == nullptr) {
144       abort();
145     }
146   }
147 
~TmpDir()148   ~TmpDir() { system("rm -rf " + path_); }
149 
path() const150   const std::string &path() const { return path_; }
151 
152  private:
153   std::string path_;
154 };
155 
test_debuginfo_only_symbol(const std::string & lib)156 void test_debuginfo_only_symbol(const std::string &lib) {
157   struct bcc_symbol sym;
158   REQUIRE(bcc_resolve_symname(lib.c_str(), "debuginfo_only_symbol", 0x0, 0,
159                               nullptr, &sym) == 0);
160   REQUIRE(sym.module[0] == '/');
161   REQUIRE(sym.offset != 0);
162   bcc_procutils_free(sym.module);
163 }
164 
165 }  // namespace
166 
167 TEST_CASE("resolve symbol name via symfs", "[c_api]") {
168   TmpDir tmpdir;
169   std::string lib_path = tmpdir.path() + "/lib.so";
170   std::string symfs = tmpdir.path() + "/symfs";
171   std::string symfs_lib_dir = symfs + "/" + tmpdir.path();
172   std::string symfs_lib_path = symfs_lib_dir + "/lib.so";
173 
174   system("mkdir -p " + symfs);
175   system("cp " CMAKE_CURRENT_BINARY_DIR "/libdebuginfo_test_lib.so " +
176          lib_path);
177   system("mkdir -p " + symfs_lib_dir);
178   system("cp " CMAKE_CURRENT_BINARY_DIR "/debuginfo.so " + symfs_lib_path);
179 
180   ::setenv("BCC_SYMFS", symfs.c_str(), 1);
181   test_debuginfo_only_symbol(lib_path);
182   ::unsetenv("BCC_SYMFS");
183 }
184 
185 TEST_CASE("resolve symbol name via buildid", "[c_api]") {
186   char build_id[128] = {0};
187   REQUIRE(bcc_elf_get_buildid(CMAKE_CURRENT_BINARY_DIR
188                               "/libdebuginfo_test_lib.so",
189                               build_id) == 0);
190 
191   TmpDir tmpdir;
192   std::string debugso_dir =
193       tmpdir.path() + "/.build-id/" + build_id[0] + build_id[1];
194   std::string debugso = debugso_dir + "/" + (build_id + 2) + ".debug";
195   system("mkdir -p " + debugso_dir);
196   system("cp " CMAKE_CURRENT_BINARY_DIR "/debuginfo.so " + debugso);
197 
198   ::setenv("BCC_DEBUGINFO_ROOT", tmpdir.path().c_str(), 1);
199   test_debuginfo_only_symbol(CMAKE_CURRENT_BINARY_DIR
200                              "/libdebuginfo_test_lib.so");
201   ::unsetenv("BCC_DEBUGINFO_ROOT");
202 }
203 
204 TEST_CASE("resolve symbol name via gnu_debuglink", "[c_api]") {
205   test_debuginfo_only_symbol(CMAKE_CURRENT_BINARY_DIR "/with_gnu_debuglink.so");
206 }
207 
208 #ifdef HAVE_LIBLZMA
209 TEST_CASE("resolve symbol name via mini debug info", "[c_api]") {
210   test_debuginfo_only_symbol(CMAKE_CURRENT_BINARY_DIR "/with_gnu_debugdata.so");
211 }
212 #endif
213 
_a_test_function(const char * a_string)214 extern "C" int _a_test_function(const char *a_string) {
215   int i;
216   for (i = 0; a_string[i]; ++i)
217     ;
218   return i;
219 }
220 
setup_tmp_mnts(void)221 static int setup_tmp_mnts(void) {
222   // Disconnect this mount namespace from its parent
223   if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0) {
224     fprintf(stderr, "unable to mark / PRIVATE: %s\n", strerror(errno));
225     return -1;
226   }
227   // create a new tmpfs mounted on /tmp
228   if (mount("tmpfs", "/tmp", "tmpfs", 0, NULL) < 0) {
229     fprintf(stderr, "unable to mount /tmp in mntns: %s\n", strerror(errno));
230     return -1;
231   }
232 
233   return 0;
234 }
235 
mntns_func(void * arg)236 static int mntns_func(void *arg) {
237   int in_fd, out_fd;
238   char buf[4096];
239   char libpath[1024];
240   ssize_t rb;
241   void *dlhdl;
242   struct link_map *lm;
243 
244   if (setup_tmp_mnts() < 0) {
245     return -1;
246   }
247 
248   // Find libz.so.1, if it's installed
249   dlhdl = dlopen("libz.so.1", RTLD_LAZY);
250   if (dlhdl == NULL) {
251     fprintf(stderr, "Unable to dlopen libz.so.1: %s\n", dlerror());
252     return -1;
253   }
254 
255   if (dlinfo(dlhdl, RTLD_DI_LINKMAP, &lm) < 0) {
256     fprintf(stderr, "Unable to find origin of libz.so.1: %s\n", dlerror());
257     return -1;
258   }
259 
260   strncpy(libpath, lm->l_name, sizeof(libpath) - 1);
261   dlclose(dlhdl);
262   dlhdl = NULL;
263 
264   // Copy a shared library from shared mntns to private /tmp
265   snprintf(buf, 4096, "%s", libpath);
266   in_fd = open(buf, O_RDONLY);
267   if (in_fd < 0) {
268     fprintf(stderr, "Unable to open %s: %s\n", buf, strerror(errno));
269     return -1;
270   }
271 
272   out_fd = open("/tmp/libz.so.1", O_RDWR|O_CREAT|O_EXCL,
273       S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
274   if (out_fd < 0) {
275     fprintf(stderr, "Unable to open /tmp/libz.so.1: %s\n", strerror(errno));
276     return -1;
277   }
278   memset(buf, 0, sizeof (buf));
279   while ((rb = read(in_fd, buf, sizeof (buf))) > 0) {
280     if (write(out_fd, buf, rb) < 0) {
281       fprintf(stderr, "Write error: %s\n", strerror(errno));
282       return -1;
283     }
284   }
285   close(in_fd);
286   close(out_fd);
287 
288   dlhdl = dlopen("/tmp/libz.so.1", RTLD_NOW);
289   if (dlhdl == NULL) {
290     fprintf(stderr, "dlopen error: %s\n", dlerror());
291     return -1;
292   }
293 
294   sleep(5);
295   dlclose(dlhdl);
296 
297   return 0;
298 }
299 
300 extern int cmd_scanf(const char *cmd, const char *fmt, ...);
301 
302 TEST_CASE("resolve symbol addresses for a given PID", "[c_api]") {
303   struct bcc_symbol sym;
304   struct bcc_symbol lazy_sym;
305   static struct bcc_symbol_option lazy_opt{
306     .use_debug_file = 1,
307     .check_debug_file_crc = 1,
308     .lazy_symbolize = 1,
309 #if defined(__powerpc64__) && defined(_CALL_ELF) && _CALL_ELF == 2
310     .use_symbol_type = BCC_SYM_ALL_TYPES | (1 << STT_PPC64_ELFV2_SYM_LEP),
311 #else
312     .use_symbol_type = BCC_SYM_ALL_TYPES,
313 #endif
314   };
315   void *resolver = bcc_symcache_new(getpid(), nullptr);
316   void *lazy_resolver = bcc_symcache_new(getpid(), &lazy_opt);
317 
318   REQUIRE(resolver);
319   REQUIRE(lazy_resolver);
320 
321   SECTION("resolve in our own binary memory space") {
322     REQUIRE(bcc_symcache_resolve(resolver, (uint64_t)&_a_test_function, &sym) ==
323             0);
324 
325     char *this_exe = realpath("/proc/self/exe", NULL);
326     REQUIRE(string(this_exe) == sym.module);
327     free(this_exe);
328 
329     REQUIRE(string("_a_test_function") == sym.name);
330 
331     REQUIRE(bcc_symcache_resolve(lazy_resolver, (uint64_t)&_a_test_function, &lazy_sym) ==
332             0);
333     REQUIRE(string(lazy_sym.name) == sym.name);
334     REQUIRE(string(lazy_sym.module) == sym.module);
335   }
336 
337   SECTION("resolve in " LIBBCC_NAME) {
338     void *libbcc = dlopen(LIBBCC_NAME, RTLD_LAZY | RTLD_NOLOAD);
339     REQUIRE(libbcc);
340 
341     void *libbcc_fptr = dlsym(libbcc, "bcc_resolve_symname");
342     REQUIRE(libbcc_fptr);
343 
344     REQUIRE(bcc_symcache_resolve(resolver, (uint64_t)libbcc_fptr, &sym) == 0);
345     REQUIRE(string(sym.module).find(LIBBCC_NAME) != string::npos);
346     REQUIRE(string("bcc_resolve_symname") == sym.name);
347 
348     REQUIRE(bcc_symcache_resolve(lazy_resolver, (uint64_t)libbcc_fptr, &lazy_sym) == 0);
349     REQUIRE(string(lazy_sym.module) == sym.module);
350     REQUIRE(string(lazy_sym.name) == sym.name);
351   }
352 
353   SECTION("resolve in libc") {
354     void *libc_fptr = dlsym(NULL, "strtok");
355     REQUIRE(libc_fptr);
356 
357     REQUIRE(bcc_symcache_resolve(resolver, (uint64_t)libc_fptr, &sym) == 0);
358     REQUIRE(sym.module);
359     REQUIRE(sym.module[0] == '/');
360     REQUIRE(string(sym.module).find("libc") != string::npos);
361 
362     REQUIRE(bcc_symcache_resolve(lazy_resolver, (uint64_t)libc_fptr, &lazy_sym) == 0);
363     REQUIRE(string(lazy_sym.module) == sym.module);
364     REQUIRE(string(lazy_sym.name) == sym.name);
365 
366     // In some cases, a symbol may have multiple aliases. Since
367     // bcc_symcache_resolve() returns only the first alias of a
368     // symbol, this may not always be "strtok" even if it points
369     // to the same address.
370     bool sym_match = (string("strtok") == sym.name);
371     if (!sym_match) {
372       uint64_t exp_addr, sym_addr;
373       char cmd[256];
374       const char *cmdfmt = "nm %s | grep \" %s$\" | cut -f 1 -d \" \"";
375 
376       // Find address of symbol by the expected name
377       sprintf(cmd, cmdfmt, sym.module, "strtok");
378       REQUIRE(cmd_scanf(cmd, "%lx", &exp_addr) == 0);
379 
380       // Find address of symbol by the name that was
381       // returned by bcc_symcache_resolve()
382       sprintf(cmd, cmdfmt, sym.module, sym.name);
383       REQUIRE(cmd_scanf(cmd, "%lx", &sym_addr) == 0);
384 
385       // If both addresses match, they are definitely
386       // aliases of the same symbol
387       sym_match = (exp_addr == sym_addr);
388     }
389 
390     REQUIRE(sym_match);
391   }
392 
393   SECTION("resolve in separate mount namespace") {
394     pid_t child;
395     uint64_t addr = 0;
396     uint64_t lazy_addr = 0;
397 
398     child = spawn_child(0, true, true, mntns_func);
399     REQUIRE(child > 0);
400 
401     void *resolver = bcc_symcache_new(child, nullptr);
402     REQUIRE(resolver);
403 
404     REQUIRE(bcc_symcache_resolve_name(resolver, "/tmp/libz.so.1", "zlibVersion",
405         &addr) == 0);
406     REQUIRE(addr != 0);
407 
408     void *lazy_resolver = bcc_symcache_new(child, &lazy_opt);
409     REQUIRE(lazy_resolver);
410     REQUIRE(bcc_symcache_resolve_name(lazy_resolver, "/tmp/libz.so.1", "zlibVersion",
411         &lazy_addr) == 0);
412     REQUIRE(lazy_addr == addr);
413     bcc_free_symcache(resolver, child);
414     bcc_free_symcache(lazy_resolver, child);
415   }
416   bcc_free_symcache(resolver, getpid());
417   bcc_free_symcache(lazy_resolver, getpid());
418 }
419 
420 TEST_CASE("resolve symbol addresses for an exited process", "[c-api]") {
421   struct bcc_symbol sym;
422   struct bcc_symbol lazy_sym;
423   static struct bcc_symbol_option lazy_opt {
424     .use_debug_file = 1, .check_debug_file_crc = 1, .lazy_symbolize = 1,
425 #if defined(__powerpc64__) && defined(_CALL_ELF) && _CALL_ELF == 2
426     .use_symbol_type = BCC_SYM_ALL_TYPES | (1 << STT_PPC64_ELFV2_SYM_LEP),
427 #else
428     .use_symbol_type = BCC_SYM_ALL_TYPES,
429 #endif
430   };
431 
432   SECTION("resolve in current namespace") {
__anon395773040302(void *) 433     pid_t child = spawn_child(nullptr, false, false, [](void *) {
434       sleep(5);
435       return 0;
436     });
437     void *resolver = bcc_symcache_new(child, nullptr);
438     void *lazy_resolver = bcc_symcache_new(child, &lazy_opt);
439 
440     REQUIRE(resolver);
441     REQUIRE(lazy_resolver);
442 
443     kill(child, SIGTERM);
444 
445     REQUIRE(bcc_symcache_resolve(resolver, (uint64_t)&_a_test_function, &sym) ==
446             0);
447 
448     char *this_exe = realpath("/proc/self/exe", NULL);
449     REQUIRE(string(this_exe) == sym.module);
450     free(this_exe);
451 
452     REQUIRE(string("_a_test_function") == sym.name);
453 
454     REQUIRE(bcc_symcache_resolve(lazy_resolver, (uint64_t)&_a_test_function,
455                                  &lazy_sym) == 0);
456     REQUIRE(string(lazy_sym.name) == sym.name);
457     REQUIRE(string(lazy_sym.module) == sym.module);
458   }
459 
460   SECTION("resolve in separate pid namespace") {
__anon395773040402(void *) 461     pid_t child = spawn_child(nullptr, true, false, [](void *) {
462       sleep(5);
463       return 0;
464     });
465     void *resolver = bcc_symcache_new(child, nullptr);
466     void *lazy_resolver = bcc_symcache_new(child, &lazy_opt);
467 
468     REQUIRE(resolver);
469     REQUIRE(lazy_resolver);
470 
471     kill(child, SIGTERM);
472 
473     REQUIRE(bcc_symcache_resolve(resolver, (uint64_t)&_a_test_function, &sym) ==
474             0);
475 
476     char *this_exe = realpath("/proc/self/exe", NULL);
477     REQUIRE(string(this_exe) == sym.module);
478     free(this_exe);
479 
480     REQUIRE(string("_a_test_function") == sym.name);
481 
482     REQUIRE(bcc_symcache_resolve(lazy_resolver, (uint64_t)&_a_test_function,
483                                  &lazy_sym) == 0);
484     REQUIRE(string(lazy_sym.name) == sym.name);
485     REQUIRE(string(lazy_sym.module) == sym.module);
486   }
487 
488   SECTION("resolve in separate pid and mount namespace") {
__anon395773040502(void *) 489     pid_t child = spawn_child(nullptr, true, true, [](void *) {
490       sleep(5);
491       return 0;
492     });
493     void *resolver = bcc_symcache_new(child, nullptr);
494     void *lazy_resolver = bcc_symcache_new(child, &lazy_opt);
495 
496     REQUIRE(resolver);
497     REQUIRE(lazy_resolver);
498 
499     kill(child, SIGTERM);
500 
501     REQUIRE(bcc_symcache_resolve(resolver, (uint64_t)&_a_test_function, &sym) ==
502             0);
503 
504     char *this_exe = realpath("/proc/self/exe", NULL);
505     REQUIRE(string(this_exe) == sym.module);
506     free(this_exe);
507 
508     REQUIRE(string("_a_test_function") == sym.name);
509 
510     REQUIRE(bcc_symcache_resolve(lazy_resolver, (uint64_t)&_a_test_function,
511                                  &lazy_sym) == 0);
512     REQUIRE(string(lazy_sym.name) == sym.name);
513     REQUIRE(string(lazy_sym.module) == sym.module);
514   }
515 }
516 
517 #define STACK_SIZE (1024 * 1024)
518 static char child_stack[STACK_SIZE];
519 
perf_map_path(pid_t pid)520 static string perf_map_path(pid_t pid) {
521   return tfm::format("/tmp/perf-%d.map", pid);
522 }
523 
make_perf_map_file(string & path,unsigned long long map_addr)524 static int make_perf_map_file(string &path, unsigned long long map_addr) {
525   FILE *file = fopen(path.c_str(), "w");
526   if (file == NULL) {
527     return -1;
528   }
529   fprintf(file, "%llx 10 dummy_fn\n", map_addr);
530   fprintf(file, "%llx 10 right_next_door_fn\n", map_addr + 0x10);
531   fclose(file);
532 
533   return 0;
534 }
535 
perf_map_func(void * arg)536 static int perf_map_func(void *arg) {
537   string path = perf_map_path(getpid());
538   if (make_perf_map_file(path, (unsigned long long)arg) < 0)
539     return -1;
540 
541   sleep(5);
542 
543   unlink(path.c_str());
544   return 0;
545 }
546 
perf_map_func_mntns(void * arg)547 static int perf_map_func_mntns(void *arg) {
548   string path = perf_map_path(getpid());
549 
550   if (setup_tmp_mnts() < 0) {
551     return -1;
552   }
553 
554   if (make_perf_map_file(path, (unsigned long long)arg) < 0)
555     return -1;
556 
557   sleep(5);
558 
559   unlink(path.c_str());
560   return 0;
561 }
562 
perf_map_func_noop(void * arg)563 static int perf_map_func_noop(void *arg) {
564   if (setup_tmp_mnts() < 0) {
565     return -1;
566   }
567 
568   sleep(5);
569 
570   return 0;
571 }
572 
spawn_child(void * map_addr,bool own_pidns,bool own_mntns,int (* child_func)(void *))573 static pid_t spawn_child(void *map_addr, bool own_pidns, bool own_mntns,
574     int (*child_func)(void *)) {
575   int flags = SIGCHLD;
576   if (own_pidns)
577     flags |= CLONE_NEWPID;
578   if (own_mntns)
579     flags |= CLONE_NEWNS;
580 
581   pid_t child = clone(child_func,
582       /* stack grows down */ child_stack + STACK_SIZE, flags, (void*)map_addr);
583   if (child < 0)
584     return -1;
585 
586   sleep(1); // let the child get set up
587   return child;
588 }
589 
590 TEST_CASE("resolve symbols using /tmp/perf-pid.map", "[c_api]") {
591   const int map_sz = 4096;
592   void *map_addr = mmap(NULL, map_sz, PROT_READ | PROT_EXEC,
593     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
594   REQUIRE(map_addr != MAP_FAILED);
595 
596   struct bcc_symbol sym;
597   pid_t child = -1;
598 
599   SECTION("same namespace") {
600     child = spawn_child(map_addr, /* own_pidns */ false, false, perf_map_func);
601     REQUIRE(child > 0);
602 
603     void *resolver = bcc_symcache_new(child, nullptr);
604     REQUIRE(resolver);
605 
606     REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr,
607         &sym) == 0);
608     REQUIRE(sym.module);
609     REQUIRE(string(sym.module) == perf_map_path(child));
610     REQUIRE(string("dummy_fn") == sym.name);
611 
612     REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr + 0x10,
613         &sym) == 0);
614     REQUIRE(sym.module);
615     REQUIRE(string(sym.module) == perf_map_path(child));
616     REQUIRE(string("right_next_door_fn") == sym.name);
617     bcc_free_symcache(resolver, child);
618 
619   }
620 
621   SECTION("separate namespace") {
622     child = spawn_child(map_addr, /* own_pidns */ true, false, perf_map_func);
623     REQUIRE(child > 0);
624 
625     void *resolver = bcc_symcache_new(child, nullptr);
626     REQUIRE(resolver);
627 
628     REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr,
629         &sym) == 0);
630     REQUIRE(sym.module);
631     // child is PID 1 in its namespace
632     REQUIRE(string(sym.module) == perf_map_path(1));
633     REQUIRE(string("dummy_fn") == sym.name);
634     unlink("/tmp/perf-1.map");
635     bcc_free_symcache(resolver, child);
636   }
637 
638   SECTION("separate pid and mount namespace") {
639     child = spawn_child(map_addr, /* own_pidns */ true, true,
640         perf_map_func_mntns);
641     REQUIRE(child > 0);
642 
643     void *resolver = bcc_symcache_new(child, nullptr);
644     REQUIRE(resolver);
645 
646     REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr,
647         &sym) == 0);
648     REQUIRE(sym.module);
649     // child is PID 1 in its namespace
650     REQUIRE(string(sym.module) == perf_map_path(1));
651     REQUIRE(string("dummy_fn") == sym.name);
652     bcc_free_symcache(resolver, child);
653   }
654 
655   SECTION("separate pid and mount namespace, perf-map in host") {
656     child = spawn_child(map_addr, /* own_pidns */ true, true,
657         perf_map_func_noop);
658     REQUIRE(child > 0);
659 
660     string path = perf_map_path(child);
661     REQUIRE(make_perf_map_file(path, (unsigned long long)map_addr) == 0);
662 
663     void *resolver = bcc_symcache_new(child, nullptr);
664     REQUIRE(resolver);
665 
666     REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr,
667         &sym) == 0);
668     REQUIRE(sym.module);
669     // child is PID 1 in its namespace
670     REQUIRE(string(sym.module) == perf_map_path(child));
671     REQUIRE(string("dummy_fn") == sym.name);
672 
673     unlink(path.c_str());
674     bcc_free_symcache(resolver, child);
675   }
676 
677 
678 
679   munmap(map_addr, map_sz);
680 }
681 
682 // must match exactly the defitinion of mod_search in bcc_syms.cc
683 struct mod_search {
684   const char *name;
685   uint64_t inode;
686   uint64_t dev_major;
687   uint64_t dev_minor;
688   uint64_t addr;
689   uint8_t inode_match_only;
690 
691   uint64_t start;
692   uint64_t file_offset;
693 };
694 
695 TEST_CASE("searching for modules in /proc/[pid]/maps", "[c_api][!mayfail]") {
696   std::string dummy_maps_path = CMAKE_CURRENT_BINARY_DIR + std::string("/dummy_proc_map.txt");
697   FILE *dummy_maps = fopen(dummy_maps_path.c_str(), "r");
698   REQUIRE(dummy_maps != NULL);
699 
700   SECTION("name match") {
701     fseek(dummy_maps, 0, SEEK_SET);
702 
703     struct mod_search search;
704     memset(&search, 0, sizeof(struct mod_search));
705     search.name = "/some/other/path/tolibs/lib/libutil-2.26.so";
706     search.addr = 0x1;
707     int res =  _procfs_maps_each_module(dummy_maps, 42, _bcc_syms_find_module,
708                                         &search);
709     REQUIRE(res == 0);
710     REQUIRE(search.start == 0x7f1515bad000);
711   }
712 
713   SECTION("expected failure to match (name only search)") {
714     fseek(dummy_maps, 0, SEEK_SET);
715 
716     struct mod_search search;
717     memset(&search, 0, sizeof(struct mod_search));
718     search.name = "/lib/that/isnt/in/maps/libdoesntexist.so";
719     search.addr = 0x1;
720     int res =  _procfs_maps_each_module(dummy_maps, 42, _bcc_syms_find_module,
721                                         &search);
722     REQUIRE(res == -1);
723   }
724 
725   SECTION("inode+dev match, names different") {
726     fseek(dummy_maps, 0, SEEK_SET);
727 
728     struct mod_search search;
729     memset(&search, 0, sizeof(struct mod_search));
730     search.name = "/proc/5/root/some/other/path/tolibs/lib/libz.so.1.2.8";
731     search.inode = 72809538;
732     search.dev_major = 0x00;
733     search.dev_minor = 0x1b;
734     search.addr = 0x2;
735     int res =  _procfs_maps_each_module(dummy_maps, 42, _bcc_syms_find_module,
736                                         &search);
737     REQUIRE(res == 0);
738     REQUIRE(search.start == 0x7f15164b5000);
739   }
740 
741   SECTION("inode+dev don't match, names same") {
742     fseek(dummy_maps, 0, SEEK_SET);
743 
744     struct mod_search search;
745     memset(&search, 0, sizeof(struct mod_search));
746     search.name = "/some/other/path/tolibs/lib/libutil-2.26.so";
747     search.inode = 9999999;
748     search.dev_major = 0x42;
749     search.dev_minor = 0x1b;
750     search.addr = 0x2;
751     int res =  _procfs_maps_each_module(dummy_maps, 42, _bcc_syms_find_module,
752                                         &search);
753     REQUIRE(res == -1);
754   }
755 
756   SECTION("inodes match, dev_major/minor don't, expected failure") {
757     fseek(dummy_maps, 0, SEEK_SET);
758 
759     struct mod_search search;
760     memset(&search, 0, sizeof(struct mod_search));
761     search.name = "/some/other/path/tolibs/lib/libutil-2.26.so";
762     search.inode = 72809526;
763     search.dev_major = 0x11;
764     search.dev_minor = 0x11;
765     search.addr = 0x2;
766     int res =  _procfs_maps_each_module(dummy_maps, 42, _bcc_syms_find_module,
767                                         &search);
768     REQUIRE(res == -1);
769   }
770 
771   SECTION("inodes match, dev_major/minor don't, match inode only") {
772     fseek(dummy_maps, 0, SEEK_SET);
773 
774     struct mod_search search;
775     memset(&search, 0, sizeof(struct mod_search));
776     search.name = "/some/other/path/tolibs/lib/libutil-2.26.so";
777     search.inode = 72809526;
778     search.dev_major = 0x11;
779     search.dev_minor = 0x11;
780     search.addr = 0x2;
781     search.inode_match_only = 1;
782     int res =  _procfs_maps_each_module(dummy_maps, 42, _bcc_syms_find_module,
783                                         &search);
784     REQUIRE(res == 0);
785     REQUIRE(search.start == 0x7f1515bad000);
786   }
787 
788   fclose(dummy_maps);
789 
790   SECTION("seach for lib in zip") {
791     std::string line =
792         "7f151476e000-7f1514779000 r-xp 00001000 00:1b "
793         "72809479 " CMAKE_CURRENT_BINARY_DIR "/archive.zip\n";
794     dummy_maps = fmemopen(nullptr, line.size(), "w+");
795     REQUIRE(fwrite(line.c_str(), line.size(), 1, dummy_maps) == 1);
796     fseek(dummy_maps, 0, SEEK_SET);
797 
798     struct mod_search search;
799     memset(&search, 0, sizeof(struct mod_search));
800     std::string zip_entry_path = zipped_lib_path();
801     search.name = zip_entry_path.c_str();
802     int res = _procfs_maps_each_module(dummy_maps, getpid(),
803                                        _bcc_syms_find_module, &search);
804     REQUIRE(res == 0);
805     REQUIRE(search.start == 0x7f151476e000);
806     REQUIRE(search.file_offset < 0x1000);
807 
808     fclose(dummy_maps);
809   }
810 }
811 
812 TEST_CASE("resolve global addr in libc in this process", "[c_api][!mayfail]") {
813   int pid = getpid();
814   char *sopath = bcc_procutils_which_so("c", pid);
815   uint64_t local_addr = 0x15;
816   uint64_t global_addr;
817 
818   struct mod_search search;
819   memset(&search, 0, sizeof(struct mod_search));
820   search.name = sopath;
821 
822   int res = bcc_procutils_each_module(pid, _bcc_syms_find_module,
823                                       &search);
824   REQUIRE(res == 0);
825   REQUIRE(search.start != 0);
826 
827   res = bcc_resolve_global_addr(pid, sopath, local_addr, 0, &global_addr);
828   REQUIRE(res == 0);
829   REQUIRE(global_addr == (search.start + local_addr - search.file_offset));
830   free(sopath);
831 }
832 
833 /* Consider the following scenario: we have some process that maps in a shared library [1] with a
834  * USDT probe [2]. The shared library's .text section doesn't have matching address and file off
835  * [3]. Since the location address in [2] is an offset relative to the base address of whatever.so
836  * in whatever process is mapping it, we need to convert the location address 0x77b8c to a global
837  * address in the process' address space in order to attach to the USDT.
838  *
839  * The formula for this (__so_calc_global_addr) is
840  *   global_addr = offset + (mod_start_addr - mod_file_offset)
841  *                        - (elf_sec_start_addr - elf_sec_file_offset)
842  *
843  * Which for our concrete example is
844  *   global_addr = 0x77b8c + (0x7f6cda31e000 - 0x72000) - (0x73c90 - 0x72c90)
845  *   global_addr = 0x7f6cda322b8c
846  *
847  * [1 - output from `cat /proc/PID/maps`]
848  * 7f6cda2ab000-7f6cda31e000 r--p 00000000 00:2d 5370022276                 /whatever.so
849  * 7f6cda31e000-7f6cda434000 r-xp 00072000 00:2d 5370022276                 /whatever.so
850  * 7f6cda434000-7f6cda43d000 r--p 00187000 00:2d 5370022276                 /whatever.so
851  * 7f6cda43d000-7f6cda43f000 rw-p 0018f000 00:2d 5370022276                 /whatever.so
852  *
853  * [2 - output from `readelf -n /whatever.so`]
854  * stapsdt              0x00000038 NT_STAPSDT (SystemTap probe descriptors)
855  *   Provider: test
856  *   Name: test_probe
857  *   Location: 0x0000000000077b8c, Base: 0x0000000000000000, Semaphore: 0x0000000000000000
858  *   Arguments: -8@$5
859  *
860  * [3 - output from `readelf -W --sections /whatever.so`]
861  *   [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
862  *   [16] .text             PROGBITS        0000000000073c90 072c90 1132dc 00  AX  0   0 16
863  */
864 TEST_CASE("conversion of module offset to/from global_addr", "[c_api]") {
865   uint64_t global_addr, offset, calc_offset, mod_start_addr, mod_file_offset;
866   uint64_t elf_sec_start_addr, elf_sec_file_offset;
867 
868   /* Initialize per example in comment above */
869   offset = 0x77b8c;
870   mod_start_addr = 0x7f6cda31e000;
871   mod_file_offset = 0x00072000;
872   elf_sec_start_addr = 0x73c90;
873   elf_sec_file_offset = 0x72c90;
874   global_addr = __so_calc_global_addr(mod_start_addr, mod_file_offset,
875                                       elf_sec_start_addr, elf_sec_file_offset,
876                                       offset);
877   REQUIRE(global_addr == 0x7f6cda322b8c);
878 
879   /* Reverse operation (global_addr -> offset) should yield original offset */
880   calc_offset = __so_calc_mod_offset(mod_start_addr, mod_file_offset,
881                                      elf_sec_start_addr, elf_sec_file_offset,
882                                      global_addr);
883   REQUIRE(calc_offset == offset);
884 }
885 
886 TEST_CASE("get online CPUs", "[c_api]") {
887 	std::vector<int> cpus = ebpf::get_online_cpus();
888 	int num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
889 	REQUIRE(cpus.size() == num_cpus);
890 }
891