*/
#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"
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,
}
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) {
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;
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;
+}
}
#include <unordered_map>
#include <vector>
+#include "syms.h"
#include "vendor/optional.hpp"
namespace USDT {
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();
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> ®ister_name() const { return register_name_; }
ssize_t cur_pos_;
protected:
- virtual bool validate_register(const std::string ®, 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);
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 ®, 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) {}
};
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);
};
}
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) {
const std::string &local_name,
const std::string &binpath,
const optional<int> &pid) const {
- std::string regname;
- normalize_register_name(®name);
-
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;
}
" 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;
}
}
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, ®size))
+ if (!normalize_register(®name, ®size))
return -start;
dest->register_name_ = regname;
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 ®,
- 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;
}
}
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()
}
TEST_CASE("list all kernel symbols", "[c_api]") {
- REQUIRE(geteuid() == 0);
+ if (geteuid() != 0)
+ return;
bcc_procutils_each_ksym(_test_ksym, NULL);
}
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());
}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}