Commit 751ac54f authored by 4ast's avatar 4ast

Merge pull request #498 from vmg/vmg/usdt

[WIP] Native implementation for USDT probes
parents 55513a3a 9259841a
......@@ -2,3 +2,4 @@
BasedOnStyle: Google
AllowShortIfStatementsOnASingleLine: false
IndentCaseLabels: false
AccessModifierOffset: -2
......@@ -33,12 +33,12 @@ if (CMAKE_COMPILER_IS_GNUCC)
endif()
endif()
add_library(bcc-shared SHARED bpf_common.cc bpf_module.cc libbpf.c perf_reader.c shared_table.cc exported_files.cc bcc_elf.c bcc_proc.c bcc_syms.cc)
add_library(bcc-shared SHARED bpf_common.cc bpf_module.cc libbpf.c perf_reader.c shared_table.cc exported_files.cc bcc_elf.c bcc_proc.c bcc_syms.cc usdt_args.cc usdt.cc)
set_target_properties(bcc-shared PROPERTIES VERSION ${REVISION_LAST} SOVERSION 0)
set_target_properties(bcc-shared PROPERTIES OUTPUT_NAME bcc)
add_library(bcc-loader-static libbpf.c perf_reader.c bcc_elf.c bcc_proc.c)
add_library(bcc-static STATIC bpf_common.cc bpf_module.cc shared_table.cc exported_files.cc bcc_syms.cc)
add_library(bcc-static STATIC bpf_common.cc bpf_module.cc shared_table.cc exported_files.cc bcc_syms.cc usdt_args.cc usdt.cc)
set_target_properties(bcc-static PROPERTIES OUTPUT_NAME bcc)
# BPF is still experimental otherwise it should be available
......
......@@ -20,6 +20,8 @@
extern "C" {
#endif
#include <stdint.h>
struct bcc_elf_usdt {
uint64_t pc;
uint64_t base_addr;
......
......@@ -100,8 +100,10 @@ int bcc_procutils_each_module(int pid, bcc_procutils_modulecb callback,
while (isspace(mapname[0])) mapname++;
if (strchr(perm, 'x') && mapname[0] && mapname[0] != '[')
callback(mapname, (uint64_t)begin, (uint64_t)end, payload);
if (strchr(perm, 'x') && mapname[0] && mapname[0] != '[') {
if (callback(mapname, (uint64_t)begin, (uint64_t)end, payload) < 0)
break;
}
}
} while (ret && ret != EOF);
......
......@@ -22,8 +22,9 @@
extern "C" {
#endif
typedef void (*bcc_procutils_modulecb)(const char *, uint64_t, uint64_t,
void *);
#include <stdint.h>
typedef int (*bcc_procutils_modulecb)(const char *, uint64_t, uint64_t, void *);
typedef void (*bcc_procutils_ksymcb)(const char *, uint64_t, void *);
const char *bcc_procutils_which_so(const char *libname);
......
......@@ -13,56 +13,41 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <vector>
#include <string>
#include <algorithm>
#include <unordered_map>
#include <sys/types.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include "bcc_syms.h"
#include "bcc_proc.h"
#include "bcc_elf.h"
#include "bcc_proc.h"
#include "bcc_syms.h"
class SymbolCache {
public:
virtual void refresh() = 0;
virtual bool resolve_addr(uint64_t addr, struct bcc_symbol *sym) = 0;
virtual bool resolve_name(const char *name, uint64_t *addr) = 0;
};
class KSyms : SymbolCache {
struct Symbol {
Symbol(const char *name, uint64_t addr) : name(name), addr(addr) {}
std::string name;
uint64_t addr;
bool operator<(const Symbol &rhs) const { return addr < rhs.addr; }
};
std::vector<Symbol> syms_;
std::unordered_map<std::string, uint64_t> symnames_;
static void _add_symbol(const char *, uint64_t, void *);
public:
virtual bool resolve_addr(uint64_t addr, struct bcc_symbol *sym);
virtual bool resolve_name(const char *name, uint64_t *addr);
virtual void refresh() {
if (syms_.empty()) {
bcc_procutils_each_ksym(_add_symbol, this);
std::sort(syms_.begin(), syms_.end());
}
}
};
#include "syms.h"
ino_t ProcStat::getinode_() {
struct stat s;
return (!stat(procfs_.c_str(), &s)) ? s.st_ino : -1;
}
ProcStat::ProcStat(int pid) : inode_(-1) {
char buffer[128];
snprintf(buffer, sizeof(buffer), "/proc/%d/exe", pid);
procfs_ = buffer;
}
void KSyms::_add_symbol(const char *symname, uint64_t addr, void *p) {
KSyms *ks = static_cast<KSyms *>(p);
ks->syms_.emplace_back(symname, addr);
}
void KSyms::refresh() {
if (syms_.empty()) {
bcc_procutils_each_ksym(_add_symbol, this);
std::sort(syms_.begin(), syms_.end());
}
}
bool KSyms::resolve_addr(uint64_t addr, struct bcc_symbol *sym) {
refresh();
......@@ -80,7 +65,8 @@ bool KSyms::resolve_addr(uint64_t addr, struct bcc_symbol *sym) {
return true;
}
bool KSyms::resolve_name(const char *name, uint64_t *addr) {
bool KSyms::resolve_name(const char *_unused, const char *name,
uint64_t *addr) {
refresh();
if (syms_.size() != symnames_.size()) {
......@@ -98,65 +84,6 @@ bool KSyms::resolve_name(const char *name, uint64_t *addr) {
return true;
}
class ProcStat {
std::string procfs_;
ino_t inode_;
ino_t getinode_() {
struct stat s;
return (!stat(procfs_.c_str(), &s)) ? s.st_ino : -1;
}
public:
ProcStat(int pid) : inode_(-1) {
char buffer[128];
snprintf(buffer, sizeof(buffer), "/proc/%d/exe", pid);
procfs_ = buffer;
}
bool is_stale() { return inode_ != getinode_(); }
void reset() { inode_ = getinode_(); }
};
class ProcSyms : SymbolCache {
struct Symbol {
Symbol(const char *name, uint64_t start, uint64_t size, int flags = 0)
: name(name), start(start), size(size), flags(flags) {}
std::string name;
uint64_t start;
uint64_t size;
int flags;
};
struct Module {
Module(const char *name, uint64_t start, uint64_t end)
: name_(name), start_(start), end_(end) {}
std::string name_;
uint64_t start_;
uint64_t end_;
std::vector<Symbol> syms_;
void load_sym_table();
bool decode_sym(uint64_t addr, struct bcc_symbol *sym);
bool is_so() { return strstr(name_.c_str(), ".so") != nullptr; }
static int _add_symbol(const char *symname, uint64_t start, uint64_t end,
int flags, void *p);
};
int pid_;
std::vector<Module> modules_;
ProcStat procstat_;
static void _add_module(const char *, uint64_t, uint64_t, void *);
public:
ProcSyms(int pid);
virtual void refresh();
virtual bool resolve_addr(uint64_t addr, struct bcc_symbol *sym);
virtual bool resolve_name(const char *name, uint64_t *addr);
};
ProcSyms::ProcSyms(int pid) : pid_(pid), procstat_(pid) { refresh(); }
void ProcSyms::refresh() {
......@@ -165,10 +92,11 @@ void ProcSyms::refresh() {
procstat_.reset();
}
void ProcSyms::_add_module(const char *modname, uint64_t start, uint64_t end,
void *payload) {
int ProcSyms::_add_module(const char *modname, uint64_t start, uint64_t end,
void *payload) {
ProcSyms *ps = static_cast<ProcSyms *>(payload);
ps->modules_.emplace_back(modname, start, end);
return 0;
}
bool ProcSyms::resolve_addr(uint64_t addr, struct bcc_symbol *sym) {
......@@ -181,13 +109,20 @@ bool ProcSyms::resolve_addr(uint64_t addr, struct bcc_symbol *sym) {
for (Module &mod : modules_) {
if (addr >= mod.start_ && addr <= mod.end_)
return mod.decode_sym(addr, sym);
return mod.find_addr(addr, sym);
}
return false;
}
bool ProcSyms::resolve_name(const char *name, uint64_t *addr) {
*addr = 0x0;
bool ProcSyms::resolve_name(const char *module, const char *name,
uint64_t *addr) {
if (procstat_.is_stale())
refresh();
for (Module &mod : modules_) {
if (mod.name_ == module)
return mod.find_name(name, addr);
}
return false;
}
......@@ -198,6 +133,10 @@ int ProcSyms::Module::_add_symbol(const char *symname, uint64_t start,
return 0;
}
bool ProcSyms::Module::is_so() const {
return strstr(name_.c_str(), ".so") != nullptr;
}
void ProcSyms::Module::load_sym_table() {
if (syms_.size())
return;
......@@ -205,7 +144,19 @@ void ProcSyms::Module::load_sym_table() {
bcc_elf_foreach_sym(name_.c_str(), _add_symbol, this);
}
bool ProcSyms::Module::decode_sym(uint64_t addr, struct bcc_symbol *sym) {
bool ProcSyms::Module::find_name(const char *symname, uint64_t *addr) {
load_sym_table();
for (Symbol &s : syms_) {
if (s.name == symname) {
*addr = is_so() ? start_ + s.start : s.start;
return true;
}
}
return false;
}
bool ProcSyms::Module::find_addr(uint64_t addr, struct bcc_symbol *sym) {
uint64_t offset = is_so() ? (addr - start_) : addr;
load_sym_table();
......@@ -240,7 +191,7 @@ int bcc_symcache_resolve(void *resolver, uint64_t addr,
int bcc_symcache_resolve_name(void *resolver, const char *name,
uint64_t *addr) {
SymbolCache *cache = static_cast<SymbolCache *>(resolver);
return cache->resolve_name(name, addr) ? 0 : -1;
return cache->resolve_name(nullptr, name, addr) ? 0 : -1;
}
void bcc_symcache_refresh(void *resolver) {
......@@ -251,6 +202,7 @@ void bcc_symcache_refresh(void *resolver) {
static int _find_sym(const char *symname, uint64_t addr, uint64_t end,
int flags, void *payload) {
struct bcc_symbol *sym = (struct bcc_symbol *)payload;
// TODO: check for actual function symbol in flags
if (!strcmp(sym->name, symname)) {
sym->offset = addr;
return -1;
......@@ -258,6 +210,10 @@ static int _find_sym(const char *symname, uint64_t addr, uint64_t end,
return 0;
}
int bcc_find_symbol_addr(struct bcc_symbol *sym) {
return bcc_elf_foreach_sym(sym->module, _find_sym, sym);
}
int bcc_resolve_symname(const char *module, const char *symname,
const uint64_t addr, struct bcc_symbol *sym) {
uint64_t load_addr;
......@@ -286,8 +242,10 @@ int bcc_resolve_symname(const char *module, const char *symname,
sym->name = symname;
sym->offset = addr;
if (sym->name && sym->offset == 0x0)
bcc_elf_foreach_sym(sym->module, _find_sym, sym);
if (sym->name && sym->offset == 0x0) {
if (bcc_find_symbol_addr(sym) < 0)
return -1;
}
if (sym->offset == 0x0)
return -1;
......
......@@ -20,6 +20,8 @@
extern "C" {
#endif
#include <stdint.h>
struct bcc_symbol {
const char *name;
const char *module;
......@@ -31,6 +33,7 @@ int bcc_symcache_resolve(void *symcache, uint64_t addr, struct bcc_symbol *sym);
int bcc_symcache_resolve_name(void *resolver, const char *name, uint64_t *addr);
void bcc_symcache_refresh(void *resolver);
int bcc_find_symbol_addr(struct bcc_symbol *sym);
int bcc_resolve_symname(const char *module, const char *symname,
const uint64_t addr, struct bcc_symbol *sym);
#ifdef __cplusplus
......
/*
* 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.
*/
#pragma once
#include <algorithm>
#include <string>
#include <unordered_map>
#include <vector>
#include <sys/types.h>
class ProcStat {
std::string procfs_;
ino_t inode_;
ino_t getinode_();
public:
ProcStat(int pid);
bool is_stale() { return inode_ != getinode_(); }
void reset() { inode_ = getinode_(); }
};
class SymbolCache {
public:
virtual void refresh() = 0;
virtual bool resolve_addr(uint64_t addr, struct bcc_symbol *sym) = 0;
virtual bool resolve_name(const char *module, const char *name,
uint64_t *addr) = 0;
};
class KSyms : SymbolCache {
struct Symbol {
Symbol(const char *name, uint64_t addr) : name(name), addr(addr) {}
std::string name;
uint64_t addr;
bool operator<(const Symbol &rhs) const { return addr < rhs.addr; }
};
std::vector<Symbol> syms_;
std::unordered_map<std::string, uint64_t> symnames_;
static void _add_symbol(const char *, uint64_t, void *);
public:
virtual bool resolve_addr(uint64_t addr, struct bcc_symbol *sym);
virtual bool resolve_name(const char *unused, const char *name,
uint64_t *addr);
virtual void refresh();
};
class ProcSyms : SymbolCache {
struct Symbol {
Symbol(const char *name, uint64_t start, uint64_t size, int flags = 0)
: name(name), start(start), size(size), flags(flags) {}
std::string name;
uint64_t start;
uint64_t size;
int flags;
};
struct Module {
Module(const char *name, uint64_t start, uint64_t end)
: name_(name), start_(start), end_(end) {}
std::string name_;
uint64_t start_;
uint64_t end_;
std::vector<Symbol> syms_;
void load_sym_table();
bool find_addr(uint64_t addr, struct bcc_symbol *sym);
bool find_name(const char *symname, uint64_t *addr);
bool is_so() const;
static int _add_symbol(const char *symname, uint64_t start, uint64_t end,
int flags, void *p);
};
int pid_;
std::vector<Module> modules_;
ProcStat procstat_;
static int _add_module(const char *, uint64_t, uint64_t, void *);
public:
ProcSyms(int pid);
virtual void refresh();
virtual bool resolve_addr(uint64_t addr, struct bcc_symbol *sym);
virtual bool resolve_name(const char *module, const char *name,
uint64_t *addr);
};
/*
* 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 <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"
namespace USDT {
Probe::Location::Location(uint64_t addr, const char *arg_fmt) : address_(addr) {
ArgumentParser_x64 parser(arg_fmt);
while (!parser.done()) {
Argument *arg = new Argument();
if (!parser.parse(arg)) {
delete arg; // TODO: report error
continue;
}
arguments_.push_back(arg);
}
}
Probe::Probe(const char *bin_path, const char *provider, const char *name,
uint64_t semaphore)
: bin_path_(bin_path),
provider_(provider),
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,
"int %s_thunk_%d(struct pt_regs *ctx) { return %s(ctx, %d); }\n",
prefix, i, prefix, i);
}
return true;
}
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 ||
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);
}
for (size_t loc_n = 0; loc_n < locations_.size(); ++loc_n) {
Location &location = locations_[loc_n];
tfm::format(stream, "if (__loc_id == %d) {\n", loc_n);
for (size_t arg_n = 0; arg_n < location.arguments_.size(); ++arg_n) {
Argument *arg = location.arguments_[arg_n];
if (!arg->assign_to_local(stream, tfm::format("arg%d", arg_n + 1),
bin_path_, pid))
return false;
}
stream << "}\n";
}
return true;
}
void Probe::add_location(uint64_t addr, const char *fmt) {
locations_.emplace_back(addr, fmt);
}
void Context::_each_probe(const char *binpath, const struct bcc_elf_usdt *probe,
void *p) {
Context *ctx = static_cast<Context *>(p);
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;
for (Probe *p : probes_) {
if (p->provider_ == probe->provider && p->name_ == probe->name) {
found_probe = p;
break;
}
}
if (!found_probe) {
found_probe =
new Probe(binpath, probe->provider, probe->name, probe->semaphore);
probes_.push_back(found_probe);
}
found_probe->add_location(probe->pc, probe->arg_fmt);
}
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) : 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) : loaded_(false) {
if (bcc_procutils_each_module(pid, _each_module, this) == 0)
loaded_ = true;
}
}
/*
* 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.
*/
#pragma once
#include <string>
#include <unordered_map>
#include <vector>
#include "syms.h"
#include "vendor/optional.hpp"
namespace USDT {
using std::experimental::optional;
using std::experimental::nullopt;
class ArgumentParser;
class Argument {
private:
optional<int> arg_size_;
optional<int> constant_;
optional<int> deref_offset_;
optional<std::string> deref_ident_;
optional<std::string> register_name_;
bool get_global_address(uint64_t *address, const std::string &binpath,
const optional<int> &pid) const;
public:
Argument();
~Argument();
bool assign_to_local(std::ostream &stream, const std::string &local_name,
const std::string &binpath,
const optional<int> &pid = nullopt) const;
int arg_size() const { return arg_size_.value_or(sizeof(void *)); }
std::string ctype() const;
const optional<std::string> &deref_ident() const { return deref_ident_; }
const optional<std::string> &register_name() const { return register_name_; }
const optional<int> constant() const { return constant_; }
const optional<int> deref_offset() const { return deref_offset_; }
friend class ArgumentParser;
};
class ArgumentParser {
const char *arg_;
ssize_t cur_pos_;
protected:
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);
ssize_t parse_register(ssize_t pos, Argument *dest);
ssize_t parse_expr(ssize_t pos, Argument *dest);
ssize_t parse_1(ssize_t pos, Argument *dest);
void print_error(ssize_t pos);
public:
bool parse(Argument *dest);
bool done() { return cur_pos_ < 0 || arg_[cur_pos_] == '\0'; }
ArgumentParser(const char *arg) : arg_(arg), cur_pos_(0) {}
};
class ArgumentParser_x64 : public ArgumentParser {
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) {}
};
class Probe {
std::string bin_path_;
std::string provider_;
std::string name_;
uint64_t semaphore_;
struct Location {
uint64_t address_;
std::vector<Argument *> arguments_;
Location(uint64_t addr, const char *arg_fmt);
};
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);
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);
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);
};
}
/*
* 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 <unordered_map>
#include "syms.h"
#include "usdt.h"
#include "vendor/tinyformat.hpp"
#include "bcc_elf.h"
#include "bcc_syms.h"
namespace USDT {
Argument::Argument() {}
Argument::~Argument() {}
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);
}
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);
}
if (bcc_elf_is_shared_obj(binpath.c_str()) == 0) {
struct bcc_symbol sym = {deref_ident_->c_str(), binpath.c_str(), 0x0};
if (!bcc_find_symbol_addr(&sym) && sym.offset) {
*address = sym.offset;
return true;
}
}
return false;
}
bool Argument::assign_to_local(std::ostream &stream,
const std::string &local_name,
const std::string &binpath,
const optional<int> &pid) const {
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(),
*register_name_);
return true;
}
if (deref_offset_ && !deref_ident_) {
tfm::format(stream,
"{\n"
" u64 __temp = ctx->%s + (%d);\n"
" bpf_probe_read(&%s, sizeof(%s), (void *)__temp);\n"
"}\n",
*register_name_, *deref_offset_, local_name, local_name);
return true;
}
if (deref_offset_ && deref_ident_) {
uint64_t global_address;
if (!get_global_address(&global_address, binpath, pid))
return false;
tfm::format(stream,
"{\n"
" u64 __temp = 0x%xull + %d;\n"
" bpf_probe_read(&%s, sizeof(%s), (void *)__temp);\n"
"}\n",
global_address, *deref_offset_, local_name, local_name);
return true;
}
return false;
}
ssize_t ArgumentParser::parse_number(ssize_t pos, optional<int> *result) {
char *endp;
int number = strtol(arg_ + pos, &endp, 0);
if (endp > arg_ + pos)
*result = number;
return endp - arg_;
}
ssize_t ArgumentParser::parse_identifier(ssize_t pos,
optional<std::string> *result) {
if (isalpha(arg_[pos]) || arg_[pos] == '_') {
ssize_t start = pos++;
while (isalnum(arg_[pos]) || arg_[pos] == '_') pos++;
if (pos - start)
result->emplace(arg_ + start, pos - start);
}
return pos;
}
ssize_t ArgumentParser::parse_register(ssize_t pos, Argument *dest) {
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 (!normalize_register(&regname, &regsize))
return -start;
dest->register_name_ = regname;
if (!dest->arg_size_)
dest->arg_size_ = regsize;
return pos;
}
ssize_t ArgumentParser::parse_expr(ssize_t pos, Argument *dest) {
if (arg_[pos] == '$')
return parse_number(pos + 1, &dest->constant_);
if (arg_[pos] == '%')
return parse_register(pos, dest);
if (isdigit(arg_[pos]) || arg_[pos] == '-') {
pos = parse_number(pos, &dest->deref_offset_);
if (arg_[pos] == '+') {
pos = parse_identifier(pos + 1, &dest->deref_ident_);
if (!dest->deref_ident_)
return -pos;
}
} else {
dest->deref_offset_ = 0;
pos = parse_identifier(pos, &dest->deref_ident_);
}
if (arg_[pos] != '(')
return -pos;
pos = parse_register(pos + 1, dest);
if (pos < 0)
return pos;
return (arg_[pos] == ')') ? pos + 1 : -pos;
}
ssize_t ArgumentParser::parse_1(ssize_t pos, Argument *dest) {
if (isdigit(arg_[pos]) || arg_[pos] == '-') {
optional<int> asize;
ssize_t m = parse_number(pos, &asize);
if (arg_[m] == '@' && asize) {
dest->arg_size_ = asize;
return parse_expr(m + 1, dest);
}
}
return parse_expr(pos, dest);
}
void ArgumentParser::print_error(ssize_t pos) {
fprintf(stderr, "Parse error:\n %s\n", arg_);
for (ssize_t i = 0; i < pos + 4; ++i) fputc('-', stderr);
fputc('^', stderr);
fputc('\n', stderr);
}
bool ArgumentParser::parse(Argument *dest) {
if (done())
return false;
ssize_t res = parse_1(cur_pos_, dest);
if (res < 0) {
print_error(-res);
return false;
}
if (!isspace(arg_[res]) && arg_[res] != '\0') {
print_error(res);
return false;
}
while (isspace(arg_[res])) res++;
cur_pos_ = res;
return true;
}
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::normalize_register(std::string *reg, int *reg_size) {
auto it = registers_.find(*reg);
if (it == registers_.end())
return false;
*reg_size = it->second.size;
reg_to_name(reg, it->second.reg);
return true;
}
}
This diff is collapsed.
This diff is collapsed.
......@@ -8,7 +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_c_api test_c_api.c)
target_link_libraries(test_c_api bcc-shared dl)
add_executable(test_libbcc
test_libbcc.cc
test_c_api.cc
test_usdt_args.cc
test_usdt_probes.cc)
add_test(NAME test_c_api COMMAND ${TEST_WRAPPER} c_test_api sudo ${CMAKE_CURRENT_BINARY_DIR}/test_c_api)
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()
This diff is collapsed.
This diff is collapsed.
#include <stdint.h>
#include <unistd.h>
#include <dlfcn.h>
#include "sput.h"
#include "bcc_elf.h"
#include "bcc_proc.h"
#include "bcc_syms.h"
static void test_procutils__which_so(void) {
const char *libm = bcc_procutils_which_so("m");
sput_fail_unless(libm, "find libm");
sput_fail_unless(libm[0] == '/', "resolve libm absolute path");
sput_fail_unless(strstr(libm, "libm.so"), "resolve libm so");
}
static void test_procutils__which(void) {
char *ld = bcc_procutils_which("ld");
sput_fail_unless(ld, "find `ld` binary");
sput_fail_unless(ld[0] == '/', "find `ld` absolute path");
free(ld);
}
static void _test_ksym(const char *sym, uint64_t addr, void *_) {
if (!strcmp(sym, "startup_64")) {
sput_fail_unless(addr == 0xffffffff81000000ull, "ksym `startup_64`");
} else if (!strcmp(sym, "__per_cpu_start"))
sput_fail_unless(addr == 0x0, "ksym `__per_cpu_start`");
}
static void test_procutils__each_ksym(void) {
sput_fail_unless(geteuid() == 0, "ensure we are root");
bcc_procutils_each_ksym(_test_ksym, NULL);
}
static void test_syms__resolve_symname(void) {
struct bcc_symbol sym;
sput_fail_unless(bcc_resolve_symname("c", "malloc", 0x0, &sym) == 0,
"bcc_resolve_symname(c, malloc)");
sput_fail_unless(strstr(sym.module, "libc.so"), "resolve to module");
sput_fail_unless(sym.module[0] == '/', "resolve to abspath");
sput_fail_unless(sym.offset != 0, "resolve sym offset");
}
static void test_syms__resolver_pid(void) {
struct bcc_symbol sym;
void *resolver = bcc_symcache_new(getpid());
sput_fail_unless(resolver, "create a new resolver for PID");
sput_fail_unless(bcc_symcache_resolve(
resolver, (uint64_t)&test_syms__resolver_pid, &sym) == 0,
"resolve the current function address");
char *this_exe = realpath("/proc/self/exe", NULL);
sput_fail_unless(strcmp(this_exe, sym.module) == 0,
"resolve a function to our own binary");
free(this_exe);
sput_fail_unless(strcmp("test_syms__resolver_pid", sym.name) == 0,
"resolve a function to its actual name");
void *libbcc = dlopen("libbcc.so", RTLD_LAZY | RTLD_NOLOAD);
sput_fail_unless(libbcc, "dlopen(libbcc.so)");
void *libbcc_fptr = dlsym(libbcc, "bcc_resolve_symname");
sput_fail_unless(libbcc_fptr, "dlsym(bcc_resolve_symname)");
sput_fail_unless(
bcc_symcache_resolve(resolver, (uint64_t)libbcc_fptr, &sym) == 0,
"resolve a function in libbcc in our current process");
sput_fail_unless(strstr(sym.module, "libbcc.so"),
"resolve a function to the loaded libbcc module");
sput_fail_unless(strcmp("bcc_resolve_symname", sym.name) == 0,
"resolve a function in libbcc to its actual name");
void *libc_fptr = dlsym(NULL, "strtok");
sput_fail_unless(libc_fptr, "dlsym(strtok)");
sput_fail_unless(
bcc_symcache_resolve(resolver, (uint64_t)libc_fptr, &sym) == 0,
"resolve a function in libc in our current process");
sput_fail_unless(
sym.module && sym.module[0] == '/' && strstr(sym.module, "libc"),
"resolve a function to linked libc module");
sput_fail_unless(strcmp("strtok", sym.name) == 0,
"resolve a function in libc to its actual name");
}
int main(int argc, char *argv[]) {
sput_start_testing();
sput_enter_suite("procutils: which_so");
sput_run_test(test_procutils__which_so);
sput_enter_suite("procutils: which");
sput_run_test(test_procutils__which);
sput_enter_suite("procutils: each_ksym");
sput_run_test(test_procutils__each_ksym);
sput_enter_suite("syms: resolve_symname");
sput_run_test(test_syms__resolve_symname);
sput_enter_suite("syms: resolver_pid");
sput_run_test(test_syms__resolver_pid);
sput_finish_testing();
return sput_get_return_value();
}
/*
* 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 <stdint.h>
#include <string.h>
#include <unistd.h>
#include "bcc_elf.h"
#include "bcc_proc.h"
#include "bcc_syms.h"
#include "catch.hpp"
using namespace std;
TEST_CASE("shared object resolution", "[c_api]") {
const char *libm = bcc_procutils_which_so("m");
REQUIRE(libm);
REQUIRE(libm[0] == '/');
REQUIRE(string(libm).find("libm.so") != string::npos);
}
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, uint64_t addr, void *_) {
if (!strcmp(sym, "startup_64")) {
REQUIRE(addr == 0xffffffff81000000ull);
} else if (!strcmp(sym, "__per_cpu_start"))
REQUIRE(addr == 0x0);
}
TEST_CASE("list all kernel symbols", "[c_api]") {
if (geteuid() != 0)
return;
bcc_procutils_each_ksym(_test_ksym, NULL);
}
TEST_CASE("resolve symbol name in external library", "[c_api]") {
struct bcc_symbol sym;
REQUIRE(bcc_resolve_symname("c", "malloc", 0x0, &sym) == 0);
REQUIRE(string(sym.module).find("libc.so") != string::npos);
REQUIRE(sym.module[0] == '/');
REQUIRE(sym.offset != 0);
}
extern "C" int _a_test_function(const char *a_string) {
int i;
for (i = 0; a_string[i]; ++i)
;
return i;
}
TEST_CASE("resolve symbol addresses for a given PID", "[c_api]") {
struct bcc_symbol sym;
void *resolver = bcc_symcache_new(getpid());
REQUIRE(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);
}
SECTION("resolve in libbcc.so") {
void *libbcc = dlopen("libbcc.so", 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.so") != string::npos);
REQUIRE(string("bcc_resolve_symname") == 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(string("strtok") == sym.name);
}
}
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
/*
* 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 <iostream>
#include <string>
#include "catch.hpp"
#include "usdt.h"
using std::experimental::optional;
using std::experimental::nullopt;
static void verify_register(USDT::ArgumentParser_x64 &parser, int arg_size,
int constant) {
USDT::Argument arg;
REQUIRE(parser.parse(&arg));
REQUIRE(arg.arg_size() == arg_size);
REQUIRE(arg.constant());
REQUIRE(arg.constant() == constant);
}
static void verify_register(USDT::ArgumentParser_x64 &parser, int arg_size,
const std::string &regname,
optional<int> deref_offset = nullopt,
optional<std::string> deref_ident = nullopt) {
USDT::Argument arg;
REQUIRE(parser.parse(&arg));
REQUIRE(arg.arg_size() == arg_size);
REQUIRE(arg.register_name());
REQUIRE(arg.register_name() == regname);
REQUIRE(arg.deref_offset() == deref_offset);
REQUIRE(arg.deref_ident() == deref_ident);
}
TEST_CASE("test usdt argument parsing", "[usdt]") {
SECTION("argument examples from the Python implementation") {
USDT::ArgumentParser_x64 parser(
"-4@$0 8@$1234 %rdi %rax %rsi "
"-8@%rbx 4@%r12 8@-8(%rbp) 4@(%rax) "
"-4@global_max_action(%rip) "
"8@24+mp_(%rip) ");
verify_register(parser, -4, 0);
verify_register(parser, 8, 1234);
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, "bp", -8);
verify_register(parser, 4, "ax", 0);
verify_register(parser, -4, "ip", 0, std::string("global_max_action"));
verify_register(parser, 8, "ip", 24, std::string("mp_"));
REQUIRE(parser.done());
}
}
/*
* 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);
}
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment