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