cc: Wrap the USDT probe context in a C API
authorVicent Marti <tanoku@gmail.com>
Sun, 1 May 2016 10:53:46 +0000 (12:53 +0200)
committerVicent Marti <tanoku@gmail.com>
Fri, 6 May 2016 09:00:43 +0000 (11:00 +0200)
src/cc/bcc_usdt.h [new file with mode: 0644]
src/cc/export/helpers.h
src/cc/usdt.cc
src/cc/usdt.h
src/cc/usdt_args.cc
tests/cc/test_usdt_probes.cc

diff --git a/src/cc/bcc_usdt.h b/src/cc/bcc_usdt.h
new file mode 100644 (file)
index 0000000..e303da8
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+#ifndef LIBBCC_USDT_H
+#define LIBBCC_USDT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+
+void *bcc_usdt_new_frompid(int pid);
+void *bcc_usdt_new_frompath(const char *path);
+void bcc_usdt_close(void *usdt);
+
+int bcc_usdt_enable_probe(void *, const char *, const char *);
+char *bcc_usdt_genargs(void *);
+
+typedef void (*bcc_usdt_uprobe_cb)(const char *, const char *, uint64_t, int);
+void bcc_usdt_foreach_uprobe(void *usdt, bcc_usdt_uprobe_cb callback);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
index 86fd88f..ac7d8cf 100644 (file)
@@ -434,5 +434,10 @@ int bpf_num_cpus() asm("llvm.bpf.extra");
 #error "bcc does not support this platform yet"
 #endif
 
+#define bpf_usdt_readarg(probearg, ctx) _bpf_readarg_##probearg(ctx)
+#define bpf_usdt_readarg_p(probearg, ctx, buf, len) {\
+  u64 __addr = bpf_usdt_readarg(probearg, ctx); \
+  bpf_probe_read(buf, len, (void *)__addr); }
+
 #endif
 )********"
index 74fe40f..a76b632 100644 (file)
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 #include <sstream>
+#include <cstring>
 
 #include <fcntl.h>
 #include <sys/types.h>
@@ -29,12 +30,10 @@ 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
+    Argument arg;
+    if (!parser.parse(&arg))
       continue;
-    }
-    arguments_.push_back(arg);
+    arguments_.push_back(std::move(arg));
   }
 }
 
@@ -51,6 +50,16 @@ bool Probe::in_shared_object() {
   return in_shared_object_.value();
 }
 
+bool Probe::resolve_global_address(uint64_t *global, const uint64_t addr, optional<int> pid) {
+  if (in_shared_object()) {
+    return (pid &&
+       bcc_resolve_global_addr(*pid, bin_path_.c_str(), addr, global) == 0);
+  }
+
+  *global = addr;
+  return true;
+}
+
 bool Probe::lookup_semaphore_addr(uint64_t *address, int pid) {
   auto it = semaphores_.find(pid);
   if (it != semaphores_.end()) {
@@ -58,12 +67,8 @@ bool Probe::lookup_semaphore_addr(uint64_t *address, int pid) {
     return true;
   }
 
-  if (in_shared_object()) {
-    uint64_t load_address = 0x0;  // TODO
-    *address = load_address + semaphore_;
-  } else {
-    *address = semaphore_;
-  }
+  if (!resolve_global_address(address, semaphore_, pid))
+    return false;
 
   semaphores_[pid] = *address;
   return true;
@@ -100,10 +105,12 @@ bool Probe::add_to_semaphore(int pid, int16_t val) {
 }
 
 bool Probe::enable(int pid) {
+  if (enabled_semaphores_.find(pid) != enabled_semaphores_.end())
+    return true;
+
   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;
 }
@@ -137,15 +144,8 @@ bool Probe::usdt_cases(std::ostream &stream, const optional<int> &pid) {
   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);
+    tfm::format(stream, "%s arg%d = 0;\n",
+       largest_arg_type(arg_n), arg_n + 1);
   }
 
   for (size_t loc_n = 0; loc_n < locations_.size(); ++loc_n) {
@@ -153,8 +153,8 @@ bool Probe::usdt_cases(std::ostream &stream, const optional<int> &pid) {
     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),
+      Argument &arg = location.arguments_[arg_n];
+      if (!arg.assign_to_local(stream, tfm::format("arg%d", arg_n + 1),
                                 bin_path_, pid))
         return false;
     }
@@ -163,6 +163,61 @@ bool Probe::usdt_cases(std::ostream &stream, const optional<int> &pid) {
   return true;
 }
 
+std::string Probe::largest_arg_type(size_t 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;
+  }
+
+  assert(largest);
+  return largest->ctype();
+}
+
+bool Probe::usdt_getarg(std::ostream &stream, const optional<int> &pid) {
+  const size_t arg_count = locations_[0].arguments_.size();
+
+  if (arg_count == 0)
+    return true;
+
+  for (size_t arg_n = 0; arg_n < arg_count; ++arg_n) {
+    std::string ctype = largest_arg_type(arg_n);
+    tfm::format(stream,
+      "static inline %s _bpf_readarg_%s_%d(struct pt_regs *ctx) {\n"
+      "  %s result = 0x0;\n",
+      ctype, name_, arg_n + 1, ctype);
+
+    if (locations_.size() == 1) {
+      Location &location = locations_.front();
+      stream << "  ";
+      if (!location.arguments_[arg_n].assign_to_local(
+         stream, "result", bin_path_, pid))
+       return false;
+      stream << "\n";
+    } else {
+      stream << "  switch(ctx->ip) {\n";
+      for (Location &location : locations_) {
+       uint64_t global_address;
+
+       if (!resolve_global_address(&global_address, location.address_, pid))
+         return false;
+
+       tfm::format(stream, "  case 0x%xULL: ", global_address);
+       if (!location.arguments_[arg_n].assign_to_local(
+           stream, "result", bin_path_, pid))
+         return false;
+
+       stream << " break;\n";
+      }
+      stream << "  }\n";
+    }
+    stream << "  return result;\n}\n";
+  }
+  return true;
+}
+
 void Probe::add_location(uint64_t addr, const char *fmt) {
   locations_.emplace_back(addr, fmt);
 }
@@ -210,7 +265,7 @@ std::string Context::resolve_bin_path(const std::string &bin_path) {
   return result;
 }
 
-Probe *Context::find_probe(const std::string &probe_name) {
+Probe *Context::get(const std::string &probe_name) const {
   for (Probe *p : probes_) {
     if (p->name_ == probe_name)
       return p;
@@ -218,6 +273,41 @@ Probe *Context::find_probe(const std::string &probe_name) {
   return nullptr;
 }
 
+bool Context::generate_usdt_args(std::ostream &stream) {
+  stream << "#include <uapi/linux/ptrace.h>\n";
+  for (auto &p : uprobes_) {
+    if (!p.first->usdt_getarg(stream, pid_))
+      return false;
+  }
+  return true;
+}
+
+bool Context::enable_probe(const std::string &probe_name, const std::string &fn_name) {
+  Probe *p = get(probe_name);
+  if (!p)
+    return false;
+
+  if (p->need_enable()) {
+    if (!pid_ || !p->enable(pid_.value()))
+      return false;
+  }
+
+  uprobes_.emplace_back(p, fn_name);
+  return true;
+}
+
+void Context::each_uprobe(each_uprobe_cb callback) {
+  for (auto &p : uprobes_) {
+    for (Probe::Location &loc : p.first->locations_) {
+      callback(
+         p.first->bin_path_.c_str(),
+         p.second.c_str(),
+         loc.address_,
+         pid_.value_or(-1));
+    }
+  }
+}
+
 Context::Context(const std::string &bin_path) : loaded_(false) {
   std::string full_path = resolve_bin_path(bin_path);
   if (!full_path.empty()) {
@@ -226,8 +316,63 @@ Context::Context(const std::string &bin_path) : loaded_(false) {
   }
 }
 
-Context::Context(int pid) : loaded_(false) {
+Context::Context(int pid) : pid_(pid), loaded_(false) {
   if (bcc_procutils_each_module(pid, _each_module, this) == 0)
     loaded_ = true;
 }
+
+Context::~Context() {
+  for (Probe *p : probes_) {
+    if (pid_ && p->enabled())
+      p->disable(pid_.value());
+    delete p;
+  }
+}
+
+}
+
+extern "C" {
+#include "bcc_usdt.h"
+
+void *bcc_usdt_new_frompid(int pid) {
+  USDT::Context *ctx = new USDT::Context(pid);
+  if (!ctx->loaded()) {
+    delete ctx;
+    return nullptr;
+  }
+  return static_cast<void *>(ctx);
+}
+
+void *bcc_usdt_new_frompath(const char *path) {
+  USDT::Context *ctx = new USDT::Context(path);
+  if (!ctx->loaded()) {
+    delete ctx;
+    return nullptr;
+  }
+  return static_cast<void *>(ctx);
+}
+
+void bcc_usdt_close(void *usdt) {
+  USDT::Context *ctx = static_cast<USDT::Context *>(usdt);
+  delete ctx;
+}
+
+int bcc_usdt_enable_probe(void *usdt, const char *probe_name, const char *fn_name) {
+  USDT::Context *ctx = static_cast<USDT::Context *>(usdt);
+  return ctx->enable_probe(probe_name, fn_name) ? 0 : -1;
+}
+
+char *bcc_usdt_genargs(void *usdt) {
+  USDT::Context *ctx = static_cast<USDT::Context *>(usdt);
+  std::ostringstream stream;
+  if (!ctx->generate_usdt_args(stream))
+    return nullptr;
+  return strdup(stream.str().c_str());
+}
+
+void bcc_usdt_foreach_uprobe(void *usdt, bcc_usdt_uprobe_cb callback) {
+  USDT::Context *ctx = static_cast<USDT::Context *>(usdt);
+  ctx->each_uprobe(callback);
+}
+
 }
index 3422106..0fd937d 100644 (file)
@@ -122,7 +122,7 @@ class Probe {
 
   struct Location {
     uint64_t address_;
-    std::vector<Argument *> arguments_;
+    std::vector<Argument> arguments_;
     Location(uint64_t addr, const char *arg_fmt);
   };
 
@@ -131,7 +131,10 @@ class Probe {
   std::unordered_map<int, ProcStat> enabled_semaphores_;
   optional<bool> in_shared_object_;
 
+  std::string largest_arg_type(size_t arg_n);
+
   bool add_to_semaphore(int pid, int16_t val);
+  bool resolve_global_address(uint64_t *global, const uint64_t addr, optional<int> pid);
   bool lookup_semaphore_addr(uint64_t *address, int pid);
   void add_location(uint64_t addr, const char *fmt);
 
@@ -142,12 +145,17 @@ public:
   size_t num_locations() const { return locations_.size(); }
   size_t num_arguments() const { return locations_.front().arguments_.size(); }
 
+  uint64_t address(size_t n = 0) const { return locations_[n].address_; }
+
   bool usdt_thunks(std::ostream &stream, const std::string &prefix);
   bool usdt_cases(std::ostream &stream, const optional<int> &pid = nullopt);
+  bool usdt_getarg(std::ostream &stream, const optional<int> &pid = nullopt);
 
   bool need_enable() const { return semaphore_ != 0x0; }
   bool enable(int pid);
   bool disable(int pid);
+  bool enabled() const { return !enabled_semaphores_.empty(); }
+
 
   bool in_shared_object();
   const std::string &name() { return name_; }
@@ -159,6 +167,8 @@ public:
 
 class Context {
   std::vector<Probe *> probes_;
+  std::vector<std::pair<Probe *, std::string>> uprobes_;
+  optional<int> pid_;
   bool loaded_;
 
   static void _each_probe(const char *binpath, const struct bcc_elf_usdt *probe,
@@ -171,9 +181,19 @@ class Context {
 public:
   Context(const std::string &bin_path);
   Context(int pid);
+  ~Context();
 
+  optional<int> pid() const { return pid_; }
   bool loaded() const { return loaded_; }
   size_t num_probes() const { return probes_.size(); }
-  Probe *find_probe(const std::string &probe_name);
+
+  Probe *get(const std::string &probe_name) const;
+  Probe *get(int pos) const { return probes_[pos]; }
+
+  bool enable_probe(const std::string &probe_name, const std::string &fn_name);
+  bool generate_usdt_args(std::ostream &stream);
+
+  typedef void (*each_uprobe_cb)(const char *, const char *, uint64_t, int);
+  void each_uprobe(each_uprobe_cb callback);
 };
 }
index 01f8d7c..679f0ef 100644 (file)
@@ -55,37 +55,35 @@ bool Argument::assign_to_local(std::ostream &stream,
                                const std::string &binpath,
                                const optional<int> &pid) const {
   if (constant_) {
-    tfm::format(stream, "%s = %d;\n", local_name, *constant_);
+    tfm::format(stream, "%s = %d;", local_name, *constant_);
     return true;
   }
 
   if (!deref_offset_) {
-    tfm::format(stream, "%s = (%s)ctx->%s;\n", local_name, ctype(),
+    tfm::format(stream, "%s = (%s)ctx->%s;", 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);
+                "{ u64 __addr = ctx->%s + (%d); %s __res = 0x0; "
+                "bpf_probe_read(&__res, sizeof(__res), (void *)__addr); "
+                               "%s = __res; }",
+                *register_name_, *deref_offset_, ctype(), local_name);
     return true;
   }
 
-  if (deref_offset_ && deref_ident_) {
+  if (deref_offset_ && deref_ident_ && *register_name_ == "ip") {
     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);
+                "{ u64 __addr = 0x%xull + %d; %s __res = 0x0; "
+                "bpf_probe_read(&__res, sizeof(__res), (void *)__addr); "
+                               "%s = __res; }",
+                global_address, *deref_offset_, ctype(), local_name);
     return true;
   }
 
index 37f8387..24ddc52 100644 (file)
@@ -39,7 +39,7 @@ TEST_CASE("test finding a probe in our own process", "[usdt]") {
   REQUIRE(ctx.num_probes() >= 1);
 
   SECTION("our test probe") {
-    USDT::Probe *probe = ctx.find_probe("sample_probe_1");
+    USDT::Probe *probe = ctx.get("sample_probe_1");
     REQUIRE(probe != nullptr);
 
     REQUIRE(probe->in_shared_object() == false);
@@ -115,7 +115,7 @@ TEST_CASE("test listing all USDT probes in Ruby/MRI", "[usdt]") {
     mri_probe_count = ctx.num_probes();
 
     SECTION("GC static probe") {
-      USDT::Probe *probe = ctx.find_probe("gc__mark__begin");
+      USDT::Probe *probe = ctx.get("gc__mark__begin");
       REQUIRE(probe != nullptr);
 
       REQUIRE(probe->in_shared_object() == true);
@@ -129,7 +129,7 @@ TEST_CASE("test listing all USDT probes in Ruby/MRI", "[usdt]") {
     }
 
     SECTION("object creation probe") {
-      USDT::Probe *probe = ctx.find_probe("object__create");
+      USDT::Probe *probe = ctx.get("object__create");
       REQUIRE(probe != nullptr);
 
       REQUIRE(probe->in_shared_object() == true);
@@ -161,7 +161,7 @@ TEST_CASE("test listing all USDT probes in Ruby/MRI", "[usdt]") {
     }
 
     SECTION("array creation probe") {
-      USDT::Probe *probe = ctx.find_probe("array__create");
+      USDT::Probe *probe = ctx.get("array__create");
       REQUIRE(probe != nullptr);
       REQUIRE(probe->name() == "array__create");
 
@@ -203,7 +203,7 @@ TEST_CASE("test listing all USDT probes in Ruby/MRI", "[usdt]") {
     REQUIRE(ctx.num_probes() >= mri_probe_count);
 
     SECTION("get probe in running process") {
-      USDT::Probe *probe = ctx.find_probe("gc__mark__begin");
+      USDT::Probe *probe = ctx.get("gc__mark__begin");
       REQUIRE(probe != nullptr);
 
       REQUIRE(probe->in_shared_object() == true);