cc: Finish USDT implementation
authorVicent Marti <tanoku@gmail.com>
Thu, 28 Apr 2016 17:42:55 +0000 (19:42 +0200)
committerVicent Marti <tanoku@gmail.com>
Thu, 28 Apr 2016 19:41:49 +0000 (21:41 +0200)
src/cc/usdt.cc
src/cc/usdt.h
src/cc/usdt_args.cc
tests/cc/CMakeLists.txt
tests/cc/test_c_api.cc
tests/cc/test_usdt_args.cc
tests/cc/test_usdt_probes.cc [new file with mode: 0644]

index 350d6a1..74fe40f 100644 (file)
  */
 #include <sstream>
 
+#include <fcntl.h>
+#include <sys/types.h>
+#include <unistd.h>
+
 #include "bcc_elf.h"
+#include "bcc_proc.h"
 #include "usdt.h"
 #include "vendor/tinyformat.hpp"
 
@@ -40,7 +45,84 @@ Probe::Probe(const char *bin_path, const char *provider, const char *name,
       name_(name),
       semaphore_(semaphore) {}
 
+bool Probe::in_shared_object() {
+  if (!in_shared_object_)
+    in_shared_object_ = (bcc_elf_is_shared_obj(bin_path_.c_str()) == 1);
+  return in_shared_object_.value();
+}
+
+bool Probe::lookup_semaphore_addr(uint64_t *address, int pid) {
+  auto it = semaphores_.find(pid);
+  if (it != semaphores_.end()) {
+    *address = it->second;
+    return true;
+  }
+
+  if (in_shared_object()) {
+    uint64_t load_address = 0x0;  // TODO
+    *address = load_address + semaphore_;
+  } else {
+    *address = semaphore_;
+  }
+
+  semaphores_[pid] = *address;
+  return true;
+}
+
+bool Probe::add_to_semaphore(int pid, int16_t val) {
+  uint64_t address;
+  if (!lookup_semaphore_addr(&address, pid))
+    return false;
+
+  std::string procmem = tfm::format("/proc/%d/mem", pid);
+  int memfd = ::open(procmem.c_str(), O_RDWR);
+  if (memfd < 0)
+    return false;
+
+  int16_t original;  // TODO: should this be unsigned?
+
+  if (::lseek(memfd, static_cast<off_t>(address), SEEK_SET) < 0 ||
+      ::read(memfd, &original, 2) != 2) {
+    ::close(memfd);
+    return false;
+  }
+
+  original = original + val;
+
+  if (::lseek(memfd, static_cast<off_t>(address), SEEK_SET) < 0 ||
+      ::write(memfd, &original, 2) != 2) {
+    ::close(memfd);
+    return false;
+  }
+
+  ::close(memfd);
+  return true;
+}
+
+bool Probe::enable(int pid) {
+  if (!add_to_semaphore(pid, +1))
+    return false;
+
+  // TODO: what happens if we enable this twice?
+  enabled_semaphores_.emplace(pid, std::move(ProcStat(pid)));
+  return true;
+}
+
+bool Probe::disable(int pid) {
+  auto it = enabled_semaphores_.find(pid);
+  if (it == enabled_semaphores_.end())
+    return false;
+
+  bool result = true;
+  if (!it->second.is_stale())
+    result = add_to_semaphore(pid, -1);
+
+  enabled_semaphores_.erase(it);
+  return result;
+}
+
 bool Probe::usdt_thunks(std::ostream &stream, const std::string &prefix) {
+  assert(!locations_.empty());
   for (size_t i = 0; i < locations_.size(); ++i) {
     tfm::format(
         stream,
@@ -51,17 +133,19 @@ bool Probe::usdt_thunks(std::ostream &stream, const std::string &prefix) {
 }
 
 bool Probe::usdt_cases(std::ostream &stream, const optional<int> &pid) {
+  assert(!locations_.empty());
   const size_t arg_count = locations_[0].arguments_.size();
 
   for (size_t arg_n = 0; arg_n < arg_count; ++arg_n) {
     Argument *largest = nullptr;
     for (Location &location : locations_) {
       Argument *candidate = location.arguments_[arg_n];
-      if (!largest || candidate->arg_size() > largest->arg_size())
+      if (!largest ||
+          std::abs(candidate->arg_size()) > std::abs(largest->arg_size()))
         largest = candidate;
     }
 
-    tfm::format(stream, "        %s arg%d = 0;\n", largest->ctype(), arg_n + 1);
+    tfm::format(stream, "%s arg%d = 0;\n", largest->ctype(), arg_n + 1);
   }
 
   for (size_t loc_n = 0; loc_n < locations_.size(); ++loc_n) {
@@ -89,6 +173,11 @@ void Context::_each_probe(const char *binpath, const struct bcc_elf_usdt *probe,
   ctx->add_probe(binpath, probe);
 }
 
+int Context::_each_module(const char *modpath, uint64_t, uint64_t, void *p) {
+  bcc_elf_foreach_usdt(modpath, _each_probe, p);
+  return 0;
+}
+
 void Context::add_probe(const char *binpath, const struct bcc_elf_usdt *probe) {
   Probe *found_probe = nullptr;
 
@@ -108,11 +197,37 @@ void Context::add_probe(const char *binpath, const struct bcc_elf_usdt *probe) {
   found_probe->add_location(probe->pc, probe->arg_fmt);
 }
 
-void Context::add_probes(const std::string &bin_path) {
-  bcc_elf_foreach_usdt(bin_path.c_str(), _each_probe, this);
+std::string Context::resolve_bin_path(const std::string &bin_path) {
+  std::string result;
+
+  if (char *which = bcc_procutils_which(bin_path.c_str())) {
+    result = which;
+    ::free(which);
+  } else if (const char *which_so = bcc_procutils_which_so(bin_path.c_str())) {
+    result = which_so;
+  }
+
+  return result;
+}
+
+Probe *Context::find_probe(const std::string &probe_name) {
+  for (Probe *p : probes_) {
+    if (p->name_ == probe_name)
+      return p;
+  }
+  return nullptr;
 }
 
-Context::Context(const std::string &bin_path) { add_probes(bin_path); }
+Context::Context(const std::string &bin_path) : loaded_(false) {
+  std::string full_path = resolve_bin_path(bin_path);
+  if (!full_path.empty()) {
+    if (bcc_elf_foreach_usdt(full_path.c_str(), _each_probe, this) == 0)
+      loaded_ = true;
+  }
+}
 
-Context::Context(int pid) {}
+Context::Context(int pid) : loaded_(false) {
+  if (bcc_procutils_each_module(pid, _each_module, this) == 0)
+    loaded_ = true;
+}
 }
index e975632..3422106 100644 (file)
@@ -19,6 +19,7 @@
 #include <unordered_map>
 #include <vector>
 
+#include "syms.h"
 #include "vendor/optional.hpp"
 
 namespace USDT {
@@ -37,7 +38,6 @@ private:
 
   bool get_global_address(uint64_t *address, const std::string &binpath,
                           const optional<int> &pid) const;
-  static const std::unordered_map<std::string, std::string> translations_;
 
 public:
   Argument();
@@ -49,7 +49,6 @@ public:
 
   int arg_size() const { return arg_size_.value_or(sizeof(void *)); }
   std::string ctype() const;
-  void normalize_register_name(std::string *normalized) const;
 
   const optional<std::string> &deref_ident() const { return deref_ident_; }
   const optional<std::string> &register_name() const { return register_name_; }
@@ -64,7 +63,7 @@ class ArgumentParser {
   ssize_t cur_pos_;
 
 protected:
-  virtual bool validate_register(const std::string &reg, int *reg_size) = 0;
+  virtual bool normalize_register(std::string *reg, int *reg_size) = 0;
 
   ssize_t parse_number(ssize_t pos, optional<int> *number);
   ssize_t parse_identifier(ssize_t pos, optional<std::string> *ident);
@@ -76,14 +75,40 @@ protected:
 
 public:
   bool parse(Argument *dest);
-  bool done() { return arg_[cur_pos_] == '\0'; }
+  bool done() { return cur_pos_ < 0 || arg_[cur_pos_] == '\0'; }
 
   ArgumentParser(const char *arg) : arg_(arg), cur_pos_(0) {}
 };
 
 class ArgumentParser_x64 : public ArgumentParser {
-  static const std::unordered_map<std::string, int> registers_;
-  bool validate_register(const std::string &reg, int *reg_size);
+  enum Register {
+    REG_A,
+    REG_B,
+    REG_C,
+    REG_D,
+    REG_SI,
+    REG_DI,
+    REG_BP,
+    REG_SP,
+    REG_8,
+    REG_9,
+    REG_10,
+    REG_11,
+    REG_12,
+    REG_13,
+    REG_14,
+    REG_15,
+    REG_RIP,
+  };
+
+  struct RegInfo {
+    Register reg;
+    int size;
+  };
+
+  static const std::unordered_map<std::string, RegInfo> registers_;
+  bool normalize_register(std::string *reg, int *reg_size);
+  void reg_to_name(std::string *norm, Register reg);
 
 public:
   ArgumentParser_x64(const char *arg) : ArgumentParser(arg) {}
@@ -102,33 +127,53 @@ class Probe {
   };
 
   std::vector<Location> locations_;
+  std::unordered_map<int, uint64_t> semaphores_;
+  std::unordered_map<int, ProcStat> enabled_semaphores_;
+  optional<bool> in_shared_object_;
+
+  bool add_to_semaphore(int pid, int16_t val);
+  bool lookup_semaphore_addr(uint64_t *address, int pid);
+  void add_location(uint64_t addr, const char *fmt);
 
 public:
   Probe(const char *bin_path, const char *provider, const char *name,
         uint64_t semaphore);
 
-  void add_location(uint64_t addr, const char *fmt);
-  bool need_enable() const { return semaphore_ != 0x0; }
   size_t num_locations() const { return locations_.size(); }
+  size_t num_arguments() const { return locations_.front().arguments_.size(); }
 
   bool usdt_thunks(std::ostream &stream, const std::string &prefix);
   bool usdt_cases(std::ostream &stream, const optional<int> &pid = nullopt);
 
+  bool need_enable() const { return semaphore_ != 0x0; }
+  bool enable(int pid);
+  bool disable(int pid);
+
+  bool in_shared_object();
+  const std::string &name() { return name_; }
+  const std::string &bin_path() { return bin_path_; }
+  const std::string &provider() { return provider_; }
+
   friend class Context;
 };
 
 class Context {
   std::vector<Probe *> probes_;
+  bool loaded_;
 
   static void _each_probe(const char *binpath, const struct bcc_elf_usdt *probe,
                           void *p);
+  static int _each_module(const char *modpath, uint64_t, uint64_t, void *p);
+
   void add_probe(const char *binpath, const struct bcc_elf_usdt *probe);
-  void add_probes(const std::string &bin_path);
+  std::string resolve_bin_path(const std::string &bin_path);
 
 public:
   Context(const std::string &bin_path);
   Context(int pid);
 
+  bool loaded() const { return loaded_; }
   size_t num_probes() const { return probes_.size(); }
+  Probe *find_probe(const std::string &probe_name);
 };
 }
index a0b92c1..01f8d7c 100644 (file)
@@ -27,37 +27,16 @@ namespace USDT {
 Argument::Argument() {}
 Argument::~Argument() {}
 
-const std::unordered_map<std::string, std::string> Argument::translations_ = {
-    {"rax", "ax"}, {"rbx", "bx"}, {"rcx", "cx"}, {"rdx", "dx"}, {"rdi", "di"},
-    {"rsi", "si"}, {"rbp", "bp"}, {"rsp", "sp"}, {"rip", "ip"}, {"eax", "ax"},
-    {"ebx", "bx"}, {"ecx", "cx"}, {"edx", "dx"}, {"edi", "di"}, {"esi", "si"},
-    {"ebp", "bp"}, {"esp", "sp"}, {"eip", "ip"},
-
-    {"al", "ax"},  {"bl", "bx"},  {"cl", "cx"},  {"dl", "dx"}};
-
 std::string Argument::ctype() const {
   const int s = arg_size() * 8;
   return (s < 0) ? tfm::format("int%d_t", -s) : tfm::format("uint%d_t", s);
 }
 
-void Argument::normalize_register_name(std::string *normalized) const {
-  if (!register_name_)
-    return;
-
-  normalized->assign(*register_name_);
-  if ((*normalized)[0] == '%')
-    normalized->erase(0, 1);
-
-  auto it = translations_.find(*normalized);
-  if (it != translations_.end())
-    normalized->assign(it->second);
-}
-
 bool Argument::get_global_address(uint64_t *address, const std::string &binpath,
                                   const optional<int> &pid) const {
   if (pid) {
-    return ProcSyms(*pid).resolve_name(binpath.c_str(), deref_ident_->c_str(),
-                                       address);
+    return ProcSyms(*pid)
+        .resolve_name(binpath.c_str(), deref_ident_->c_str(), address);
   }
 
   if (bcc_elf_is_shared_obj(binpath.c_str()) == 0) {
@@ -75,16 +54,14 @@ bool Argument::assign_to_local(std::ostream &stream,
                                const std::string &local_name,
                                const std::string &binpath,
                                const optional<int> &pid) const {
-  std::string regname;
-  normalize_register_name(&regname);
-
   if (constant_) {
     tfm::format(stream, "%s = %d;\n", local_name, *constant_);
     return true;
   }
 
   if (!deref_offset_) {
-    tfm::format(stream, "%s = (%s)ctx->%s;\n", local_name, ctype(), regname);
+    tfm::format(stream, "%s = (%s)ctx->%s;\n", local_name, ctype(),
+                *register_name_);
     return true;
   }
 
@@ -94,7 +71,7 @@ bool Argument::assign_to_local(std::ostream &stream,
                 "    u64 __temp = ctx->%s + (%d);\n"
                 "    bpf_probe_read(&%s, sizeof(%s), (void *)__temp);\n"
                 "}\n",
-                regname, *deref_offset_, local_name, local_name);
+                *register_name_, *deref_offset_, local_name, local_name);
     return true;
   }
 
@@ -135,15 +112,16 @@ ssize_t ArgumentParser::parse_identifier(ssize_t pos,
 }
 
 ssize_t ArgumentParser::parse_register(ssize_t pos, Argument *dest) {
-  ssize_t start = pos++;
-  if (arg_[start] != '%')
+  ssize_t start = ++pos;
+  if (arg_[start - 1] != '%')
     return -start;
+
   while (isalnum(arg_[pos])) pos++;
 
   std::string regname(arg_ + start, pos - start);
   int regsize = 0;
 
-  if (!validate_register(regname, &regsize))
+  if (!normalize_register(&regname, &regsize))
     return -start;
 
   dest->register_name_ = regname;
@@ -219,26 +197,125 @@ bool ArgumentParser::parse(Argument *dest) {
   return true;
 }
 
-const std::unordered_map<std::string, int> ArgumentParser_x64::registers_ = {
-    {"%rax", 8}, {"%rbx", 8}, {"%rcx", 8}, {"%rdx", 8}, {"%rdi", 8},
-    {"%rsi", 8}, {"%rbp", 8}, {"%rsp", 8}, {"%rip", 8}, {"%r8", 8},
-    {"%r9", 8},  {"%r10", 8}, {"%r11", 8}, {"%r12", 8}, {"%r13", 8},
-    {"%r14", 8}, {"%r15", 8},
-
-    {"%eax", 4}, {"%ebx", 4}, {"%ecx", 4}, {"%edx", 4}, {"%edi", 4},
-    {"%esi", 4}, {"%ebp", 4}, {"%esp", 4}, {"%eip", 4},
-
-    {"%ax", 2},  {"%bx", 2},  {"%cx", 2},  {"%dx", 2},  {"%di", 2},
-    {"%si", 2},  {"%bp", 2},  {"%sp", 2},  {"%ip", 2},
-
-    {"%al", 1},  {"%bl", 1},  {"%cl", 1},  {"%dl", 1}};
+const std::unordered_map<std::string, ArgumentParser_x64::RegInfo>
+    ArgumentParser_x64::registers_ = {
+        {"rax", {REG_A, 8}},   {"eax", {REG_A, 4}},
+        {"ax", {REG_A, 2}},    {"al", {REG_A, 1}},
+
+        {"rbx", {REG_B, 8}},   {"ebx", {REG_B, 4}},
+        {"bx", {REG_B, 2}},    {"bl", {REG_B, 1}},
+
+        {"rcx", {REG_C, 8}},   {"ecx", {REG_C, 4}},
+        {"cx", {REG_C, 2}},    {"cl", {REG_C, 1}},
+
+        {"rdx", {REG_D, 8}},   {"edx", {REG_D, 4}},
+        {"dx", {REG_D, 2}},    {"dl", {REG_D, 1}},
+
+        {"rsi", {REG_SI, 8}},  {"esi", {REG_SI, 4}},
+        {"si", {REG_SI, 2}},   {"sil", {REG_SI, 1}},
+
+        {"rdi", {REG_DI, 8}},  {"edi", {REG_DI, 4}},
+        {"di", {REG_DI, 2}},   {"dil", {REG_DI, 1}},
+
+        {"rbp", {REG_BP, 8}},  {"ebp", {REG_BP, 4}},
+        {"bp", {REG_BP, 2}},   {"bpl", {REG_BP, 1}},
+
+        {"rsp", {REG_SP, 8}},  {"esp", {REG_SP, 4}},
+        {"sp", {REG_SP, 2}},   {"spl", {REG_SP, 1}},
+
+        {"r8", {REG_8, 8}},    {"r8d", {REG_8, 4}},
+        {"r8w", {REG_8, 2}},   {"r8b", {REG_8, 1}},
+
+        {"r9", {REG_9, 8}},    {"r9d", {REG_9, 4}},
+        {"r9w", {REG_9, 2}},   {"r9b", {REG_9, 1}},
+
+        {"r10", {REG_10, 8}},  {"r10d", {REG_10, 4}},
+        {"r10w", {REG_10, 2}}, {"r10b", {REG_10, 1}},
+
+        {"r11", {REG_11, 8}},  {"r11d", {REG_11, 4}},
+        {"r11w", {REG_11, 2}}, {"r11b", {REG_11, 1}},
+
+        {"r12", {REG_12, 8}},  {"r12d", {REG_12, 4}},
+        {"r12w", {REG_12, 2}}, {"r12b", {REG_12, 1}},
+
+        {"r13", {REG_13, 8}},  {"r13d", {REG_13, 4}},
+        {"r13w", {REG_13, 2}}, {"r13b", {REG_13, 1}},
+
+        {"r14", {REG_14, 8}},  {"r14d", {REG_14, 4}},
+        {"r14w", {REG_14, 2}}, {"r14b", {REG_14, 1}},
+
+        {"r15", {REG_15, 8}},  {"r15d", {REG_15, 4}},
+        {"r15w", {REG_15, 2}}, {"r15b", {REG_15, 1}},
+
+        {"rip", {REG_RIP, 8}},
+};
+
+void ArgumentParser_x64::reg_to_name(std::string *norm, Register reg) {
+  switch (reg) {
+  case REG_A:
+    *norm = "ax";
+    break;
+  case REG_B:
+    *norm = "bx";
+    break;
+  case REG_C:
+    *norm = "cx";
+    break;
+  case REG_D:
+    *norm = "dx";
+    break;
+
+  case REG_SI:
+    *norm = "si";
+    break;
+  case REG_DI:
+    *norm = "di";
+    break;
+  case REG_BP:
+    *norm = "bp";
+    break;
+  case REG_SP:
+    *norm = "sp";
+    break;
+
+  case REG_8:
+    *norm = "r8";
+    break;
+  case REG_9:
+    *norm = "r9";
+    break;
+  case REG_10:
+    *norm = "r10";
+    break;
+  case REG_11:
+    *norm = "r11";
+    break;
+  case REG_12:
+    *norm = "r12";
+    break;
+  case REG_13:
+    *norm = "r13";
+    break;
+  case REG_14:
+    *norm = "r14";
+    break;
+  case REG_15:
+    *norm = "r15";
+    break;
+
+  case REG_RIP:
+    *norm = "ip";
+    break;
+  }
+}
 
-bool ArgumentParser_x64::validate_register(const std::string &reg,
-                                           int *reg_size) {
-  auto it = registers_.find(reg);
+bool ArgumentParser_x64::normalize_register(std::string *reg, int *reg_size) {
+  auto it = registers_.find(*reg);
   if (it == registers_.end())
     return false;
-  *reg_size = it->second;
+
+  *reg_size = it->second.size;
+  reg_to_name(reg, it->second.reg);
   return true;
 }
 }
index cad37a3..7f095e1 100644 (file)
@@ -8,6 +8,16 @@ target_link_libraries(test_static bcc-static)
 
 add_test(NAME c_test_static COMMAND ${TEST_WRAPPER} c_test_static sudo ${CMAKE_CURRENT_BINARY_DIR}/test_static)
 
-add_executable(test_libbcc test_libbcc.cc test_c_api.cc test_usdt_args.cc)
+add_executable(test_libbcc
+       test_libbcc.cc
+       test_c_api.cc
+       test_usdt_args.cc
+       test_usdt_probes.cc)
+
 target_link_libraries(test_libbcc bcc-shared dl)
 add_test(NAME test_libbcc COMMAND ${TEST_WRAPPER} c_test_all sudo ${CMAKE_CURRENT_BINARY_DIR}/test_libbcc)
+
+find_path(SDT_HEADER NAMES "sys/sdt.h")
+if (SDT_HEADER)
+       target_compile_definitions(test_libbcc PRIVATE HAVE_SDT_HEADER=1)
+endif()
index 1b5165e..7f6d3be 100644 (file)
@@ -48,7 +48,8 @@ static void _test_ksym(const char *sym, uint64_t addr, void *_) {
 }
 
 TEST_CASE("list all kernel symbols", "[c_api]") {
-  REQUIRE(geteuid() == 0);
+  if (geteuid() != 0)
+    return;
   bcc_procutils_each_ksym(_test_ksym, NULL);
 }
 
index 946e519..57ccb4f 100644 (file)
@@ -58,17 +58,17 @@ TEST_CASE("test usdt argument parsing", "[usdt]") {
     verify_register(parser, -4, 0);
     verify_register(parser, 8, 1234);
 
-    verify_register(parser, 8, "%rdi");
-    verify_register(parser, 8, "%rax");
-    verify_register(parser, 8, "%rsi");
-    verify_register(parser, -8, "%rbx");
-    verify_register(parser, 4, "%r12");
+    verify_register(parser, 8, "di");
+    verify_register(parser, 8, "ax");
+    verify_register(parser, 8, "si");
+    verify_register(parser, -8, "bx");
+    verify_register(parser, 4, "r12");
 
-    verify_register(parser, 8, "%rbp", -8);
-    verify_register(parser, 4, "%rax", 0);
+    verify_register(parser, 8, "bp", -8);
+    verify_register(parser, 4, "ax", 0);
 
-    verify_register(parser, -4, "%rip", 0, std::string("global_max_action"));
-    verify_register(parser, 8, "%rip", 24, std::string("mp_"));
+    verify_register(parser, -4, "ip", 0, std::string("global_max_action"));
+    verify_register(parser, 8, "ip", 24, std::string("mp_"));
 
     REQUIRE(parser.done());
   }
diff --git a/tests/cc/test_usdt_probes.cc b/tests/cc/test_usdt_probes.cc
new file mode 100644 (file)
index 0000000..37f8387
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ * 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 <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "catch.hpp"
+#include "usdt.h"
+
+#ifdef HAVE_SDT_HEADER
+/* required to insert USDT probes on this very executable --
+ * we're gonna be testing them live! */
+#include <sys/sdt.h>
+
+static int a_probed_function() {
+  int an_int = 23 + getpid();
+  void *a_pointer = malloc(4);
+  DTRACE_PROBE2(libbcc_test, sample_probe_1, an_int, a_pointer);
+  free(a_pointer);
+  return an_int;
+}
+
+TEST_CASE("test finding a probe in our own process", "[usdt]") {
+  USDT::Context ctx(getpid());
+  REQUIRE(ctx.num_probes() >= 1);
+
+  SECTION("our test probe") {
+    USDT::Probe *probe = ctx.find_probe("sample_probe_1");
+    REQUIRE(probe != nullptr);
+
+    REQUIRE(probe->in_shared_object() == false);
+    REQUIRE(probe->name() == "sample_probe_1");
+    REQUIRE(probe->provider() == "libbcc_test");
+    REQUIRE(probe->bin_path().find("/test_libbcc") != std::string::npos);
+
+    REQUIRE(probe->num_locations() == 1);
+    REQUIRE(probe->num_arguments() == 2);
+    REQUIRE(probe->need_enable() == false);
+
+    REQUIRE(a_probed_function() != 0);
+
+    std::ostringstream case_stream;
+    REQUIRE(probe->usdt_cases(case_stream));
+
+    std::string cases = case_stream.str();
+    REQUIRE(cases.find("int32_t arg1") != std::string::npos);
+    REQUIRE(cases.find("uint64_t arg2") != std::string::npos);
+  }
+}
+#endif  // HAVE_SDT_HEADER
+
+static size_t countsubs(const std::string &str, const std::string &sub) {
+  size_t count = 0;
+  for (size_t offset = str.find(sub); offset != std::string::npos;
+       offset = str.find(sub, offset + sub.length())) {
+    ++count;
+  }
+  return count;
+}
+
+class ChildProcess {
+  pid_t pid_;
+
+public:
+  ChildProcess(const char *name, char *const argv[]) {
+    pid_ = fork();
+    if (pid_ == 0) {
+      execvp(name, argv);
+      exit(0);
+    }
+    if (spawned()) {
+      usleep(250000);
+      if (kill(pid_, 0) < 0)
+        pid_ = -1;
+    }
+  }
+
+  ~ChildProcess() {
+    if (spawned()) {
+      int status;
+      kill(pid_, SIGKILL);
+      if (waitpid(pid_, &status, 0) != pid_)
+        abort();
+    }
+  }
+
+  bool spawned() const { return pid_ > 0; }
+  pid_t pid() const { return pid_; }
+};
+
+TEST_CASE("test listing all USDT probes in Ruby/MRI", "[usdt]") {
+  size_t mri_probe_count = 0;
+
+  SECTION("without a running Ruby process") {
+    USDT::Context ctx("ruby");
+
+    if (!ctx.loaded())
+      return;
+
+    REQUIRE(ctx.num_probes() > 10);
+    mri_probe_count = ctx.num_probes();
+
+    SECTION("GC static probe") {
+      USDT::Probe *probe = ctx.find_probe("gc__mark__begin");
+      REQUIRE(probe != nullptr);
+
+      REQUIRE(probe->in_shared_object() == true);
+      REQUIRE(probe->name() == "gc__mark__begin");
+      REQUIRE(probe->provider() == "ruby");
+      REQUIRE(probe->bin_path().find("/ruby") != std::string::npos);
+
+      REQUIRE(probe->num_locations() == 1);
+      REQUIRE(probe->num_arguments() == 0);
+      REQUIRE(probe->need_enable() == true);
+    }
+
+    SECTION("object creation probe") {
+      USDT::Probe *probe = ctx.find_probe("object__create");
+      REQUIRE(probe != nullptr);
+
+      REQUIRE(probe->in_shared_object() == true);
+      REQUIRE(probe->name() == "object__create");
+      REQUIRE(probe->provider() == "ruby");
+      REQUIRE(probe->bin_path().find("/ruby") != std::string::npos);
+
+      REQUIRE(probe->num_locations() == 1);
+      REQUIRE(probe->num_arguments() == 3);
+      REQUIRE(probe->need_enable() == true);
+
+      std::ostringstream thunks_stream;
+      REQUIRE(probe->usdt_thunks(thunks_stream, "ruby_usdt"));
+
+      std::string thunks = thunks_stream.str();
+      REQUIRE(std::count(thunks.begin(), thunks.end(), '\n') == 1);
+      REQUIRE(thunks.find("ruby_usdt_thunk_0") != std::string::npos);
+
+      std::ostringstream case_stream;
+      REQUIRE(probe->usdt_cases(case_stream));
+
+      std::string cases = case_stream.str();
+      REQUIRE(countsubs(cases, "arg1") == 2);
+      REQUIRE(countsubs(cases, "arg2") == 2);
+      REQUIRE(countsubs(cases, "arg3") == 2);
+
+      REQUIRE(countsubs(cases, "uint64_t") == 4);
+      REQUIRE(countsubs(cases, "int32_t") == 2);
+    }
+
+    SECTION("array creation probe") {
+      USDT::Probe *probe = ctx.find_probe("array__create");
+      REQUIRE(probe != nullptr);
+      REQUIRE(probe->name() == "array__create");
+
+      REQUIRE(probe->num_locations() == 7);
+      REQUIRE(probe->num_arguments() == 3);
+      REQUIRE(probe->need_enable() == true);
+
+      std::ostringstream thunks_stream;
+      REQUIRE(probe->usdt_thunks(thunks_stream, "ruby_usdt"));
+
+      std::string thunks = thunks_stream.str();
+      REQUIRE(std::count(thunks.begin(), thunks.end(), '\n') == 7);
+      REQUIRE(thunks.find("ruby_usdt_thunk_0") != std::string::npos);
+      REQUIRE(thunks.find("ruby_usdt_thunk_6") != std::string::npos);
+      REQUIRE(thunks.find("ruby_usdt_thunk_7") == std::string::npos);
+
+      std::ostringstream case_stream;
+      REQUIRE(probe->usdt_cases(case_stream));
+
+      std::string cases = case_stream.str();
+      REQUIRE(countsubs(cases, "arg1") == 8);
+      REQUIRE(countsubs(cases, "arg2") == 8);
+      REQUIRE(countsubs(cases, "arg3") == 8);
+
+      REQUIRE(countsubs(cases, "__loc_id") == 7);
+      REQUIRE(cases.find("int64_t arg1 =") != std::string::npos);
+    }
+  }
+
+  SECTION("with a running Ruby process") {
+    static char _ruby[] = "ruby";
+    char *const argv[2] = {_ruby, NULL};
+
+    ChildProcess ruby(argv[0], argv);
+    if (!ruby.spawned())
+      return;
+
+    USDT::Context ctx(ruby.pid());
+    REQUIRE(ctx.num_probes() >= mri_probe_count);
+
+    SECTION("get probe in running process") {
+      USDT::Probe *probe = ctx.find_probe("gc__mark__begin");
+      REQUIRE(probe != nullptr);
+
+      REQUIRE(probe->in_shared_object() == true);
+      REQUIRE(probe->name() == "gc__mark__begin");
+      REQUIRE(probe->provider() == "ruby");
+      REQUIRE(probe->bin_path().find("/ruby") != std::string::npos);
+
+      REQUIRE(probe->num_locations() == 1);
+      REQUIRE(probe->num_arguments() == 0);
+      REQUIRE(probe->need_enable() == true);
+    }
+  }
+}