/*
 * 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 with the generalized function", "[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_in_process("elf", getpid());
  REQUIRE(libelf);
  REQUIRE(libelf[0] == '/');
  REQUIRE(string(libelf).find("libelf") != string::npos);
  free(libelf);
}

TEST_CASE("shared object resolution using loaded libraries with the generalized function", "[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);
}
