blob: 7499bb9f9f04d239a7bb032fdc9d87af1072c98c [file] [log] [blame]
/*
* Copyright (c) 2016 GitHub, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <dlfcn.h>
#include <fcntl.h>
#include <link.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cstdlib>
#include "bcc_elf.h"
#include "bcc_perf_map.h"
#include "bcc_proc.h"
#include "bcc_syms.h"
#include "catch.hpp"
#include "common.h"
#include "vendor/tinyformat.hpp"
using namespace std;
static pid_t spawn_child(void *, bool, bool, int (*)(void *));
TEST_CASE("language detection", "[c_api]") {
const char *c = bcc_procutils_language(getpid());
REQUIRE(c);
REQUIRE(string(c).compare("c") == 0);
}
TEST_CASE("shared object resolution", "[c_api]") {
char *libm = bcc_procutils_which_so("m", 0);
REQUIRE(libm);
REQUIRE(libm[0] == '/');
REQUIRE(string(libm).find("libm.so") != string::npos);
free(libm);
}
TEST_CASE("shared object resolution using loaded libraries", "[c_api]") {
char *libelf = bcc_procutils_which_so("elf", getpid());
REQUIRE(libelf);
REQUIRE(libelf[0] == '/');
REQUIRE(string(libelf).find("libelf") != string::npos);
free(libelf);
}
TEST_CASE("binary resolution with `which`", "[c_api]") {
char *ld = bcc_procutils_which("ld");
REQUIRE(ld);
REQUIRE(ld[0] == '/');
free(ld);
}
static void _test_ksym(const char *sym, const char *mod, uint64_t addr, void *_) {
if (!strcmp(sym, "startup_64"))
REQUIRE(addr != 0x0ull);
}
TEST_CASE("list all kernel symbols", "[c_api]") {
if (geteuid() != 0)
return;
bcc_procutils_each_ksym(_test_ksym, NULL);
}
TEST_CASE("file-backed mapping identification") {
CHECK(bcc_mapping_is_file_backed("/bin/ls") == 1);
CHECK(bcc_mapping_is_file_backed("") == 0);
CHECK(bcc_mapping_is_file_backed("//anon") == 0);
CHECK(bcc_mapping_is_file_backed("/dev/zero") == 0);
CHECK(bcc_mapping_is_file_backed("/anon_hugepage") == 0);
CHECK(bcc_mapping_is_file_backed("/anon_hugepage (deleted)") == 0);
CHECK(bcc_mapping_is_file_backed("[stack") == 0);
CHECK(bcc_mapping_is_file_backed("/SYSV") == 0);
CHECK(bcc_mapping_is_file_backed("[heap]") == 0);
}
TEST_CASE("resolve symbol name in external library", "[c_api]") {
struct bcc_symbol sym;
REQUIRE(bcc_resolve_symname("c", "malloc", 0x0, 0, nullptr, &sym) == 0);
REQUIRE(string(sym.module).find("libc.so") != string::npos);
REQUIRE(sym.module[0] == '/');
REQUIRE(sym.offset != 0);
bcc_procutils_free(sym.module);
}
TEST_CASE("resolve symbol name in external library using loaded libraries", "[c_api]") {
struct bcc_symbol sym;
REQUIRE(bcc_resolve_symname("bcc", "bcc_procutils_which", 0x0, getpid(), nullptr, &sym) == 0);
REQUIRE(string(sym.module).find(LIBBCC_NAME) != string::npos);
REQUIRE(sym.module[0] == '/');
REQUIRE(sym.offset != 0);
bcc_procutils_free(sym.module);
}
namespace {
static std::string zipped_lib_path() {
return CMAKE_CURRENT_BINARY_DIR "/archive.zip!/libdebuginfo_test_lib.so";
}
} // namespace
TEST_CASE("resolve symbol name in external zipped library", "[c_api]") {
struct bcc_symbol sym;
REQUIRE(bcc_resolve_symname(zipped_lib_path().c_str(), "symbol", 0x0, 0,
nullptr, &sym) == 0);
REQUIRE(sym.module == zipped_lib_path());
REQUIRE(sym.offset != 0);
bcc_procutils_free(sym.module);
}
namespace {
void system(const std::string &command) {
if (::system(command.c_str())) {
abort();
}
}
class TmpDir {
public:
TmpDir() : path_("/tmp/bcc-test-XXXXXX") {
if (::mkdtemp(&path_[0]) == nullptr) {
abort();
}
}
~TmpDir() { system("rm -rf " + path_); }
const std::string &path() const { return path_; }
private:
std::string path_;
};
void test_debuginfo_only_symbol(const std::string &lib) {
struct bcc_symbol sym;
REQUIRE(bcc_resolve_symname(lib.c_str(), "debuginfo_only_symbol", 0x0, 0,
nullptr, &sym) == 0);
REQUIRE(sym.module[0] == '/');
REQUIRE(sym.offset != 0);
bcc_procutils_free(sym.module);
}
} // namespace
TEST_CASE("resolve symbol name via symfs", "[c_api]") {
TmpDir tmpdir;
std::string lib_path = tmpdir.path() + "/lib.so";
std::string symfs = tmpdir.path() + "/symfs";
std::string symfs_lib_dir = symfs + "/" + tmpdir.path();
std::string symfs_lib_path = symfs_lib_dir + "/lib.so";
system("mkdir -p " + symfs);
system("cp " CMAKE_CURRENT_BINARY_DIR "/libdebuginfo_test_lib.so " +
lib_path);
system("mkdir -p " + symfs_lib_dir);
system("cp " CMAKE_CURRENT_BINARY_DIR "/debuginfo.so " + symfs_lib_path);
::setenv("BCC_SYMFS", symfs.c_str(), 1);
test_debuginfo_only_symbol(lib_path);
::unsetenv("BCC_SYMFS");
}
TEST_CASE("resolve symbol name via buildid", "[c_api]") {
char build_id[128] = {0};
REQUIRE(bcc_elf_get_buildid(CMAKE_CURRENT_BINARY_DIR
"/libdebuginfo_test_lib.so",
build_id) == 0);
TmpDir tmpdir;
std::string debugso_dir =
tmpdir.path() + "/.build-id/" + build_id[0] + build_id[1];
std::string debugso = debugso_dir + "/" + (build_id + 2) + ".debug";
system("mkdir -p " + debugso_dir);
system("cp " CMAKE_CURRENT_BINARY_DIR "/debuginfo.so " + debugso);
::setenv("BCC_DEBUGINFO_ROOT", tmpdir.path().c_str(), 1);
test_debuginfo_only_symbol(CMAKE_CURRENT_BINARY_DIR
"/libdebuginfo_test_lib.so");
::unsetenv("BCC_DEBUGINFO_ROOT");
}
TEST_CASE("resolve symbol name via gnu_debuglink", "[c_api]") {
test_debuginfo_only_symbol(CMAKE_CURRENT_BINARY_DIR "/with_gnu_debuglink.so");
}
#ifdef HAVE_LIBLZMA
TEST_CASE("resolve symbol name via mini debug info", "[c_api]") {
test_debuginfo_only_symbol(CMAKE_CURRENT_BINARY_DIR "/with_gnu_debugdata.so");
}
#endif
extern "C" int _a_test_function(const char *a_string) {
int i;
for (i = 0; a_string[i]; ++i)
;
return i;
}
static int setup_tmp_mnts(void) {
// Disconnect this mount namespace from its parent
if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0) {
fprintf(stderr, "unable to mark / PRIVATE: %s\n", strerror(errno));
return -1;
}
// create a new tmpfs mounted on /tmp
if (mount("tmpfs", "/tmp", "tmpfs", 0, NULL) < 0) {
fprintf(stderr, "unable to mount /tmp in mntns: %s\n", strerror(errno));
return -1;
}
return 0;
}
static int mntns_func(void *arg) {
int in_fd, out_fd;
char buf[4096];
char libpath[1024];
ssize_t rb;
void *dlhdl;
struct link_map *lm;
if (setup_tmp_mnts() < 0) {
return -1;
}
// Find libz.so.1, if it's installed
dlhdl = dlopen("libz.so.1", RTLD_LAZY);
if (dlhdl == NULL) {
fprintf(stderr, "Unable to dlopen libz.so.1: %s\n", dlerror());
return -1;
}
if (dlinfo(dlhdl, RTLD_DI_LINKMAP, &lm) < 0) {
fprintf(stderr, "Unable to find origin of libz.so.1: %s\n", dlerror());
return -1;
}
strncpy(libpath, lm->l_name, sizeof(libpath) - 1);
dlclose(dlhdl);
dlhdl = NULL;
// Copy a shared library from shared mntns to private /tmp
snprintf(buf, 4096, "%s", libpath);
in_fd = open(buf, O_RDONLY);
if (in_fd < 0) {
fprintf(stderr, "Unable to open %s: %s\n", buf, strerror(errno));
return -1;
}
out_fd = open("/tmp/libz.so.1", O_RDWR|O_CREAT|O_EXCL,
S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
if (out_fd < 0) {
fprintf(stderr, "Unable to open /tmp/libz.so.1: %s\n", strerror(errno));
return -1;
}
memset(buf, 0, sizeof (buf));
while ((rb = read(in_fd, buf, sizeof (buf))) > 0) {
if (write(out_fd, buf, rb) < 0) {
fprintf(stderr, "Write error: %s\n", strerror(errno));
return -1;
}
}
close(in_fd);
close(out_fd);
dlhdl = dlopen("/tmp/libz.so.1", RTLD_NOW);
if (dlhdl == NULL) {
fprintf(stderr, "dlopen error: %s\n", dlerror());
return -1;
}
sleep(5);
dlclose(dlhdl);
return 0;
}
extern int cmd_scanf(const char *cmd, const char *fmt, ...);
TEST_CASE("resolve symbol addresses for a given PID", "[c_api]") {
struct bcc_symbol sym;
struct bcc_symbol lazy_sym;
static struct bcc_symbol_option lazy_opt{
.use_debug_file = 1,
.check_debug_file_crc = 1,
.lazy_symbolize = 1,
#if defined(__powerpc64__) && defined(_CALL_ELF) && _CALL_ELF == 2
.use_symbol_type = BCC_SYM_ALL_TYPES | (1 << STT_PPC64_ELFV2_SYM_LEP),
#else
.use_symbol_type = BCC_SYM_ALL_TYPES,
#endif
};
void *resolver = bcc_symcache_new(getpid(), nullptr);
void *lazy_resolver = bcc_symcache_new(getpid(), &lazy_opt);
REQUIRE(resolver);
REQUIRE(lazy_resolver);
SECTION("resolve in our own binary memory space") {
REQUIRE(bcc_symcache_resolve(resolver, (uint64_t)&_a_test_function, &sym) ==
0);
char *this_exe = realpath("/proc/self/exe", NULL);
REQUIRE(string(this_exe) == sym.module);
free(this_exe);
REQUIRE(string("_a_test_function") == sym.name);
REQUIRE(bcc_symcache_resolve(lazy_resolver, (uint64_t)&_a_test_function, &lazy_sym) ==
0);
REQUIRE(string(lazy_sym.name) == sym.name);
REQUIRE(string(lazy_sym.module) == sym.module);
}
SECTION("resolve in " LIBBCC_NAME) {
void *libbcc = dlopen(LIBBCC_NAME, RTLD_LAZY | RTLD_NOLOAD);
REQUIRE(libbcc);
void *libbcc_fptr = dlsym(libbcc, "bcc_resolve_symname");
REQUIRE(libbcc_fptr);
REQUIRE(bcc_symcache_resolve(resolver, (uint64_t)libbcc_fptr, &sym) == 0);
REQUIRE(string(sym.module).find(LIBBCC_NAME) != string::npos);
REQUIRE(string("bcc_resolve_symname") == sym.name);
REQUIRE(bcc_symcache_resolve(lazy_resolver, (uint64_t)libbcc_fptr, &lazy_sym) == 0);
REQUIRE(string(lazy_sym.module) == sym.module);
REQUIRE(string(lazy_sym.name) == sym.name);
}
SECTION("resolve in libc") {
void *libc_fptr = dlsym(NULL, "strtok");
REQUIRE(libc_fptr);
REQUIRE(bcc_symcache_resolve(resolver, (uint64_t)libc_fptr, &sym) == 0);
REQUIRE(sym.module);
REQUIRE(sym.module[0] == '/');
REQUIRE(string(sym.module).find("libc") != string::npos);
REQUIRE(bcc_symcache_resolve(lazy_resolver, (uint64_t)libc_fptr, &lazy_sym) == 0);
REQUIRE(string(lazy_sym.module) == sym.module);
REQUIRE(string(lazy_sym.name) == sym.name);
// In some cases, a symbol may have multiple aliases. Since
// bcc_symcache_resolve() returns only the first alias of a
// symbol, this may not always be "strtok" even if it points
// to the same address.
bool sym_match = (string("strtok") == sym.name);
if (!sym_match) {
uint64_t exp_addr, sym_addr;
char cmd[256];
const char *cmdfmt = "nm %s | grep \" %s$\" | cut -f 1 -d \" \"";
// Find address of symbol by the expected name
sprintf(cmd, cmdfmt, sym.module, "strtok");
REQUIRE(cmd_scanf(cmd, "%lx", &exp_addr) == 0);
// Find address of symbol by the name that was
// returned by bcc_symcache_resolve()
sprintf(cmd, cmdfmt, sym.module, sym.name);
REQUIRE(cmd_scanf(cmd, "%lx", &sym_addr) == 0);
// If both addresses match, they are definitely
// aliases of the same symbol
sym_match = (exp_addr == sym_addr);
}
REQUIRE(sym_match);
}
SECTION("resolve in separate mount namespace") {
pid_t child;
uint64_t addr = 0;
uint64_t lazy_addr = 0;
child = spawn_child(0, true, true, mntns_func);
REQUIRE(child > 0);
void *resolver = bcc_symcache_new(child, nullptr);
REQUIRE(resolver);
REQUIRE(bcc_symcache_resolve_name(resolver, "/tmp/libz.so.1", "zlibVersion",
&addr) == 0);
REQUIRE(addr != 0);
void *lazy_resolver = bcc_symcache_new(child, &lazy_opt);
REQUIRE(lazy_resolver);
REQUIRE(bcc_symcache_resolve_name(lazy_resolver, "/tmp/libz.so.1", "zlibVersion",
&lazy_addr) == 0);
REQUIRE(lazy_addr == addr);
bcc_free_symcache(resolver, child);
bcc_free_symcache(lazy_resolver, child);
}
bcc_free_symcache(resolver, getpid());
bcc_free_symcache(lazy_resolver, getpid());
}
TEST_CASE("resolve symbol addresses for an exited process", "[c-api]") {
struct bcc_symbol sym;
struct bcc_symbol lazy_sym;
static struct bcc_symbol_option lazy_opt {
.use_debug_file = 1, .check_debug_file_crc = 1, .lazy_symbolize = 1,
#if defined(__powerpc64__) && defined(_CALL_ELF) && _CALL_ELF == 2
.use_symbol_type = BCC_SYM_ALL_TYPES | (1 << STT_PPC64_ELFV2_SYM_LEP),
#else
.use_symbol_type = BCC_SYM_ALL_TYPES,
#endif
};
SECTION("resolve in current namespace") {
pid_t child = spawn_child(nullptr, false, false, [](void *) {
sleep(5);
return 0;
});
void *resolver = bcc_symcache_new(child, nullptr);
void *lazy_resolver = bcc_symcache_new(child, &lazy_opt);
REQUIRE(resolver);
REQUIRE(lazy_resolver);
kill(child, SIGTERM);
REQUIRE(bcc_symcache_resolve(resolver, (uint64_t)&_a_test_function, &sym) ==
0);
char *this_exe = realpath("/proc/self/exe", NULL);
REQUIRE(string(this_exe) == sym.module);
free(this_exe);
REQUIRE(string("_a_test_function") == sym.name);
REQUIRE(bcc_symcache_resolve(lazy_resolver, (uint64_t)&_a_test_function,
&lazy_sym) == 0);
REQUIRE(string(lazy_sym.name) == sym.name);
REQUIRE(string(lazy_sym.module) == sym.module);
}
SECTION("resolve in separate pid namespace") {
pid_t child = spawn_child(nullptr, true, false, [](void *) {
sleep(5);
return 0;
});
void *resolver = bcc_symcache_new(child, nullptr);
void *lazy_resolver = bcc_symcache_new(child, &lazy_opt);
REQUIRE(resolver);
REQUIRE(lazy_resolver);
kill(child, SIGTERM);
REQUIRE(bcc_symcache_resolve(resolver, (uint64_t)&_a_test_function, &sym) ==
0);
char *this_exe = realpath("/proc/self/exe", NULL);
REQUIRE(string(this_exe) == sym.module);
free(this_exe);
REQUIRE(string("_a_test_function") == sym.name);
REQUIRE(bcc_symcache_resolve(lazy_resolver, (uint64_t)&_a_test_function,
&lazy_sym) == 0);
REQUIRE(string(lazy_sym.name) == sym.name);
REQUIRE(string(lazy_sym.module) == sym.module);
}
SECTION("resolve in separate pid and mount namespace") {
pid_t child = spawn_child(nullptr, true, true, [](void *) {
sleep(5);
return 0;
});
void *resolver = bcc_symcache_new(child, nullptr);
void *lazy_resolver = bcc_symcache_new(child, &lazy_opt);
REQUIRE(resolver);
REQUIRE(lazy_resolver);
kill(child, SIGTERM);
REQUIRE(bcc_symcache_resolve(resolver, (uint64_t)&_a_test_function, &sym) ==
0);
char *this_exe = realpath("/proc/self/exe", NULL);
REQUIRE(string(this_exe) == sym.module);
free(this_exe);
REQUIRE(string("_a_test_function") == sym.name);
REQUIRE(bcc_symcache_resolve(lazy_resolver, (uint64_t)&_a_test_function,
&lazy_sym) == 0);
REQUIRE(string(lazy_sym.name) == sym.name);
REQUIRE(string(lazy_sym.module) == sym.module);
}
}
#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];
static string perf_map_path(pid_t pid) {
return tfm::format("/tmp/perf-%d.map", pid);
}
static int make_perf_map_file(string &path, unsigned long long map_addr) {
FILE *file = fopen(path.c_str(), "w");
if (file == NULL) {
return -1;
}
fprintf(file, "%llx 10 dummy_fn\n", map_addr);
fprintf(file, "%llx 10 right_next_door_fn\n", map_addr + 0x10);
fclose(file);
return 0;
}
static int perf_map_func(void *arg) {
string path = perf_map_path(getpid());
if (make_perf_map_file(path, (unsigned long long)arg) < 0)
return -1;
sleep(5);
unlink(path.c_str());
return 0;
}
static int perf_map_func_mntns(void *arg) {
string path = perf_map_path(getpid());
if (setup_tmp_mnts() < 0) {
return -1;
}
if (make_perf_map_file(path, (unsigned long long)arg) < 0)
return -1;
sleep(5);
unlink(path.c_str());
return 0;
}
static int perf_map_func_noop(void *arg) {
if (setup_tmp_mnts() < 0) {
return -1;
}
sleep(5);
return 0;
}
static pid_t spawn_child(void *map_addr, bool own_pidns, bool own_mntns,
int (*child_func)(void *)) {
int flags = SIGCHLD;
if (own_pidns)
flags |= CLONE_NEWPID;
if (own_mntns)
flags |= CLONE_NEWNS;
pid_t child = clone(child_func,
/* stack grows down */ child_stack + STACK_SIZE, flags, (void*)map_addr);
if (child < 0)
return -1;
sleep(1); // let the child get set up
return child;
}
TEST_CASE("resolve symbols using /tmp/perf-pid.map", "[c_api]") {
const int map_sz = 4096;
void *map_addr = mmap(NULL, map_sz, PROT_READ | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
REQUIRE(map_addr != MAP_FAILED);
struct bcc_symbol sym;
pid_t child = -1;
SECTION("same namespace") {
child = spawn_child(map_addr, /* own_pidns */ false, false, perf_map_func);
REQUIRE(child > 0);
void *resolver = bcc_symcache_new(child, nullptr);
REQUIRE(resolver);
REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr,
&sym) == 0);
REQUIRE(sym.module);
REQUIRE(string(sym.module) == perf_map_path(child));
REQUIRE(string("dummy_fn") == sym.name);
REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr + 0x10,
&sym) == 0);
REQUIRE(sym.module);
REQUIRE(string(sym.module) == perf_map_path(child));
REQUIRE(string("right_next_door_fn") == sym.name);
bcc_free_symcache(resolver, child);
}
SECTION("separate namespace") {
child = spawn_child(map_addr, /* own_pidns */ true, false, perf_map_func);
REQUIRE(child > 0);
void *resolver = bcc_symcache_new(child, nullptr);
REQUIRE(resolver);
REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr,
&sym) == 0);
REQUIRE(sym.module);
// child is PID 1 in its namespace
REQUIRE(string(sym.module) == perf_map_path(1));
REQUIRE(string("dummy_fn") == sym.name);
unlink("/tmp/perf-1.map");
bcc_free_symcache(resolver, child);
}
SECTION("separate pid and mount namespace") {
child = spawn_child(map_addr, /* own_pidns */ true, true,
perf_map_func_mntns);
REQUIRE(child > 0);
void *resolver = bcc_symcache_new(child, nullptr);
REQUIRE(resolver);
REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr,
&sym) == 0);
REQUIRE(sym.module);
// child is PID 1 in its namespace
REQUIRE(string(sym.module) == perf_map_path(1));
REQUIRE(string("dummy_fn") == sym.name);
bcc_free_symcache(resolver, child);
}
SECTION("separate pid and mount namespace, perf-map in host") {
child = spawn_child(map_addr, /* own_pidns */ true, true,
perf_map_func_noop);
REQUIRE(child > 0);
string path = perf_map_path(child);
REQUIRE(make_perf_map_file(path, (unsigned long long)map_addr) == 0);
void *resolver = bcc_symcache_new(child, nullptr);
REQUIRE(resolver);
REQUIRE(bcc_symcache_resolve(resolver, (unsigned long long)map_addr,
&sym) == 0);
REQUIRE(sym.module);
// child is PID 1 in its namespace
REQUIRE(string(sym.module) == perf_map_path(child));
REQUIRE(string("dummy_fn") == sym.name);
unlink(path.c_str());
bcc_free_symcache(resolver, child);
}
munmap(map_addr, map_sz);
}
// must match exactly the defitinion of mod_search in bcc_syms.cc
struct mod_search {
const char *name;
uint64_t inode;
uint64_t dev_major;
uint64_t dev_minor;
uint64_t addr;
uint8_t inode_match_only;
uint64_t start;
uint64_t file_offset;
};
TEST_CASE("searching for modules in /proc/[pid]/maps", "[c_api][!mayfail]") {
std::string dummy_maps_path = CMAKE_CURRENT_BINARY_DIR + std::string("/dummy_proc_map.txt");
FILE *dummy_maps = fopen(dummy_maps_path.c_str(), "r");
REQUIRE(dummy_maps != NULL);
SECTION("name match") {
fseek(dummy_maps, 0, SEEK_SET);
struct mod_search search;
memset(&search, 0, sizeof(struct mod_search));
search.name = "/some/other/path/tolibs/lib/libutil-2.26.so";
search.addr = 0x1;
int res = _procfs_maps_each_module(dummy_maps, 42, _bcc_syms_find_module,
&search);
REQUIRE(res == 0);
REQUIRE(search.start == 0x7f1515bad000);
}
SECTION("expected failure to match (name only search)") {
fseek(dummy_maps, 0, SEEK_SET);
struct mod_search search;
memset(&search, 0, sizeof(struct mod_search));
search.name = "/lib/that/isnt/in/maps/libdoesntexist.so";
search.addr = 0x1;
int res = _procfs_maps_each_module(dummy_maps, 42, _bcc_syms_find_module,
&search);
REQUIRE(res == -1);
}
SECTION("inode+dev match, names different") {
fseek(dummy_maps, 0, SEEK_SET);
struct mod_search search;
memset(&search, 0, sizeof(struct mod_search));
search.name = "/proc/5/root/some/other/path/tolibs/lib/libz.so.1.2.8";
search.inode = 72809538;
search.dev_major = 0x00;
search.dev_minor = 0x1b;
search.addr = 0x2;
int res = _procfs_maps_each_module(dummy_maps, 42, _bcc_syms_find_module,
&search);
REQUIRE(res == 0);
REQUIRE(search.start == 0x7f15164b5000);
}
SECTION("inode+dev don't match, names same") {
fseek(dummy_maps, 0, SEEK_SET);
struct mod_search search;
memset(&search, 0, sizeof(struct mod_search));
search.name = "/some/other/path/tolibs/lib/libutil-2.26.so";
search.inode = 9999999;
search.dev_major = 0x42;
search.dev_minor = 0x1b;
search.addr = 0x2;
int res = _procfs_maps_each_module(dummy_maps, 42, _bcc_syms_find_module,
&search);
REQUIRE(res == -1);
}
SECTION("inodes match, dev_major/minor don't, expected failure") {
fseek(dummy_maps, 0, SEEK_SET);
struct mod_search search;
memset(&search, 0, sizeof(struct mod_search));
search.name = "/some/other/path/tolibs/lib/libutil-2.26.so";
search.inode = 72809526;
search.dev_major = 0x11;
search.dev_minor = 0x11;
search.addr = 0x2;
int res = _procfs_maps_each_module(dummy_maps, 42, _bcc_syms_find_module,
&search);
REQUIRE(res == -1);
}
SECTION("inodes match, dev_major/minor don't, match inode only") {
fseek(dummy_maps, 0, SEEK_SET);
struct mod_search search;
memset(&search, 0, sizeof(struct mod_search));
search.name = "/some/other/path/tolibs/lib/libutil-2.26.so";
search.inode = 72809526;
search.dev_major = 0x11;
search.dev_minor = 0x11;
search.addr = 0x2;
search.inode_match_only = 1;
int res = _procfs_maps_each_module(dummy_maps, 42, _bcc_syms_find_module,
&search);
REQUIRE(res == 0);
REQUIRE(search.start == 0x7f1515bad000);
}
fclose(dummy_maps);
SECTION("seach for lib in zip") {
std::string line =
"7f151476e000-7f1514779000 r-xp 00001000 00:1b "
"72809479 " CMAKE_CURRENT_BINARY_DIR "/archive.zip\n";
dummy_maps = fmemopen(nullptr, line.size(), "w+");
REQUIRE(fwrite(line.c_str(), line.size(), 1, dummy_maps) == 1);
fseek(dummy_maps, 0, SEEK_SET);
struct mod_search search;
memset(&search, 0, sizeof(struct mod_search));
std::string zip_entry_path = zipped_lib_path();
search.name = zip_entry_path.c_str();
int res = _procfs_maps_each_module(dummy_maps, getpid(),
_bcc_syms_find_module, &search);
REQUIRE(res == 0);
REQUIRE(search.start == 0x7f151476e000);
REQUIRE(search.file_offset < 0x1000);
fclose(dummy_maps);
}
}
TEST_CASE("resolve global addr in libc in this process", "[c_api][!mayfail]") {
int pid = getpid();
char *sopath = bcc_procutils_which_so("c", pid);
uint64_t local_addr = 0x15;
uint64_t global_addr;
struct mod_search search;
memset(&search, 0, sizeof(struct mod_search));
search.name = sopath;
int res = bcc_procutils_each_module(pid, _bcc_syms_find_module,
&search);
REQUIRE(res == 0);
REQUIRE(search.start != 0);
res = bcc_resolve_global_addr(pid, sopath, local_addr, 0, &global_addr);
REQUIRE(res == 0);
REQUIRE(global_addr == (search.start + local_addr - search.file_offset));
free(sopath);
}
/* Consider the following scenario: we have some process that maps in a shared library [1] with a
* USDT probe [2]. The shared library's .text section doesn't have matching address and file off
* [3]. Since the location address in [2] is an offset relative to the base address of whatever.so
* in whatever process is mapping it, we need to convert the location address 0x77b8c to a global
* address in the process' address space in order to attach to the USDT.
*
* The formula for this (__so_calc_global_addr) is
* global_addr = offset + (mod_start_addr - mod_file_offset)
* - (elf_sec_start_addr - elf_sec_file_offset)
*
* Which for our concrete example is
* global_addr = 0x77b8c + (0x7f6cda31e000 - 0x72000) - (0x73c90 - 0x72c90)
* global_addr = 0x7f6cda322b8c
*
* [1 - output from `cat /proc/PID/maps`]
* 7f6cda2ab000-7f6cda31e000 r--p 00000000 00:2d 5370022276 /whatever.so
* 7f6cda31e000-7f6cda434000 r-xp 00072000 00:2d 5370022276 /whatever.so
* 7f6cda434000-7f6cda43d000 r--p 00187000 00:2d 5370022276 /whatever.so
* 7f6cda43d000-7f6cda43f000 rw-p 0018f000 00:2d 5370022276 /whatever.so
*
* [2 - output from `readelf -n /whatever.so`]
* stapsdt 0x00000038 NT_STAPSDT (SystemTap probe descriptors)
* Provider: test
* Name: test_probe
* Location: 0x0000000000077b8c, Base: 0x0000000000000000, Semaphore: 0x0000000000000000
* Arguments: -8@$5
*
* [3 - output from `readelf -W --sections /whatever.so`]
* [Nr] Name Type Address Off Size ES Flg Lk Inf Al
* [16] .text PROGBITS 0000000000073c90 072c90 1132dc 00 AX 0 0 16
*/
TEST_CASE("conversion of module offset to/from global_addr", "[c_api]") {
uint64_t global_addr, offset, calc_offset, mod_start_addr, mod_file_offset;
uint64_t elf_sec_start_addr, elf_sec_file_offset;
/* Initialize per example in comment above */
offset = 0x77b8c;
mod_start_addr = 0x7f6cda31e000;
mod_file_offset = 0x00072000;
elf_sec_start_addr = 0x73c90;
elf_sec_file_offset = 0x72c90;
global_addr = __so_calc_global_addr(mod_start_addr, mod_file_offset,
elf_sec_start_addr, elf_sec_file_offset,
offset);
REQUIRE(global_addr == 0x7f6cda322b8c);
/* Reverse operation (global_addr -> offset) should yield original offset */
calc_offset = __so_calc_mod_offset(mod_start_addr, mod_file_offset,
elf_sec_start_addr, elf_sec_file_offset,
global_addr);
REQUIRE(calc_offset == offset);
}
TEST_CASE("get online CPUs", "[c_api]") {
std::vector<int> cpus = ebpf::get_online_cpus();
int num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
REQUIRE(cpus.size() == num_cpus);
}