From 9259841a5d623e9044afc1bef6584c31be42a5e2 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Thu, 28 Apr 2016 19:42:55 +0200 Subject: [PATCH] cc: Finish USDT implementation --- src/cc/usdt.cc | 127 +++++++++++++++++++++++-- src/cc/usdt.h | 63 +++++++++++-- src/cc/usdt_args.cc | 173 ++++++++++++++++++++++++---------- tests/cc/CMakeLists.txt | 12 ++- tests/cc/test_c_api.cc | 3 +- tests/cc/test_usdt_args.cc | 18 ++-- tests/cc/test_usdt_probes.cc | 219 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 541 insertions(+), 74 deletions(-) create mode 100644 tests/cc/test_usdt_probes.cc diff --git a/src/cc/usdt.cc b/src/cc/usdt.cc index 350d6a1..74fe40f 100644 --- a/src/cc/usdt.cc +++ b/src/cc/usdt.cc @@ -15,7 +15,12 @@ */ #include +#include +#include +#include + #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(address), SEEK_SET) < 0 || + ::read(memfd, &original, 2) != 2) { + ::close(memfd); + return false; + } + + original = original + val; + + if (::lseek(memfd, static_cast(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 &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; +} } diff --git a/src/cc/usdt.h b/src/cc/usdt.h index e975632..3422106 100644 --- a/src/cc/usdt.h +++ b/src/cc/usdt.h @@ -19,6 +19,7 @@ #include #include +#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 &pid) const; - static const std::unordered_map 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 &deref_ident() const { return deref_ident_; } const optional ®ister_name() const { return register_name_; } @@ -64,7 +63,7 @@ class ArgumentParser { 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 *number); ssize_t parse_identifier(ssize_t pos, optional *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 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 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 locations_; + std::unordered_map semaphores_; + std::unordered_map enabled_semaphores_; + optional 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 &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 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); }; } diff --git a/src/cc/usdt_args.cc b/src/cc/usdt_args.cc index a0b92c1..01f8d7c 100644 --- a/src/cc/usdt_args.cc +++ b/src/cc/usdt_args.cc @@ -27,37 +27,16 @@ namespace USDT { Argument::Argument() {} Argument::~Argument() {} -const std::unordered_map 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 &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 &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; } @@ -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, ®size)) + if (!normalize_register(®name, ®size)) return -start; dest->register_name_ = regname; @@ -219,26 +197,125 @@ bool ArgumentParser::parse(Argument *dest) { return true; } -const std::unordered_map 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 + 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; } } diff --git a/tests/cc/CMakeLists.txt b/tests/cc/CMakeLists.txt index cad37a3..7f095e1 100644 --- a/tests/cc/CMakeLists.txt +++ b/tests/cc/CMakeLists.txt @@ -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() diff --git a/tests/cc/test_c_api.cc b/tests/cc/test_c_api.cc index 1b5165e..7f6d3be 100644 --- a/tests/cc/test_c_api.cc +++ b/tests/cc/test_c_api.cc @@ -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); } diff --git a/tests/cc/test_usdt_args.cc b/tests/cc/test_usdt_args.cc index 946e519..57ccb4f 100644 --- a/tests/cc/test_usdt_args.cc +++ b/tests/cc/test_usdt_args.cc @@ -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 index 0000000..37f8387 --- /dev/null +++ b/tests/cc/test_usdt_probes.cc @@ -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 +#include +#include +#include + +#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 + +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); + } + } +} -- 2.7.4