Carl преди 9 месеца
ревизия
3d49bc05ca
променени са 16 файла, в които са добавени 5096 реда и са изтрити 0 реда
  1. 35 0
      .gitignore
  2. 3 0
      .gitmodules
  3. 1 0
      CREDITS
  4. 0 0
      EXPERIMENTAL
  5. 1056 0
      api/BPF.cc
  6. 430 0
      api/BPF.h
  7. 832 0
      api/BPFTable.cc
  8. 670 0
      api/BPFTable.h
  9. 4 0
      api/CMakeLists.txt
  10. 114 0
      config.m4
  11. 13 0
      config.w32
  12. 1 0
      deps/bcc
  13. 1683 0
      ebpf.cpp
  14. 21 0
      ebpf.php
  15. 212 0
      php_ebpf.h
  16. 21 0
      tests/001.phpt

+ 35 - 0
.gitignore

@@ -0,0 +1,35 @@
+*.lo
+*.la
+.libs
+.idea
+acinclude.m4
+aclocal.m4
+autom4te.cache
+build
+config.guess
+config.h
+config.h.in
+config.log
+config.nice
+config.status
+config.sub
+configure
+configure.in
+include
+install-sh
+libtool
+ltmain.sh
+Makefile
+Makefile.fragments
+Makefile.global
+Makefile.objects
+missing
+mkinstalldirs
+modules
+run-tests.php
+tests/*/*.diff
+tests/*/*.out
+tests/*/*.php
+tests/*/*.exp
+tests/*/*.log
+tests/*/*.sh

+ 3 - 0
.gitmodules

@@ -0,0 +1,3 @@
+[submodule "deps/bcc"]
+	path = deps/bcc
+	url = https://github.com/iovisor/bcc.git

+ 1 - 0
CREDITS

@@ -0,0 +1 @@
+ebpf

+ 0 - 0
EXPERIMENTAL


+ 1056 - 0
api/BPF.cc

@@ -0,0 +1,1056 @@
+/*
+ * Copyright (c) 2016 Facebook, 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 <linux/bpf.h>
+#include <linux/perf_event.h>
+#include <unistd.h>
+#include <cstdio>
+#include <cstring>
+#include <exception>
+#include <fcntl.h>
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <utility>
+#include <vector>
+
+#include "bcc_exception.h"
+#include "bcc_elf.h"
+#include "bcc_syms.h"
+#include "bpf_module.h"
+#include "common.h"
+#include "libbpf.h"
+#include "perf_reader.h"
+#include "syms.h"
+#include "table_storage.h"
+#include "usdt.h"
+
+#include "BPF.h"
+
+namespace {
+/*
+ * Kernels ~4.20 and later support specifying the ref_ctr_offset as an argument
+ * to attaching a uprobe, which negates the need to seek to this memory offset
+ * in userspace to manage semaphores, as the kernel will do it for us.  This
+ * helper function checks if this support is available by reading the uprobe
+ * format for this value, added in a6ca88b241d5e929e6e60b12ad8cd288f0ffa
+*/
+bool uprobe_ref_ctr_supported() {
+  const char *ref_ctr_pmu_path =
+      "/sys/bus/event_source/devices/uprobe/format/ref_ctr_offset";
+  const char *ref_ctr_pmu_expected = "config:32-63\0";
+  char ref_ctr_pmu_fmt[64];  // in Linux source this buffer is compared vs
+                             // PAGE_SIZE, but 64 is probably ample
+  int fd = open(ref_ctr_pmu_path, O_RDONLY);
+  if (fd < 0)
+    return false;
+
+  int ret = read(fd, ref_ctr_pmu_fmt, sizeof(ref_ctr_pmu_fmt));
+  close(fd);
+  if (ret < 0) {
+    return false;
+  }
+  if (strncmp(ref_ctr_pmu_expected, ref_ctr_pmu_fmt,
+              strlen(ref_ctr_pmu_expected)) == 0) {
+    return true;
+  }
+  return false;
+}
+} // namespace
+
+namespace ebpf {
+
+std::string uint_to_hex(uint64_t value) {
+  std::stringstream ss;
+  ss << std::hex << value;
+  return ss.str();
+}
+
+std::string sanitize_str(std::string str, bool (*validator)(char),
+                         char replacement = '_') {
+  for (size_t i = 0; i < str.length(); i++)
+    if (!validator(str[i]))
+      str[i] = replacement;
+  return str;
+}
+
+StatusTuple BPF::init_usdt(const USDT& usdt) {
+  USDT u(usdt);
+  StatusTuple init_stp = u.init();
+  if (!init_stp.ok()) {
+    return init_stp;
+  }
+
+  usdt_.push_back(std::move(u));
+  all_bpf_program_ += usdt_.back().program_text_;
+  return StatusTuple::OK();
+}
+
+void BPF::init_fail_reset() {
+  usdt_.clear();
+  all_bpf_program_ = "";
+}
+
+StatusTuple BPF::init(const std::string& bpf_program,
+                      const std::vector<std::string>& cflags,
+                      const std::vector<USDT>& usdt) {
+  usdt_.reserve(usdt.size());
+  for (const auto& u : usdt) {
+    StatusTuple init_stp = init_usdt(u);
+    if (!init_stp.ok()) {
+      init_fail_reset();
+      return init_stp;
+    }
+  }
+
+  std::vector<const char*> flags;
+  for (const auto& c: cflags)
+    flags.push_back(c.c_str());
+
+  all_bpf_program_ += bpf_program;
+  if (bpf_module_->load_string(all_bpf_program_,
+                               flags.data(),
+                               flags.size()) != 0) {
+    init_fail_reset();
+    return StatusTuple(-1, "Unable to initialize BPF program");
+  }
+
+  return StatusTuple::OK();
+};
+
+BPF::~BPF() {
+  auto res = detach_all();
+  if (!res.ok())
+    std::cerr << "Failed to detach all probes on destruction: " << std::endl
+              << res.msg() << std::endl;
+  bcc_free_buildsymcache(bsymcache_);
+  bsymcache_ = NULL;
+}
+
+StatusTuple BPF::detach_all() {
+  bool has_error = false;
+  std::string error_msg;
+
+  for (auto& it : kprobes_) {
+    auto res = detach_kprobe_event(it.first, it.second);
+    if (!res.ok()) {
+      error_msg += "Failed to detach kprobe event " + it.first + ": ";
+      error_msg += res.msg() + "\n";
+      has_error = true;
+    }
+  }
+
+  for (auto& it : uprobes_) {
+    auto res = detach_uprobe_event(it.first, it.second);
+    if (!res.ok()) {
+      error_msg += "Failed to detach uprobe event " + it.first + ": ";
+      error_msg += res.msg() + "\n";
+      has_error = true;
+    }
+  }
+
+  for (auto& it : tracepoints_) {
+    auto res = detach_tracepoint_event(it.first, it.second);
+    if (!res.ok()) {
+      error_msg += "Failed to detach Tracepoint " + it.first + ": ";
+      error_msg += res.msg() + "\n";
+      has_error = true;
+    }
+  }
+
+  for (auto& it : raw_tracepoints_) {
+    auto res = detach_raw_tracepoint_event(it.first, it.second);
+    if (!res.ok()) {
+      error_msg += "Failed to detach Raw tracepoint " + it.first + ": ";
+      error_msg += res.msg() + "\n";
+      has_error = true;
+    }
+  }
+
+  for (auto& it : perf_buffers_) {
+    auto res = it.second->close_all_cpu();
+    if (!res.ok()) {
+      error_msg += "Failed to close perf buffer " + it.first + ": ";
+      error_msg += res.msg() + "\n";
+      has_error = true;
+    }
+    delete it.second;
+  }
+
+  for (auto& it : perf_event_arrays_) {
+    auto res = it.second->close_all_cpu();
+    if (!res.ok()) {
+      error_msg += "Failed to close perf event array " + it.first + ": ";
+      error_msg += res.msg() + "\n";
+      has_error = true;
+    }
+    delete it.second;
+  }
+
+  for (auto& it : perf_events_) {
+    auto res = detach_perf_event_all_cpu(it.second);
+    if (!res.ok()) {
+      error_msg += res.msg() + "\n";
+      has_error = true;
+    }
+  }
+
+  for (auto& it : funcs_) {
+    int res = close(it.second);
+    if (res != 0) {
+      error_msg += "Failed to unload BPF program for " + it.first + ": ";
+      error_msg += std::string(std::strerror(errno)) + "\n";
+      has_error = true;
+    }
+  }
+
+  if (has_error)
+    return StatusTuple(-1, error_msg);
+  else
+    return StatusTuple::OK();
+}
+
+StatusTuple BPF::attach_kprobe(const std::string& kernel_func,
+                               const std::string& probe_func,
+                               uint64_t kernel_func_offset,
+                               bpf_probe_attach_type attach_type,
+                               int maxactive) {
+  std::string probe_event = get_kprobe_event(kernel_func, attach_type);
+  if (kprobes_.find(probe_event) != kprobes_.end())
+    return StatusTuple(-1, "kprobe %s already attached", probe_event.c_str());
+
+  int probe_fd;
+  TRY2(load_func(probe_func, BPF_PROG_TYPE_KPROBE, probe_fd));
+
+  int res_fd = bpf_attach_kprobe(probe_fd, attach_type, probe_event.c_str(),
+                                 kernel_func.c_str(), kernel_func_offset,
+                                 maxactive);
+
+  if (res_fd < 0) {
+    TRY2(unload_func(probe_func));
+    return StatusTuple(-1, "Unable to attach %skprobe for %s using %s",
+                       attach_type_debug(attach_type).c_str(),
+                       kernel_func.c_str(), probe_func.c_str());
+  }
+
+  open_probe_t p = {};
+  p.perf_event_fd = res_fd;
+  p.func = probe_func;
+  kprobes_[probe_event] = std::move(p);
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::attach_uprobe(const std::string& binary_path,
+                               const std::string& symbol,
+                               const std::string& probe_func,
+                               uint64_t symbol_addr,
+                               bpf_probe_attach_type attach_type, pid_t pid,
+                               uint64_t symbol_offset,
+                               uint32_t ref_ctr_offset) {
+
+  if (symbol_addr != 0 && symbol_offset != 0)
+    return StatusTuple(-1,
+             "Attachng uprobe with addr %lx and offset %lx is not supported",
+             symbol_addr, symbol_offset);
+
+  std::string module;
+  uint64_t offset;
+  TRY2(check_binary_symbol(binary_path, symbol, symbol_addr, module, offset, pid,
+                           symbol_offset));
+
+  std::string probe_event = get_uprobe_event(module, offset, attach_type, pid);
+  if (uprobes_.find(probe_event) != uprobes_.end())
+    return StatusTuple(-1, "uprobe %s already attached", probe_event.c_str());
+
+  int probe_fd;
+  TRY2(load_func(probe_func, BPF_PROG_TYPE_KPROBE, probe_fd));
+
+  int res_fd = bpf_attach_uprobe(probe_fd, attach_type, probe_event.c_str(),
+                                 module.c_str(), offset, pid,
+                                 ref_ctr_offset);
+
+  if (res_fd < 0) {
+    TRY2(unload_func(probe_func));
+    return StatusTuple(
+        -1,
+        "Unable to attach %suprobe for binary %s symbol %s addr %lx "
+        "offset %lx using %s\n",
+        attach_type_debug(attach_type).c_str(), binary_path.c_str(),
+        symbol.c_str(), symbol_addr, symbol_offset, probe_func.c_str());
+  }
+
+  open_probe_t p = {};
+  p.perf_event_fd = res_fd;
+  p.func = probe_func;
+  uprobes_[probe_event] = std::move(p);
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::attach_usdt_without_validation(const USDT& u, pid_t pid) {
+  auto& probe = *static_cast<::USDT::Probe*>(u.probe_.get());
+  if (!uprobe_ref_ctr_supported() && !probe.enable(u.probe_func_))
+    return StatusTuple(-1, "Unable to enable USDT %s", u.print_name().c_str());
+
+  bool failed = false;
+  std::string err_msg;
+  int cnt = 0;
+  for (const auto& loc : probe.locations_) {
+    auto res = attach_uprobe(loc.bin_path_, std::string(), u.probe_func_,
+                             loc.address_, BPF_PROBE_ENTRY, pid, 0,
+                             probe.semaphore_offset());
+    if (!res.ok()) {
+      failed = true;
+      err_msg += "USDT " + u.print_name() + " at " + loc.bin_path_ +
+                  " address " + std::to_string(loc.address_);
+      err_msg += ": " + res.msg() + "\n";
+      break;
+    }
+    cnt++;
+  }
+  if (failed) {
+    for (int i = 0; i < cnt; i++) {
+      auto res = detach_uprobe(probe.locations_[i].bin_path_, std::string(),
+                               probe.locations_[i].address_, BPF_PROBE_ENTRY, pid);
+      if (!res.ok())
+        err_msg += "During clean up: " + res.msg() + "\n";
+    }
+    return StatusTuple(-1, err_msg);
+  } else {
+    return StatusTuple::OK();
+  }
+}
+
+StatusTuple BPF::attach_usdt(const USDT& usdt, pid_t pid) {
+  for (const auto& u : usdt_) {
+    if (u == usdt) {
+      return attach_usdt_without_validation(u, pid);
+    }
+  }
+
+  return StatusTuple(-1, "USDT %s not found", usdt.print_name().c_str());
+}
+
+StatusTuple BPF::attach_usdt_all() {
+  for (const auto& u : usdt_) {
+    auto res = attach_usdt_without_validation(u, -1);
+    if (!res.ok()) {
+      return res;
+    }
+  }
+
+  return StatusTuple::OK();
+}
+
+
+StatusTuple BPF::attach_tracepoint(const std::string& tracepoint,
+                                   const std::string& probe_func) {
+  if (tracepoints_.find(tracepoint) != tracepoints_.end())
+    return StatusTuple(-1, "Tracepoint %s already attached",
+                       tracepoint.c_str());
+
+  auto pos = tracepoint.find(":");
+  if ((pos == std::string::npos) || (pos != tracepoint.rfind(":")))
+    return StatusTuple(-1, "Unable to parse Tracepoint %s", tracepoint.c_str());
+  std::string tp_category = tracepoint.substr(0, pos);
+  std::string tp_name = tracepoint.substr(pos + 1);
+
+  int probe_fd;
+  TRY2(load_func(probe_func, BPF_PROG_TYPE_TRACEPOINT, probe_fd));
+
+  int res_fd =
+      bpf_attach_tracepoint(probe_fd, tp_category.c_str(), tp_name.c_str());
+
+  if (res_fd < 0) {
+    TRY2(unload_func(probe_func));
+    return StatusTuple(-1, "Unable to attach Tracepoint %s using %s",
+                       tracepoint.c_str(), probe_func.c_str());
+  }
+
+  open_probe_t p = {};
+  p.perf_event_fd = res_fd;
+  p.func = probe_func;
+  tracepoints_[tracepoint] = std::move(p);
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::attach_raw_tracepoint(const std::string& tracepoint, const std::string& probe_func) {
+  if (raw_tracepoints_.find(tracepoint) != raw_tracepoints_.end())
+    return StatusTuple(-1, "Raw tracepoint %s already attached",
+                       tracepoint.c_str());
+
+  int probe_fd;
+  TRY2(load_func(probe_func, BPF_PROG_TYPE_RAW_TRACEPOINT, probe_fd));
+
+  int res_fd = bpf_attach_raw_tracepoint(probe_fd, tracepoint.c_str());
+
+  if (res_fd < 0) {
+    TRY2(unload_func(probe_func));
+    return StatusTuple(-1, "Unable to attach Raw tracepoint %s using %s",
+                       tracepoint.c_str(), probe_func.c_str());
+  }
+
+  open_probe_t p = {};
+  p.perf_event_fd = res_fd;
+  p.func = probe_func;
+  raw_tracepoints_[tracepoint] = std::move(p);
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::attach_perf_event(uint32_t ev_type, uint32_t ev_config,
+                                   const std::string& probe_func,
+                                   uint64_t sample_period, uint64_t sample_freq,
+                                   pid_t pid, int cpu, int group_fd) {
+  auto ev_pair = std::make_pair(ev_type, ev_config);
+  if (perf_events_.find(ev_pair) != perf_events_.end())
+    return StatusTuple(-1, "Perf event type %d config %d already attached",
+                       ev_type, ev_config);
+
+  int probe_fd;
+  TRY2(load_func(probe_func, BPF_PROG_TYPE_PERF_EVENT, probe_fd));
+
+  std::vector<int> cpus;
+  if (cpu >= 0)
+    cpus.push_back(cpu);
+  else
+    cpus = get_online_cpus();
+  auto fds = new std::vector<std::pair<int, int>>();
+  fds->reserve(cpus.size());
+  for (int i : cpus) {
+    int fd = bpf_attach_perf_event(probe_fd, ev_type, ev_config, sample_period,
+                                   sample_freq, pid, i, group_fd);
+    if (fd < 0) {
+      for (const auto& it : *fds)
+        close(it.second);
+      delete fds;
+      TRY2(unload_func(probe_func));
+      return StatusTuple(-1, "Failed to attach perf event type %d config %d",
+                         ev_type, ev_config);
+    }
+    fds->emplace_back(i, fd);
+  }
+
+  open_probe_t p = {};
+  p.func = probe_func;
+  p.per_cpu_fd = fds;
+  perf_events_[ev_pair] = std::move(p);
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::attach_perf_event_raw(void* perf_event_attr,
+                                       const std::string& probe_func, pid_t pid,
+                                       int cpu, int group_fd,
+                                       unsigned long extra_flags) {
+  auto attr = static_cast<struct perf_event_attr*>(perf_event_attr);
+  auto ev_pair = std::make_pair(attr->type, attr->config);
+  if (perf_events_.find(ev_pair) != perf_events_.end())
+    return StatusTuple(-1, "Perf event type %d config %d already attached",
+                       attr->type, attr->config);
+
+  int probe_fd;
+  TRY2(load_func(probe_func, BPF_PROG_TYPE_PERF_EVENT, probe_fd));
+
+  std::vector<int> cpus;
+  if (cpu >= 0)
+    cpus.push_back(cpu);
+  else
+    cpus = get_online_cpus();
+  auto fds = new std::vector<std::pair<int, int>>();
+  fds->reserve(cpus.size());
+  for (int i : cpus) {
+    int fd = bpf_attach_perf_event_raw(probe_fd, attr, pid, i, group_fd,
+                                       extra_flags);
+    if (fd < 0) {
+      for (const auto& it : *fds)
+        close(it.second);
+      delete fds;
+      TRY2(unload_func(probe_func));
+      return StatusTuple(-1, "Failed to attach perf event type %d config %d",
+                         attr->type, attr->config);
+    }
+    fds->emplace_back(i, fd);
+  }
+
+  open_probe_t p = {};
+  p.func = probe_func;
+  p.per_cpu_fd = fds;
+  perf_events_[ev_pair] = std::move(p);
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::detach_kprobe(const std::string& kernel_func,
+                               bpf_probe_attach_type attach_type) {
+  std::string event = get_kprobe_event(kernel_func, attach_type);
+
+  auto it = kprobes_.find(event);
+  if (it == kprobes_.end())
+    return StatusTuple(-1, "No open %skprobe for %s",
+                       attach_type_debug(attach_type).c_str(),
+                       kernel_func.c_str());
+
+  TRY2(detach_kprobe_event(it->first, it->second));
+  kprobes_.erase(it);
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::detach_uprobe(const std::string& binary_path,
+                               const std::string& symbol, uint64_t symbol_addr,
+                               bpf_probe_attach_type attach_type, pid_t pid,
+                               uint64_t symbol_offset) {
+  std::string module;
+  uint64_t offset;
+  TRY2(check_binary_symbol(binary_path, symbol, symbol_addr, module, offset, pid,
+                           symbol_offset));
+
+  std::string event = get_uprobe_event(module, offset, attach_type, pid);
+  auto it = uprobes_.find(event);
+  if (it == uprobes_.end())
+    return StatusTuple(-1, "No open %suprobe for binary %s symbol %s addr %lx",
+                       attach_type_debug(attach_type).c_str(),
+                       binary_path.c_str(), symbol.c_str(), symbol_addr);
+
+  TRY2(detach_uprobe_event(it->first, it->second));
+  uprobes_.erase(it);
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::detach_usdt_without_validation(const USDT& u, pid_t pid) {
+  auto& probe = *static_cast<::USDT::Probe*>(u.probe_.get());
+  bool failed = false;
+  std::string err_msg;
+  for (const auto& loc : probe.locations_) {
+    auto res = detach_uprobe(loc.bin_path_, std::string(), loc.address_,
+                             BPF_PROBE_ENTRY, pid);
+    if (!res.ok()) {
+      failed = true;
+      err_msg += "USDT " + u.print_name() + " at " + loc.bin_path_ +
+                  " address " + std::to_string(loc.address_);
+      err_msg += ": " + res.msg() + "\n";
+    }
+  }
+
+  if (!uprobe_ref_ctr_supported() && !probe.disable()) {
+    failed = true;
+    err_msg += "Unable to disable USDT " + u.print_name();
+  }
+
+  if (failed)
+    return StatusTuple(-1, err_msg);
+  else
+    return StatusTuple::OK();
+}
+
+StatusTuple BPF::detach_usdt(const USDT& usdt, pid_t pid) {
+  for (const auto& u : usdt_) {
+    if (u == usdt) {
+      return detach_usdt_without_validation(u, pid);
+    }
+  }
+
+  return StatusTuple(-1, "USDT %s not found", usdt.print_name().c_str());
+}
+
+StatusTuple BPF::detach_usdt_all() {
+  for (const auto& u : usdt_) {
+    auto ret = detach_usdt_without_validation(u, -1);
+    if (!ret.ok()) {
+      return ret;
+    }
+  }
+
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::detach_tracepoint(const std::string& tracepoint) {
+  auto it = tracepoints_.find(tracepoint);
+  if (it == tracepoints_.end())
+    return StatusTuple(-1, "No open Tracepoint %s", tracepoint.c_str());
+
+  TRY2(detach_tracepoint_event(it->first, it->second));
+  tracepoints_.erase(it);
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::detach_raw_tracepoint(const std::string& tracepoint) {
+  auto it = raw_tracepoints_.find(tracepoint);
+  if (it == raw_tracepoints_.end())
+    return StatusTuple(-1, "No open Raw tracepoint %s", tracepoint.c_str());
+
+  TRY2(detach_raw_tracepoint_event(it->first, it->second));
+  raw_tracepoints_.erase(it);
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::detach_perf_event(uint32_t ev_type, uint32_t ev_config) {
+  auto it = perf_events_.find(std::make_pair(ev_type, ev_config));
+  if (it == perf_events_.end())
+    return StatusTuple(-1, "Perf Event type %d config %d not attached", ev_type,
+                       ev_config);
+  TRY2(detach_perf_event_all_cpu(it->second));
+  perf_events_.erase(it);
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::detach_perf_event_raw(void* perf_event_attr) {
+  auto attr = static_cast<struct perf_event_attr*>(perf_event_attr);
+  return detach_perf_event(attr->type, attr->config);
+}
+
+StatusTuple BPF::open_perf_event(const std::string& name, uint32_t type,
+                                 uint64_t config, int pid) {
+  if (perf_event_arrays_.find(name) == perf_event_arrays_.end()) {
+    TableStorage::iterator it;
+    if (!bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+      return StatusTuple(-1, "open_perf_event: unable to find table_storage %s",
+                         name.c_str());
+    perf_event_arrays_[name] = new BPFPerfEventArray(it->second);
+  }
+  auto table = perf_event_arrays_[name];
+  TRY2(table->open_all_cpu(type, config, pid));
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::close_perf_event(const std::string& name) {
+  auto it = perf_event_arrays_.find(name);
+  if (it == perf_event_arrays_.end())
+    return StatusTuple(-1, "Perf Event for %s not open", name.c_str());
+  TRY2(it->second->close_all_cpu());
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::open_perf_buffer(const std::string& name,
+                                  perf_reader_raw_cb cb,
+                                  perf_reader_lost_cb lost_cb, void* cb_cookie,
+                                  int page_cnt) {
+  if (perf_buffers_.find(name) == perf_buffers_.end()) {
+    TableStorage::iterator it;
+    if (!bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+      return StatusTuple(-1,
+                         "open_perf_buffer: unable to find table_storage %s",
+                         name.c_str());
+    perf_buffers_[name] = new BPFPerfBuffer(it->second);
+  }
+  if ((page_cnt & (page_cnt - 1)) != 0)
+    return StatusTuple(-1, "open_perf_buffer page_cnt must be a power of two");
+  auto table = perf_buffers_[name];
+  TRY2(table->open_all_cpu(cb, lost_cb, cb_cookie, page_cnt));
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::close_perf_buffer(const std::string& name) {
+  auto it = perf_buffers_.find(name);
+  if (it == perf_buffers_.end())
+    return StatusTuple(-1, "Perf buffer for %s not open", name.c_str());
+  TRY2(it->second->close_all_cpu());
+  return StatusTuple::OK();
+}
+
+BPFPerfBuffer* BPF::get_perf_buffer(const std::string& name) {
+  auto it = perf_buffers_.find(name);
+  return (it == perf_buffers_.end()) ? nullptr : it->second;
+}
+
+int BPF::poll_perf_buffer(const std::string& name, int timeout_ms) {
+  auto it = perf_buffers_.find(name);
+  if (it == perf_buffers_.end())
+    return -1;
+  return it->second->poll(timeout_ms);
+}
+
+size_t BPF:: get_num_functions() {return bpf_module_->num_functions();}
+
+const char *BPF::get_function_name(size_t id) {
+  if (!bpf_module_) return nullptr;
+  return bpf_module_->function_name(id);
+}
+
+StatusTuple BPF::load_func(const std::string& func_name, bpf_prog_type type,
+                           int& fd, unsigned flags, bpf_attach_type expected_attach_type) {
+  if (funcs_.find(func_name) != funcs_.end()) {
+    fd = funcs_[func_name];
+    return StatusTuple::OK();
+  }
+
+  uint8_t* func_start = bpf_module_->function_start(func_name);
+  if (!func_start)
+    return StatusTuple(-1, "Can't find start of function %s",
+                       func_name.c_str());
+  size_t func_size = bpf_module_->function_size(func_name);
+
+  int log_level = 0;
+  if (flag_ & DEBUG_BPF_REGISTER_STATE)
+    log_level = 2;
+  else if (flag_ & DEBUG_BPF)
+    log_level = 1;
+
+  fd = bpf_module_->bcc_func_load(type, func_name.c_str(),
+                     reinterpret_cast<struct bpf_insn*>(func_start), func_size,
+                     bpf_module_->license(), bpf_module_->kern_version(),
+                     log_level, nullptr, 0, nullptr, flags, expected_attach_type);
+
+  if (fd < 0)
+    return StatusTuple(-1, "Failed to load %s: %d", func_name.c_str(), fd);
+
+  int ret = bpf_module_->annotate_prog_tag(
+      func_name, fd, reinterpret_cast<struct bpf_insn*>(func_start), func_size);
+  if (ret < 0)
+    fprintf(stderr, "WARNING: cannot get prog tag, ignore saving source with program tag\n");
+  funcs_[func_name] = fd;
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::unload_func(const std::string& func_name) {
+  auto it = funcs_.find(func_name);
+  if (it == funcs_.end())
+    return StatusTuple::OK();
+
+  int res = close(it->second);
+  if (res != 0)
+    return StatusTuple(-1, "Can't close FD for %s: %d", it->first.c_str(), res);
+
+  funcs_.erase(it);
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::attach_func(int prog_fd, int attachable_fd,
+                             enum bpf_attach_type attach_type,
+                             uint64_t flags) {
+  int res = bpf_module_->bcc_func_attach(prog_fd, attachable_fd, attach_type, flags);
+  if (res != 0)
+    return StatusTuple(-1, "Can't attach for prog_fd %d, attachable_fd %d, "
+                           "attach_type %d, flags %ld: error %d",
+                       prog_fd, attachable_fd, attach_type, flags, res);
+
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::detach_func(int prog_fd, int attachable_fd,
+                             enum bpf_attach_type attach_type) {
+  int res = bpf_module_->bcc_func_detach(prog_fd, attachable_fd, attach_type);
+  if (res != 0)
+    return StatusTuple(-1, "Can't detach for prog_fd %d, attachable_fd %d, "
+                           "attach_type %d: error %d",
+                       prog_fd, attachable_fd, attach_type, res);
+
+  return StatusTuple::OK();
+}
+
+std::string BPF::get_syscall_fnname(const std::string& name) {
+  if (syscall_prefix_ == nullptr) {
+    KSyms ksym;
+    uint64_t addr;
+
+    if (ksym.resolve_name(nullptr, "sys_bpf", &addr))
+      syscall_prefix_.reset(new std::string("sys_"));
+    else if (ksym.resolve_name(nullptr, "__x64_sys_bpf", &addr))
+      syscall_prefix_.reset(new std::string("__x64_sys_"));
+    else
+      syscall_prefix_.reset(new std::string());
+  }
+
+  return *syscall_prefix_ + name;
+}
+
+StatusTuple BPF::check_binary_symbol(const std::string& binary_path,
+                                     const std::string& symbol,
+                                     uint64_t symbol_addr,
+                                     std::string& module_res,
+                                     uint64_t& offset_res, pid_t pid,
+                                     uint64_t symbol_offset) {
+  bcc_symbol output;
+  int res = bcc_resolve_symname(binary_path.c_str(), symbol.c_str(),
+                                symbol_addr, pid, nullptr, &output);
+  if (res < 0)
+    return StatusTuple(
+        -1, "Unable to find offset for binary %s symbol %s address %lx",
+        binary_path.c_str(), symbol.c_str(), symbol_addr);
+
+  if (output.module) {
+    module_res = output.module;
+    ::free(const_cast<char*>(output.module));
+  } else {
+    module_res = binary_path;
+  }
+  offset_res = output.offset + symbol_offset;
+  return StatusTuple::OK();
+}
+
+std::string BPF::get_kprobe_event(const std::string& kernel_func,
+                                  bpf_probe_attach_type type) {
+  std::string res = attach_type_prefix(type) + "_";
+  res += sanitize_str(kernel_func, &BPF::kprobe_event_validator);
+  return res;
+}
+
+BPFProgTable BPF::get_prog_table(const std::string& name) {
+  TableStorage::iterator it;
+  if (bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+    return BPFProgTable(it->second);
+  return BPFProgTable({});
+}
+
+BPFCgroupArray BPF::get_cgroup_array(const std::string& name) {
+  TableStorage::iterator it;
+  if (bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+    return BPFCgroupArray(it->second);
+  return BPFCgroupArray({});
+}
+
+BPFDevmapTable BPF::get_devmap_table(const std::string& name) {
+  TableStorage::iterator it;
+  if (bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+    return BPFDevmapTable(it->second);
+  return BPFDevmapTable({});
+}
+
+BPFXskmapTable BPF::get_xskmap_table(const std::string& name) {
+  TableStorage::iterator it;
+  if (bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+    return BPFXskmapTable(it->second);
+  return BPFXskmapTable({});
+}
+
+BPFStackTable BPF::get_stack_table(const std::string& name, bool use_debug_file,
+                                   bool check_debug_file_crc) {
+  TableStorage::iterator it;
+  if (bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+    return BPFStackTable(it->second, use_debug_file, check_debug_file_crc);
+  return BPFStackTable({}, use_debug_file, check_debug_file_crc);
+}
+
+BPFStackBuildIdTable BPF::get_stackbuildid_table(const std::string &name, bool use_debug_file,
+                                                 bool check_debug_file_crc) {
+  TableStorage::iterator it;
+
+  if (bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+    return BPFStackBuildIdTable(it->second, use_debug_file, check_debug_file_crc, get_bsymcache());
+  return BPFStackBuildIdTable({}, use_debug_file, check_debug_file_crc, get_bsymcache());
+}
+
+BPFSockmapTable BPF::get_sockmap_table(const std::string& name) {
+  TableStorage::iterator it;
+  if (bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+    return BPFSockmapTable(it->second);
+  return BPFSockmapTable({});
+}
+
+BPFSockhashTable BPF::get_sockhash_table(const std::string& name) {
+  TableStorage::iterator it;
+  if (bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+    return BPFSockhashTable(it->second);
+  return BPFSockhashTable({});
+}
+
+bool BPF::add_module(std::string module)
+{
+  return bcc_buildsymcache_add_module(get_bsymcache(), module.c_str()) != 0 ?
+    false : true;
+}
+
+namespace {
+
+constexpr size_t kEventNameSizeLimit = 224;
+
+std::string shorten_event_name(const std::string& name) {
+  std::string hash = uint_to_hex(std::hash<std::string>{}(name));
+  return name.substr(0, kEventNameSizeLimit - hash.size()) + hash;
+}
+
+} // namespace
+
+std::string BPF::get_uprobe_event(const std::string& binary_path,
+                                  uint64_t offset, bpf_probe_attach_type type,
+                                  pid_t pid) {
+  std::string res = attach_type_prefix(type) + "_";
+  res += sanitize_str(binary_path, &BPF::uprobe_path_validator);
+  res += "_0x" + uint_to_hex(offset);
+  if (pid != -1)
+    res += "_" + std::to_string(pid);
+  if (res.size() > kEventNameSizeLimit) {
+    return shorten_event_name(res);
+  }
+  return res;
+}
+
+StatusTuple BPF::detach_kprobe_event(const std::string& event,
+                                     open_probe_t& attr) {
+  bpf_close_perf_event_fd(attr.perf_event_fd);
+  TRY2(unload_func(attr.func));
+  if (bpf_detach_kprobe(event.c_str()) < 0)
+    return StatusTuple(-1, "Unable to detach kprobe %s", event.c_str());
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::detach_uprobe_event(const std::string& event,
+                                     open_probe_t& attr) {
+  bpf_close_perf_event_fd(attr.perf_event_fd);
+  TRY2(unload_func(attr.func));
+  if (bpf_detach_uprobe(event.c_str()) < 0)
+    return StatusTuple(-1, "Unable to detach uprobe %s", event.c_str());
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::detach_tracepoint_event(const std::string& tracepoint,
+                                         open_probe_t& attr) {
+  bpf_close_perf_event_fd(attr.perf_event_fd);
+  TRY2(unload_func(attr.func));
+
+  // TODO: bpf_detach_tracepoint currently does nothing.
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::detach_raw_tracepoint_event(const std::string& tracepoint,
+                                             open_probe_t& attr) {
+  TRY2(close(attr.perf_event_fd));
+  TRY2(unload_func(attr.func));
+
+  return StatusTuple::OK();
+}
+
+StatusTuple BPF::detach_perf_event_all_cpu(open_probe_t& attr) {
+  bool has_error = false;
+  std::string err_msg;
+  for (const auto& it : *attr.per_cpu_fd) {
+    int res = bpf_close_perf_event_fd(it.second);
+    if (res != 0) {
+      has_error = true;
+      err_msg += "Failed to close perf event FD " + std::to_string(it.second) +
+                 " For CPU " + std::to_string(it.first) + ": ";
+      err_msg += std::string(std::strerror(errno)) + "\n";
+    }
+  }
+  delete attr.per_cpu_fd;
+  TRY2(unload_func(attr.func));
+
+  if (has_error)
+    return StatusTuple(-1, err_msg);
+  return StatusTuple::OK();
+}
+
+int BPF::free_bcc_memory() {
+  return bcc_free_memory();
+}
+
+USDT::USDT(const std::string& binary_path, const std::string& provider,
+           const std::string& name, const std::string& probe_func)
+    : initialized_(false),
+      binary_path_(binary_path),
+      pid_(-1),
+      provider_(provider),
+      name_(name),
+      probe_func_(probe_func),
+      mod_match_inode_only_(1) {}
+
+USDT::USDT(pid_t pid, const std::string& provider, const std::string& name,
+           const std::string& probe_func)
+    : initialized_(false),
+      binary_path_(),
+      pid_(pid),
+      provider_(provider),
+      name_(name),
+      probe_func_(probe_func),
+      mod_match_inode_only_(1) {}
+
+USDT::USDT(const std::string& binary_path, pid_t pid,
+           const std::string& provider, const std::string& name,
+           const std::string& probe_func)
+    : initialized_(false),
+      binary_path_(binary_path),
+      pid_(pid),
+      provider_(provider),
+      name_(name),
+      probe_func_(probe_func),
+      mod_match_inode_only_(1) {}
+
+USDT::USDT(const USDT& usdt)
+    : initialized_(false),
+      binary_path_(usdt.binary_path_),
+      pid_(usdt.pid_),
+      provider_(usdt.provider_),
+      name_(usdt.name_),
+      probe_func_(usdt.probe_func_),
+      mod_match_inode_only_(usdt.mod_match_inode_only_) {}
+
+USDT::USDT(USDT&& usdt) noexcept
+    : initialized_(usdt.initialized_),
+      binary_path_(std::move(usdt.binary_path_)),
+      pid_(usdt.pid_),
+      provider_(std::move(usdt.provider_)),
+      name_(std::move(usdt.name_)),
+      probe_func_(std::move(usdt.probe_func_)),
+      probe_(std::move(usdt.probe_)),
+      program_text_(std::move(usdt.program_text_)),
+      mod_match_inode_only_(usdt.mod_match_inode_only_) {
+  usdt.initialized_ = false;
+}
+
+bool USDT::operator==(const USDT& other) const {
+  return (provider_ == other.provider_) && (name_ == other.name_) &&
+         (binary_path_ == other.binary_path_) && (pid_ == other.pid_) &&
+         (probe_func_ == other.probe_func_);
+}
+
+int USDT::set_probe_matching_kludge(uint8_t kludge) {
+  if (kludge != 0 && kludge != 1)
+    return -1;
+
+  mod_match_inode_only_ = kludge;
+  return 0;
+}
+
+StatusTuple USDT::init() {
+  std::unique_ptr<::USDT::Context> ctx;
+  if (!binary_path_.empty() && pid_ > 0)
+    ctx.reset(new ::USDT::Context(pid_, binary_path_, mod_match_inode_only_));
+  else if (!binary_path_.empty())
+    ctx.reset(new ::USDT::Context(binary_path_, mod_match_inode_only_));
+  else if (pid_ > 0)
+    ctx.reset(new ::USDT::Context(pid_, mod_match_inode_only_));
+  else
+    return StatusTuple(-1, "No valid Binary Path or PID provided");
+
+  if (!ctx->loaded())
+    return StatusTuple(-1, "Unable to load USDT " + print_name());
+
+  auto deleter = [](void* probe) { delete static_cast<::USDT::Probe*>(probe); };
+  for (auto& p : ctx->probes_) {
+    if (p->provider_ == provider_ && p->name_ == name_) {
+      // Take ownership of the probe that we are interested in, and avoid it
+      // being destructed when we destruct the USDT::Context instance
+      probe_ = std::unique_ptr<void, std::function<void(void*)>>(p.release(),
+                                                                 deleter);
+      p.swap(ctx->probes_.back());
+      ctx->probes_.pop_back();
+      break;
+    }
+  }
+  if (!probe_)
+    return StatusTuple(-1, "Unable to find USDT " + print_name());
+  ctx.reset(nullptr);
+  auto& probe = *static_cast<::USDT::Probe*>(probe_.get());
+
+  std::ostringstream stream;
+  if (!probe.usdt_getarg(stream, probe_func_))
+    return StatusTuple(
+        -1, "Unable to generate program text for USDT " + print_name());
+  program_text_ = ::USDT::USDT_PROGRAM_HEADER + stream.str();
+
+  initialized_ = true;
+  return StatusTuple::OK();
+}
+
+}  // namespace ebpf

+ 430 - 0
api/BPF.h

@@ -0,0 +1,430 @@
+/*
+ * Copyright (c) 2016 Facebook, 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 <cctype>
+#include <cstdint>
+#include <memory>
+#include <ostream>
+#include <string>
+
+#include "BPFTable.h"
+#include "bcc_exception.h"
+#include "bcc_syms.h"
+#include "bpf_module.h"
+#include "linux/bpf.h"
+#include "libbpf.h"
+#include "table_storage.h"
+
+static const int DEFAULT_PERF_BUFFER_PAGE_CNT = 8;
+
+namespace ebpf {
+
+struct open_probe_t {
+  int perf_event_fd;
+  std::string func;
+  std::vector<std::pair<int, int>>* per_cpu_fd;
+};
+
+class BPF;
+
+class USDT {
+ public:
+  USDT(const std::string& binary_path, const std::string& provider,
+       const std::string& name, const std::string& probe_func);
+  USDT(pid_t pid, const std::string& provider, const std::string& name,
+       const std::string& probe_func);
+  USDT(const std::string& binary_path, pid_t pid, const std::string& provider,
+       const std::string& name, const std::string& probe_func);
+  USDT(const USDT& usdt);
+  USDT(USDT&& usdt) noexcept;
+
+  const std::string &binary_path() const { return binary_path_; }
+  pid_t pid() const { return pid_; }
+  const std::string &provider() const { return provider_; }
+  const std::string &name() const { return name_; }
+  const std::string &probe_func() const { return probe_func_; }
+
+  StatusTuple init();
+
+  bool operator==(const USDT& other) const;
+
+  std::string print_name() const {
+    return provider_ + ":" + name_ + " from binary " + binary_path_ + " PID " +
+           std::to_string(pid_) + " for probe " + probe_func_;
+  }
+
+  friend std::ostream& operator<<(std::ostream& out, const USDT& usdt) {
+    return out << usdt.provider_ << ":" << usdt.name_ << " from binary "
+               << usdt.binary_path_ << " PID " << usdt.pid_ << " for probe "
+               << usdt.probe_func_;
+  }
+
+  // When the kludge flag is set to 1 (default), we will only match on inode
+  // when searching for modules in /proc/PID/maps that might contain the
+  // tracepoint we're looking for.
+  // By setting this to 0, we will match on both inode and
+  // (dev_major, dev_minor), which is a more accurate way to uniquely
+  // identify a file, but may fail depending on the filesystem backing the
+  // target file (see bcc#2715)
+  //
+  // This hack exists because btrfs and overlayfs report different device
+  // numbers for files in /proc/PID/maps vs stat syscall. Don't use it unless
+  // you've had issues with inode collisions. Both btrfs and overlayfs are
+  // known to require inode-only resolution to accurately match a file.
+  //
+  // set_probe_matching_kludge(0) must be called before USDTs are submitted to
+  // BPF::init()
+  int set_probe_matching_kludge(uint8_t kludge);
+
+ private:
+  bool initialized_;
+
+  std::string binary_path_;
+  pid_t pid_;
+
+  std::string provider_;
+  std::string name_;
+  std::string probe_func_;
+
+  std::unique_ptr<void, std::function<void(void*)>> probe_;
+  std::string program_text_;
+
+  uint8_t mod_match_inode_only_;
+
+  friend class BPF;
+};
+
+class BPF {
+ public:
+  static const int BPF_MAX_STACK_DEPTH = 127;
+
+  explicit BPF(unsigned int flag = 0, TableStorage* ts = nullptr,
+               bool rw_engine_enabled = bpf_module_rw_engine_enabled(),
+               const std::string &maps_ns = "",
+               bool allow_rlimit = true)
+      : flag_(flag),
+        bsymcache_(NULL),
+        bpf_module_(new BPFModule(flag, ts, rw_engine_enabled, maps_ns,
+                    allow_rlimit)) {}
+  StatusTuple init(const std::string& bpf_program,
+                   const std::vector<std::string>& cflags = {},
+                   const std::vector<USDT>& usdt = {});
+
+  StatusTuple init_usdt(const USDT& usdt);
+
+  ~BPF();
+  StatusTuple detach_all();
+
+  StatusTuple attach_kprobe(const std::string& kernel_func,
+                            const std::string& probe_func,
+                            uint64_t kernel_func_offset = 0,
+                            bpf_probe_attach_type = BPF_PROBE_ENTRY,
+                            int maxactive = 0);
+  StatusTuple detach_kprobe(
+      const std::string& kernel_func,
+      bpf_probe_attach_type attach_type = BPF_PROBE_ENTRY);
+
+  StatusTuple attach_uprobe(const std::string& binary_path,
+                            const std::string& symbol,
+                            const std::string& probe_func,
+                            uint64_t symbol_addr = 0,
+                            bpf_probe_attach_type attach_type = BPF_PROBE_ENTRY,
+                            pid_t pid = -1,
+                            uint64_t symbol_offset = 0,
+                            uint32_t ref_ctr_offset = 0);
+  StatusTuple detach_uprobe(const std::string& binary_path,
+                            const std::string& symbol, uint64_t symbol_addr = 0,
+                            bpf_probe_attach_type attach_type = BPF_PROBE_ENTRY,
+                            pid_t pid = -1,
+                            uint64_t symbol_offset = 0);
+  StatusTuple attach_usdt(const USDT& usdt, pid_t pid = -1);
+  StatusTuple attach_usdt_all();
+  StatusTuple detach_usdt(const USDT& usdt, pid_t pid = -1);
+  StatusTuple detach_usdt_all();
+
+  StatusTuple attach_tracepoint(const std::string& tracepoint,
+                                const std::string& probe_func);
+  StatusTuple detach_tracepoint(const std::string& tracepoint);
+
+  StatusTuple attach_raw_tracepoint(const std::string& tracepoint,
+                                    const std::string& probe_func);
+  StatusTuple detach_raw_tracepoint(const std::string& tracepoint);
+
+  StatusTuple attach_perf_event(uint32_t ev_type, uint32_t ev_config,
+                                const std::string& probe_func,
+                                uint64_t sample_period, uint64_t sample_freq,
+                                pid_t pid = -1, int cpu = -1,
+                                int group_fd = -1);
+  StatusTuple attach_perf_event_raw(void* perf_event_attr,
+                                    const std::string& probe_func,
+                                    pid_t pid = -1, int cpu = -1,
+                                    int group_fd = -1,
+                                    unsigned long extra_flags = 0);
+  StatusTuple detach_perf_event(uint32_t ev_type, uint32_t ev_config);
+  StatusTuple detach_perf_event_raw(void* perf_event_attr);
+  std::string get_syscall_fnname(const std::string& name);
+
+	const BPFModule *get_mod() {
+		return bpf_module_.get();
+	}
+
+	/*php add*/
+	size_t get_num_functions();
+
+	/*php add*/
+	const char *get_function_name(size_t id);
+
+
+	BPFTable get_table(const std::string& name) {
+    TableStorage::iterator it;
+    if (bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+      return BPFTable(it->second);
+    return BPFTable({});
+  }
+
+  template <class ValueType>
+  BPFArrayTable<ValueType> get_array_table(const std::string& name) {
+    TableStorage::iterator it;
+    if (bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+      return BPFArrayTable<ValueType>(it->second);
+    return BPFArrayTable<ValueType>({});
+  }
+
+  template <class ValueType>
+  BPFPercpuArrayTable<ValueType> get_percpu_array_table(
+      const std::string& name) {
+    TableStorage::iterator it;
+    if (bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+      return BPFPercpuArrayTable<ValueType>(it->second);
+    return BPFPercpuArrayTable<ValueType>({});
+  }
+
+  template <class KeyType, class ValueType>
+  BPFHashTable<KeyType, ValueType> get_hash_table(const std::string& name) {
+    TableStorage::iterator it;
+    if (bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+      return BPFHashTable<KeyType, ValueType>(it->second);
+    return BPFHashTable<KeyType, ValueType>({});
+  }
+
+  template <class KeyType, class ValueType>
+  BPFPercpuHashTable<KeyType, ValueType> get_percpu_hash_table(
+      const std::string& name) {
+    TableStorage::iterator it;
+    if (bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+      return BPFPercpuHashTable<KeyType, ValueType>(it->second);
+    return BPFPercpuHashTable<KeyType, ValueType>({});
+  }
+
+  template <class ValueType>
+  BPFSkStorageTable<ValueType> get_sk_storage_table(const std::string& name) {
+    TableStorage::iterator it;
+    if (bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+      return BPFSkStorageTable<ValueType>(it->second);
+    return BPFSkStorageTable<ValueType>({});
+  }
+
+  template <class ValueType>
+  BPFInodeStorageTable<ValueType> get_inode_storage_table(const std::string& name) {
+    TableStorage::iterator it;
+    if (bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+      return BPFInodeStorageTable<ValueType>(it->second);
+    return BPFInodeStorageTable<ValueType>({});
+  }
+
+  template <class ValueType>
+  BPFTaskStorageTable<ValueType> get_task_storage_table(const std::string& name) {
+    TableStorage::iterator it;
+    if (bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+      return BPFTaskStorageTable<ValueType>(it->second);
+    return BPFTaskStorageTable<ValueType>({});
+  }
+
+  template <class ValueType>
+  BPFCgStorageTable<ValueType> get_cg_storage_table(const std::string& name) {
+    TableStorage::iterator it;
+    if (bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+      return BPFCgStorageTable<ValueType>(it->second);
+    return BPFCgStorageTable<ValueType>({});
+  }
+
+  template <class ValueType>
+  BPFPercpuCgStorageTable<ValueType> get_percpu_cg_storage_table(const std::string& name) {
+    TableStorage::iterator it;
+    if (bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+      return BPFPercpuCgStorageTable<ValueType>(it->second);
+    return BPFPercpuCgStorageTable<ValueType>({});
+  }
+
+  template <class ValueType>
+  BPFQueueStackTable<ValueType> get_queuestack_table(const std::string& name) {
+    TableStorage::iterator it;
+    if (bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+      return BPFQueueStackTable<ValueType>(it->second);
+    return BPFQueueStackTable<ValueType>({});
+  }
+
+  void* get_bsymcache(void) {
+    if (bsymcache_ == NULL) {
+      bsymcache_ = bcc_buildsymcache_new();
+    }
+    return bsymcache_;
+  }
+
+  BPFProgTable get_prog_table(const std::string& name);
+
+  BPFCgroupArray get_cgroup_array(const std::string& name);
+
+  BPFDevmapTable get_devmap_table(const std::string& name);
+
+  BPFXskmapTable get_xskmap_table(const std::string& name);
+
+  BPFSockmapTable get_sockmap_table(const std::string& name);
+
+  BPFSockhashTable get_sockhash_table(const std::string& name);
+
+  BPFStackTable get_stack_table(const std::string& name,
+                                bool use_debug_file = true,
+                                bool check_debug_file_crc = true);
+
+  BPFStackBuildIdTable get_stackbuildid_table(const std::string &name,
+                                              bool use_debug_file = true,
+                                              bool check_debug_file_crc = true);
+  template <class KeyType>
+  BPFMapInMapTable<KeyType> get_map_in_map_table(const std::string& name){
+      TableStorage::iterator it;
+      if (bpf_module_->table_storage().Find(Path({bpf_module_->id(), name}), it))
+        return BPFMapInMapTable<KeyType>(it->second);
+      return BPFMapInMapTable<KeyType>({});
+  }
+
+  bool add_module(std::string module);
+
+  StatusTuple open_perf_event(const std::string& name, uint32_t type,
+                              uint64_t config, int pid = -1);
+
+  StatusTuple close_perf_event(const std::string& name);
+
+  // Open a Perf Buffer of given name, providing callback and callback cookie
+  // to use when polling. BPF class owns the opened Perf Buffer and will free
+  // it on-demand or on destruction.
+  StatusTuple open_perf_buffer(const std::string& name, perf_reader_raw_cb cb,
+                               perf_reader_lost_cb lost_cb = nullptr,
+                               void* cb_cookie = nullptr,
+                               int page_cnt = DEFAULT_PERF_BUFFER_PAGE_CNT);
+  // Close and free the Perf Buffer of given name.
+  StatusTuple close_perf_buffer(const std::string& name);
+  // Obtain an pointer to the opened BPFPerfBuffer instance of given name.
+  // Will return nullptr if such open Perf Buffer doesn't exist.
+  BPFPerfBuffer* get_perf_buffer(const std::string& name);
+  // Poll an opened Perf Buffer of given name with given timeout, using callback
+  // provided when opening. Do nothing if such open Perf Buffer doesn't exist.
+  // Returns:
+  //   -1 on error or if perf buffer with such name doesn't exist;
+  //   0, if no data was available before timeout;
+  //   number of CPUs that have new data, otherwise.
+  int poll_perf_buffer(const std::string& name, int timeout_ms = -1);
+
+  StatusTuple load_func(const std::string& func_name, enum bpf_prog_type type,
+                        int& fd, unsigned flags = 0, enum bpf_attach_type = (bpf_attach_type) -1);
+  StatusTuple unload_func(const std::string& func_name);
+
+  StatusTuple attach_func(int prog_fd, int attachable_fd,
+                          enum bpf_attach_type attach_type,
+                          uint64_t flags);
+  StatusTuple detach_func(int prog_fd, int attachable_fd,
+                          enum bpf_attach_type attach_type);
+
+  int free_bcc_memory();
+
+ private:
+  std::string get_kprobe_event(const std::string& kernel_func,
+                               bpf_probe_attach_type type);
+  std::string get_uprobe_event(const std::string& binary_path, uint64_t offset,
+                               bpf_probe_attach_type type, pid_t pid);
+
+  StatusTuple attach_usdt_without_validation(const USDT& usdt, pid_t pid);
+  StatusTuple detach_usdt_without_validation(const USDT& usdt, pid_t pid);
+
+  StatusTuple detach_kprobe_event(const std::string& event, open_probe_t& attr);
+  StatusTuple detach_uprobe_event(const std::string& event, open_probe_t& attr);
+  StatusTuple detach_tracepoint_event(const std::string& tracepoint,
+                                      open_probe_t& attr);
+  StatusTuple detach_raw_tracepoint_event(const std::string& tracepoint,
+                                          open_probe_t& attr);
+  StatusTuple detach_perf_event_all_cpu(open_probe_t& attr);
+
+  std::string attach_type_debug(bpf_probe_attach_type type) {
+    switch (type) {
+    case BPF_PROBE_ENTRY:
+      return "";
+    case BPF_PROBE_RETURN:
+      return "return ";
+    }
+    return "ERROR";
+  }
+
+  std::string attach_type_prefix(bpf_probe_attach_type type) {
+    switch (type) {
+    case BPF_PROBE_ENTRY:
+      return "p";
+    case BPF_PROBE_RETURN:
+      return "r";
+    }
+    return "ERROR";
+  }
+
+  static bool kprobe_event_validator(char c) {
+    return (c != '+') && (c != '.');
+  }
+
+  static bool uprobe_path_validator(char c) {
+    return std::isalpha(c) || std::isdigit(c) || (c == '_');
+  }
+
+  StatusTuple check_binary_symbol(const std::string& binary_path,
+                                  const std::string& symbol,
+                                  uint64_t symbol_addr, std::string& module_res,
+                                  uint64_t& offset_res,pid_t pid,
+                                  uint64_t symbol_offset = 0);
+
+  void init_fail_reset();
+
+  int flag_;
+
+  void *bsymcache_;
+
+  std::unique_ptr<std::string> syscall_prefix_;
+
+  std::unique_ptr<BPFModule> bpf_module_;
+
+  std::map<std::string, int> funcs_;
+
+  std::vector<USDT> usdt_;
+  std::string all_bpf_program_;
+
+  std::map<std::string, open_probe_t> kprobes_;
+  std::map<std::string, open_probe_t> uprobes_;
+  std::map<std::string, open_probe_t> tracepoints_;
+  std::map<std::string, open_probe_t> raw_tracepoints_;
+  std::map<std::string, BPFPerfBuffer*> perf_buffers_;
+  std::map<std::string, BPFPerfEventArray*> perf_event_arrays_;
+  std::map<std::pair<uint32_t, uint32_t>, open_probe_t> perf_events_;
+};
+
+}  // namespace ebpf

+ 832 - 0
api/BPFTable.cc

@@ -0,0 +1,832 @@
+/*
+ * Copyright (c) 2016 Facebook, 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 <fcntl.h>
+#include <linux/elf.h>
+#include <linux/perf_event.h>
+#include <sys/epoll.h>
+#include <unistd.h>
+#include <cerrno>
+#include <cinttypes>
+#include <cstdint>
+#include <cstring>
+#include <iostream>
+#include <memory>
+
+#include "BPFTable.h"
+
+#include "bcc_exception.h"
+#include "bcc_syms.h"
+#include "common.h"
+#include "file_desc.h"
+#include "libbpf.h"
+#include "perf_reader.h"
+
+namespace ebpf {
+
+BPFTable::BPFTable(const TableDesc& desc) : BPFTableBase<void, void>(desc) {}
+
+StatusTuple BPFTable::get_value(const std::string& key_str,
+                                std::string& value_str) {
+  char key[desc.key_size];
+  char value[desc.leaf_size];
+
+  StatusTuple r(0);
+
+  r = string_to_key(key_str, key);
+  if (!r.ok())
+    return r;
+
+  if (!lookup(key, value))
+    return StatusTuple(-1, "error getting value");
+
+  return leaf_to_string(value, value_str);
+}
+
+StatusTuple BPFTable::get_value(const std::string& key_str,
+                                std::vector<std::string>& value_str) {
+  size_t ncpus = get_possible_cpus().size();
+  char key[desc.key_size];
+  char value[desc.leaf_size * ncpus];
+
+  StatusTuple r(0);
+
+  r = string_to_key(key_str, key);
+  if (!r.ok())
+    return r;
+
+  if (!lookup(key, value))
+    return StatusTuple(-1, "error getting value");
+
+  value_str.resize(ncpus);
+
+  for (size_t i = 0; i < ncpus; i++) {
+    r = leaf_to_string(value + i * desc.leaf_size, value_str.at(i));
+    if (!r.ok())
+      return r;
+  }
+  return StatusTuple::OK();
+}
+
+StatusTuple BPFTable::update_value(const std::string& key_str,
+                                   const std::string& value_str) {
+  char key[desc.key_size];
+  char value[desc.leaf_size];
+
+  StatusTuple r(0);
+
+  r = string_to_key(key_str, key);
+  if (!r.ok())
+    return r;
+
+  r = string_to_leaf(value_str, value);
+  if (!r.ok())
+    return r;
+
+  if (!update(key, value))
+    return StatusTuple(-1, "error updating element");
+
+  return StatusTuple::OK();
+}
+
+StatusTuple BPFTable::update_value(const std::string& key_str,
+                                   const std::vector<std::string>& value_str) {
+  size_t ncpus = get_possible_cpus().size();
+  char key[desc.key_size];
+  char value[desc.leaf_size * ncpus];
+
+  StatusTuple r(0);
+
+  r = string_to_key(key_str, key);
+  if (!r.ok())
+    return r;
+
+  if (value_str.size() != ncpus)
+    return StatusTuple(-1, "bad value size");
+
+  for (size_t i = 0; i < ncpus; i++) {
+    r = string_to_leaf(value_str.at(i), value + i * desc.leaf_size);
+    if (!r.ok())
+      return r;
+  }
+
+  if (!update(key, value))
+    return StatusTuple(-1, "error updating element");
+
+  return StatusTuple::OK();
+}
+
+StatusTuple BPFTable::remove_value(const std::string& key_str) {
+  char key[desc.key_size];
+
+  StatusTuple r(0);
+
+  r = string_to_key(key_str, key);
+  if (!r.ok())
+    return r;
+
+  if (!remove(key))
+    return StatusTuple(-1, "error removing element");
+
+  return StatusTuple::OK();
+}
+
+StatusTuple BPFTable::clear_table_non_atomic() {
+  if (desc.type == BPF_MAP_TYPE_HASH ||
+      desc.type == BPF_MAP_TYPE_LRU_HASH ||
+      desc.type == BPF_MAP_TYPE_PERCPU_HASH ||
+      desc.type == BPF_MAP_TYPE_HASH_OF_MAPS) {
+    // For hash maps, use the first() interface (which uses get_next_key) to
+    // iterate through the map and clear elements
+    auto key = std::unique_ptr<void, decltype(::free)*>(::malloc(desc.key_size),
+                                                        ::free);
+
+    while (this->first(key.get()))
+      if (!this->remove(key.get())) {
+        return StatusTuple(-1,
+                           "Failed to delete element when clearing table %s",
+                           desc.name.c_str());
+      }
+  } else if (desc.type == BPF_MAP_TYPE_ARRAY ||
+             desc.type == BPF_MAP_TYPE_PERCPU_ARRAY) {
+    return StatusTuple(-1, "Array map %s do not support clearing elements",
+                       desc.name.c_str());
+  } else if (desc.type == BPF_MAP_TYPE_PROG_ARRAY ||
+             desc.type == BPF_MAP_TYPE_PERF_EVENT_ARRAY ||
+             desc.type == BPF_MAP_TYPE_STACK_TRACE ||
+             desc.type == BPF_MAP_TYPE_ARRAY_OF_MAPS) {
+    // For Stack-trace and FD arrays, just iterate over all indices
+    for (size_t i = 0; i < desc.max_entries; i++) {
+      this->remove(&i);
+    }
+  } else {
+    return StatusTuple(-1, "Clearing for map type of %s not supported yet",
+                       desc.name.c_str());
+  }
+
+  return StatusTuple::OK();
+}
+
+StatusTuple BPFTable::get_table_offline(
+  std::vector<std::pair<std::string, std::string>> &res) {
+  StatusTuple r(0);
+  int err;
+
+  auto key = std::unique_ptr<void, decltype(::free)*>(::malloc(desc.key_size),
+                                                      ::free);
+  auto value = std::unique_ptr<void, decltype(::free)*>(::malloc(desc.leaf_size),
+                                                      ::free);
+  std::string key_str;
+  std::string value_str;
+
+  if (desc.type == BPF_MAP_TYPE_ARRAY ||
+      desc.type == BPF_MAP_TYPE_PROG_ARRAY ||
+      desc.type == BPF_MAP_TYPE_PERF_EVENT_ARRAY ||
+      desc.type == BPF_MAP_TYPE_PERCPU_ARRAY ||
+      desc.type == BPF_MAP_TYPE_CGROUP_ARRAY ||
+      desc.type == BPF_MAP_TYPE_ARRAY_OF_MAPS ||
+      desc.type == BPF_MAP_TYPE_DEVMAP ||
+      desc.type == BPF_MAP_TYPE_CPUMAP ||
+      desc.type == BPF_MAP_TYPE_REUSEPORT_SOCKARRAY) {
+    // For arrays, just iterate over all indices
+    for (size_t i = 0; i < desc.max_entries; i++) {
+      err = bpf_lookup_elem(desc.fd, &i, value.get());
+      if (err < 0 && errno == ENOENT) {
+        // Element is not present, skip it
+        continue;
+      } else if (err < 0) {
+        // Other error, abort
+        return StatusTuple(-1, "Error looking up value: %s", std::strerror(errno));
+      }
+
+      r = key_to_string(&i, key_str);
+      if (!r.ok())
+        return r;
+
+      r = leaf_to_string(value.get(), value_str);
+      if (!r.ok())
+        return r;
+      res.emplace_back(key_str, value_str);
+    }
+  } else {
+    res.clear();
+    // For other maps, try to use the first() and next() interfaces
+    if (!this->first(key.get()))
+      return StatusTuple::OK();
+
+    while (true) {
+      if (!this->lookup(key.get(), value.get()))
+        break;
+      r = key_to_string(key.get(), key_str);
+      if (!r.ok())
+        return r;
+
+      r = leaf_to_string(value.get(), value_str);
+      if (!r.ok())
+        return r;
+      res.emplace_back(key_str, value_str);
+      if (!this->next(key.get(), key.get()))
+        break;
+    }
+  }
+
+  return StatusTuple::OK();
+}
+
+StatusTuple BPFTable::get_table_offline_ptr(
+		std::vector<std::pair<std::vector<char>, std::vector<char>>> &res) {
+	StatusTuple r(0);
+	int err;
+
+	auto key = std::unique_ptr<void, decltype(::free)*>(::malloc(desc.key_size),
+	                                                    ::free);
+	auto value = std::unique_ptr<void, decltype(::free)*>(::malloc(desc.leaf_size),
+	                                                      ::free);
+	std::string key_str;
+	std::string value_str;
+
+	if (desc.type == BPF_MAP_TYPE_ARRAY ||
+	    desc.type == BPF_MAP_TYPE_PROG_ARRAY ||
+	    desc.type == BPF_MAP_TYPE_PERF_EVENT_ARRAY ||
+	    desc.type == BPF_MAP_TYPE_PERCPU_ARRAY ||
+	    desc.type == BPF_MAP_TYPE_CGROUP_ARRAY ||
+	    desc.type == BPF_MAP_TYPE_ARRAY_OF_MAPS ||
+	    desc.type == BPF_MAP_TYPE_DEVMAP ||
+	    desc.type == BPF_MAP_TYPE_CPUMAP ||
+	    desc.type == BPF_MAP_TYPE_REUSEPORT_SOCKARRAY) {
+		// For arrays, just iterate over all indices
+		for (size_t i = 0; i < desc.max_entries; i++) {
+			err = bpf_lookup_elem(desc.fd, &i, value.get());
+			if (err < 0 && errno == ENOENT) {
+				// Element is not present, skip it
+				continue;
+			} else if (err < 0) {
+				// Other error, abort
+				return StatusTuple(-1, "Error looking up value: %s", std::strerror(errno));
+			}
+
+			r = key_to_string(&i, key_str);
+			if (!r.ok())
+				return r;
+
+			r = leaf_to_string(value.get(), value_str);
+			if (!r.ok())
+				return r;
+			std::vector<char> key_vec(desc.key_size);
+			std::vector<char> value_vec(desc.leaf_size);
+			std::memcpy(key_vec.data(), key.get(), desc.key_size);
+			std::memcpy(value_vec.data(), value.get(), desc.leaf_size);
+
+			res.emplace_back(key_vec, value_vec);
+		}
+	} else {
+		res.clear();
+		// For other maps, try to use the first() and next() interfaces
+		if (!this->first(key.get()))
+			return StatusTuple::OK();
+
+		while (true) {
+			if (!this->lookup(key.get(), value.get()))
+				break;
+			r = key_to_string(key.get(), key_str);
+			if (!r.ok())
+				return r;
+
+			r = leaf_to_string(value.get(), value_str);
+			if (!r.ok())
+				return r;
+
+
+			std::vector<char> key_vec(desc.key_size);
+			std::vector<char> value_vec(desc.leaf_size);
+			std::memcpy(key_vec.data(), key.get(), desc.key_size);
+			std::memcpy(value_vec.data(), value.get(), desc.leaf_size);
+
+			res.emplace_back(key_vec, value_vec);
+			if (!this->next(key.get(), key.get()))
+				break;
+		}
+	}
+
+	return StatusTuple::OK();
+}
+
+size_t BPFTable::get_possible_cpu_count() { return get_possible_cpus().size(); }
+
+BPFStackTable::BPFStackTable(const TableDesc& desc, bool use_debug_file,
+                             bool check_debug_file_crc)
+    : BPFTableBase<int, stacktrace_t>(desc) {
+  if (desc.type != BPF_MAP_TYPE_STACK_TRACE)
+    throw std::invalid_argument("Table '" + desc.name +
+                                "' is not a stack table");
+
+  uint32_t use_symbol_type = (1 << STT_FUNC) | (1 << STT_GNU_IFUNC);
+  symbol_option_ = {.use_debug_file = use_debug_file,
+                    .check_debug_file_crc = check_debug_file_crc,
+                    .lazy_symbolize = 1,
+                    .use_symbol_type = use_symbol_type};
+}
+
+BPFStackTable::BPFStackTable(BPFStackTable&& that)
+    : BPFTableBase<int, stacktrace_t>(that.desc),
+      symbol_option_(std::move(that.symbol_option_)),
+      pid_sym_(std::move(that.pid_sym_)) {
+  that.pid_sym_.clear();
+}
+
+BPFStackTable::~BPFStackTable() {
+  for (auto it : pid_sym_)
+    bcc_free_symcache(it.second, it.first);
+}
+
+void BPFStackTable::free_symcache(int pid) {
+  auto iter = pid_sym_.find(pid);
+  if (iter != pid_sym_.end()) {
+    bcc_free_symcache(iter->second, iter->first);
+    pid_sym_.erase(iter);
+  }
+}
+
+void BPFStackTable::clear_table_non_atomic() {
+  for (int i = 0; size_t(i) < capacity(); i++) {
+    remove(&i);
+  }
+}
+
+std::vector<uintptr_t> BPFStackTable::get_stack_addr(int stack_id) {
+  std::vector<uintptr_t> res;
+  stacktrace_t stack;
+  if (stack_id < 0)
+    return res;
+  if (!lookup(&stack_id, &stack))
+    return res;
+  for (int i = 0; (i < BPF_MAX_STACK_DEPTH) && (stack.ip[i] != 0); i++)
+    res.push_back(stack.ip[i]);
+  return res;
+}
+
+std::vector<std::string> BPFStackTable::get_stack_symbol(int stack_id,
+                                                         int pid) {
+  auto addresses = get_stack_addr(stack_id);
+  std::vector<std::string> res;
+  if (addresses.empty())
+    return res;
+  res.reserve(addresses.size());
+
+  if (pid < 0)
+    pid = -1;
+  if (pid_sym_.find(pid) == pid_sym_.end())
+    pid_sym_[pid] = bcc_symcache_new(pid, &symbol_option_);
+  void* cache = pid_sym_[pid];
+
+  bcc_symbol symbol;
+  for (auto addr : addresses)
+    if (bcc_symcache_resolve(cache, addr, &symbol) != 0)
+      res.emplace_back("[UNKNOWN]");
+    else {
+      res.push_back(symbol.demangle_name);
+      bcc_symbol_free_demangle_name(&symbol);
+    }
+
+  return res;
+}
+
+BPFStackBuildIdTable::BPFStackBuildIdTable(const TableDesc& desc, bool use_debug_file,
+                                           bool check_debug_file_crc,
+                                           void *bsymcache)
+    : BPFTableBase<int, stacktrace_buildid_t>(desc),
+      bsymcache_(bsymcache) {
+  if (desc.type != BPF_MAP_TYPE_STACK_TRACE)
+    throw std::invalid_argument("Table '" + desc.name +
+                                "' is not a stack table");
+
+  symbol_option_ = {.use_debug_file = use_debug_file,
+                    .check_debug_file_crc = check_debug_file_crc,
+                    .lazy_symbolize = 1,
+                    .use_symbol_type = (1 << STT_FUNC) | (1 << STT_GNU_IFUNC)};
+}
+
+void BPFStackBuildIdTable::clear_table_non_atomic() {
+  for (int i = 0; size_t(i) < capacity(); i++) {
+    remove(&i);
+  }
+}
+
+std::vector<bpf_stack_build_id> BPFStackBuildIdTable::get_stack_addr(int stack_id) {
+  std::vector<bpf_stack_build_id> res;
+  struct stacktrace_buildid_t stack;
+  if (stack_id < 0)
+    return res;
+  if (!lookup(&stack_id, &stack))
+    return res;
+  for (int i = 0; (i < BPF_MAX_STACK_DEPTH) && \
+       (stack.trace[i].status == BPF_STACK_BUILD_ID_VALID);
+       i++) {
+        /* End of stack marker is BCC_STACK_BUILD_ID_EMPTY or
+         * BCC_STACK_BUILD_IP(fallback) mechanism.
+         * We do not support fallback mechanism
+         */
+    res.push_back(stack.trace[i]);
+  }
+  return res;
+}
+
+std::vector<std::string> BPFStackBuildIdTable::get_stack_symbol(int stack_id)
+{
+  auto addresses = get_stack_addr(stack_id);
+  std::vector<std::string> res;
+  if (addresses.empty())
+    return res;
+  res.reserve(addresses.size());
+
+  bcc_symbol symbol;
+  struct bpf_stack_build_id trace;
+  for (auto addr : addresses) {
+    memcpy(trace.build_id, addr.build_id, sizeof(trace.build_id));
+    trace.status = addr.status;
+    trace.offset = addr.offset;
+    if (bcc_buildsymcache_resolve(bsymcache_,&trace,&symbol) != 0) {
+      res.emplace_back("[UNKNOWN]");
+    } else {
+      res.push_back(symbol.name);
+      bcc_symbol_free_demangle_name(&symbol);
+    }
+  }
+  return res;
+}
+
+BPFPerfBuffer::BPFPerfBuffer(const TableDesc& desc)
+    : BPFTableBase<int, int>(desc), epfd_(-1) {
+  if (desc.type != BPF_MAP_TYPE_PERF_EVENT_ARRAY)
+    throw std::invalid_argument("Table '" + desc.name +
+                                "' is not a perf buffer");
+}
+
+StatusTuple BPFPerfBuffer::open_on_cpu(perf_reader_raw_cb cb, perf_reader_lost_cb lost_cb,
+                                       void* cb_cookie, int page_cnt,
+                                       struct bcc_perf_buffer_opts& opts) {
+  if (cpu_readers_.find(opts.cpu) != cpu_readers_.end())
+    return StatusTuple(-1, "Perf buffer already open on CPU %d", opts.cpu);
+
+  auto reader = static_cast<perf_reader*>(
+      bpf_open_perf_buffer_opts(cb, lost_cb, cb_cookie, page_cnt, &opts));
+  if (reader == nullptr)
+    return StatusTuple(-1, "Unable to construct perf reader");
+
+  int reader_fd = perf_reader_fd(reader);
+  if (!update(&opts.cpu, &reader_fd)) {
+    perf_reader_free(static_cast<void*>(reader));
+    return StatusTuple(-1, "Unable to open perf buffer on CPU %d: %s", opts.cpu,
+                       std::strerror(errno));
+  }
+
+  struct epoll_event event = {};
+  event.events = EPOLLIN;
+  event.data.ptr = static_cast<void*>(reader);
+  if (epoll_ctl(epfd_, EPOLL_CTL_ADD, reader_fd, &event) != 0) {
+    perf_reader_free(static_cast<void*>(reader));
+    return StatusTuple(-1, "Unable to add perf_reader FD to epoll: %s",
+                       std::strerror(errno));
+  }
+
+  cpu_readers_[opts.cpu] = reader;
+  return StatusTuple::OK();
+}
+
+StatusTuple BPFPerfBuffer::open_all_cpu(perf_reader_raw_cb cb,
+                                        perf_reader_lost_cb lost_cb,
+                                        void* cb_cookie, int page_cnt) {
+  return open_all_cpu(cb, lost_cb, cb_cookie, page_cnt, 1);
+}
+
+StatusTuple BPFPerfBuffer::open_all_cpu(perf_reader_raw_cb cb,
+                                        perf_reader_lost_cb lost_cb,
+                                        void* cb_cookie, int page_cnt,
+                                        int wakeup_events)
+{
+  if (cpu_readers_.size() != 0 || epfd_ != -1)
+    return StatusTuple(-1, "Previously opened perf buffer not cleaned");
+
+  std::vector<int> cpus = get_online_cpus();
+  ep_events_.reset(new epoll_event[cpus.size()]);
+  epfd_ = epoll_create1(EPOLL_CLOEXEC);
+
+  for (int i : cpus) {
+    struct bcc_perf_buffer_opts opts = {
+      .pid = -1,
+      .cpu = i,
+      .wakeup_events = wakeup_events,
+    };
+    auto res = open_on_cpu(cb, lost_cb, cb_cookie, page_cnt, opts);
+    if (!res.ok()) {
+      TRY2(close_all_cpu());
+      return res;
+    }
+  }
+  return StatusTuple::OK();
+}
+
+StatusTuple BPFPerfBuffer::close_on_cpu(int cpu) {
+  auto it = cpu_readers_.find(cpu);
+  if (it == cpu_readers_.end())
+    return StatusTuple::OK();
+  perf_reader_free(static_cast<void*>(it->second));
+  if (!remove(const_cast<int*>(&(it->first))))
+    return StatusTuple(-1, "Unable to close perf buffer on CPU %d", it->first);
+  cpu_readers_.erase(it);
+  return StatusTuple::OK();
+}
+
+StatusTuple BPFPerfBuffer::close_all_cpu() {
+  std::string errors;
+  bool has_error = false;
+
+  if (epfd_ >= 0) {
+    int close_res = close(epfd_);
+    epfd_ = -1;
+    ep_events_.reset();
+    if (close_res != 0) {
+      has_error = true;
+      errors += std::string(std::strerror(errno)) + "\n";
+    }
+  }
+
+  std::vector<int> opened_cpus;
+  for (auto it : cpu_readers_)
+    opened_cpus.push_back(it.first);
+  for (int i : opened_cpus) {
+    auto res = close_on_cpu(i);
+    if (!res.ok()) {
+      errors += "Failed to close CPU" + std::to_string(i) + " perf buffer: ";
+      errors += res.msg() + "\n";
+      has_error = true;
+    }
+  }
+
+  if (has_error)
+    return StatusTuple(-1, errors);
+  return StatusTuple::OK();
+}
+
+int BPFPerfBuffer::poll(int timeout_ms) {
+  if (epfd_ < 0)
+    return -1;
+  int cnt =
+      epoll_wait(epfd_, ep_events_.get(), cpu_readers_.size(), timeout_ms);
+  for (int i = 0; i < cnt; i++)
+    perf_reader_event_read(static_cast<perf_reader*>(ep_events_[i].data.ptr));
+  return cnt;
+}
+
+int BPFPerfBuffer::consume() {
+  if (epfd_ < 0)
+    return -1;
+  for (auto it : cpu_readers_)
+    perf_reader_event_read(it.second);
+  return 0;
+}
+
+BPFPerfBuffer::~BPFPerfBuffer() {
+  auto res = close_all_cpu();
+  if (!res.ok())
+    std::cerr << "Failed to close all perf buffer on destruction: " << res.msg()
+              << std::endl;
+}
+
+BPFPerfEventArray::BPFPerfEventArray(const TableDesc& desc)
+    : BPFTableBase<int, int>(desc) {
+  if (desc.type != BPF_MAP_TYPE_PERF_EVENT_ARRAY)
+    throw std::invalid_argument("Table '" + desc.name +
+                                "' is not a perf event array");
+}
+
+StatusTuple BPFPerfEventArray::open_all_cpu(uint32_t type, uint64_t config,
+                                            int pid) {
+  if (cpu_fds_.size() != 0)
+    return StatusTuple(-1, "Previously opened perf event not cleaned");
+
+  std::vector<int> cpus = get_online_cpus();
+
+  for (int i : cpus) {
+    auto res = open_on_cpu(i, type, config, pid);
+    if (!res.ok()) {
+      TRY2(close_all_cpu());
+      return res;
+    }
+  }
+  return StatusTuple::OK();
+}
+
+StatusTuple BPFPerfEventArray::close_all_cpu() {
+  std::string errors;
+  bool has_error = false;
+
+  std::vector<int> opened_cpus;
+  for (auto it : cpu_fds_)
+    opened_cpus.push_back(it.first);
+  for (int i : opened_cpus) {
+    auto res = close_on_cpu(i);
+    if (!res.ok()) {
+      errors += "Failed to close CPU" + std::to_string(i) + " perf event: ";
+      errors += res.msg() + "\n";
+      has_error = true;
+    }
+  }
+
+  if (has_error)
+    return StatusTuple(-1, errors);
+  return StatusTuple::OK();
+}
+
+StatusTuple BPFPerfEventArray::open_on_cpu(int cpu, uint32_t type,
+                                           uint64_t config, int pid) {
+  if (cpu_fds_.find(cpu) != cpu_fds_.end())
+    return StatusTuple(-1, "Perf event already open on CPU %d", cpu);
+  int fd = bpf_open_perf_event(type, config, pid, cpu);
+  if (fd < 0) {
+    return StatusTuple(-1, "Error constructing perf event %" PRIu32 ":%" PRIu64,
+                       type, config);
+  }
+  if (!update(&cpu, &fd)) {
+    bpf_close_perf_event_fd(fd);
+    return StatusTuple(-1, "Unable to open perf event on CPU %d: %s", cpu,
+                       std::strerror(errno));
+  }
+  cpu_fds_[cpu] = fd;
+  return StatusTuple::OK();
+}
+
+StatusTuple BPFPerfEventArray::close_on_cpu(int cpu) {
+  auto it = cpu_fds_.find(cpu);
+  if (it == cpu_fds_.end()) {
+    return StatusTuple::OK();
+  }
+  bpf_close_perf_event_fd(it->second);
+  cpu_fds_.erase(it);
+  return StatusTuple::OK();
+}
+
+BPFPerfEventArray::~BPFPerfEventArray() {
+  auto res = close_all_cpu();
+  if (!res.ok()) {
+    std::cerr << "Failed to close all perf buffer on destruction: " << res.msg()
+              << std::endl;
+  }
+}
+
+BPFProgTable::BPFProgTable(const TableDesc& desc)
+    : BPFTableBase<int, int>(desc) {
+  if (desc.type != BPF_MAP_TYPE_PROG_ARRAY)
+    throw std::invalid_argument("Table '" + desc.name +
+                                "' is not a prog table");
+}
+
+StatusTuple BPFProgTable::update_value(const int& index, const int& prog_fd) {
+  if (!this->update(const_cast<int*>(&index), const_cast<int*>(&prog_fd)))
+    return StatusTuple(-1, "Error updating value: %s", std::strerror(errno));
+  return StatusTuple::OK();
+}
+
+StatusTuple BPFProgTable::remove_value(const int& index) {
+  if (!this->remove(const_cast<int*>(&index)))
+    return StatusTuple(-1, "Error removing value: %s", std::strerror(errno));
+  return StatusTuple::OK();
+}
+
+BPFCgroupArray::BPFCgroupArray(const TableDesc& desc)
+    : BPFTableBase<int, int>(desc) {
+  if (desc.type != BPF_MAP_TYPE_CGROUP_ARRAY)
+    throw std::invalid_argument("Table '" + desc.name +
+                                "' is not a cgroup array");
+}
+
+StatusTuple BPFCgroupArray::update_value(const int& index,
+                                         const int& cgroup2_fd) {
+  if (!this->update(const_cast<int*>(&index), const_cast<int*>(&cgroup2_fd)))
+    return StatusTuple(-1, "Error updating value: %s", std::strerror(errno));
+  return StatusTuple::OK();
+}
+
+StatusTuple BPFCgroupArray::update_value(const int& index,
+                                         const std::string& cgroup2_path) {
+  FileDesc f(::open(cgroup2_path.c_str(), O_RDONLY | O_CLOEXEC));
+  if ((int)f < 0)
+    return StatusTuple(-1, "Unable to open %s", cgroup2_path.c_str());
+  TRY2(update_value(index, (int)f));
+  return StatusTuple::OK();
+}
+
+StatusTuple BPFCgroupArray::remove_value(const int& index) {
+  if (!this->remove(const_cast<int*>(&index)))
+    return StatusTuple(-1, "Error removing value: %s", std::strerror(errno));
+  return StatusTuple::OK();
+}
+
+BPFDevmapTable::BPFDevmapTable(const TableDesc& desc)
+    : BPFTableBase<int, int>(desc) {
+    if(desc.type != BPF_MAP_TYPE_DEVMAP)
+      throw std::invalid_argument("Table '" + desc.name +
+                                  "' is not a devmap table");
+}
+
+StatusTuple BPFDevmapTable::update_value(const int& index,
+                                         const int& value) {
+    if (!this->update(const_cast<int*>(&index), const_cast<int*>(&value)))
+      return StatusTuple(-1, "Error updating value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+}
+
+StatusTuple BPFDevmapTable::get_value(const int& index,
+                                      int& value) {
+    if (!this->lookup(const_cast<int*>(&index), &value))
+      return StatusTuple(-1, "Error getting value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+}
+
+StatusTuple BPFDevmapTable::remove_value(const int& index) {
+    if (!this->remove(const_cast<int*>(&index)))
+      return StatusTuple(-1, "Error removing value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+}
+
+BPFXskmapTable::BPFXskmapTable(const TableDesc& desc)
+    : BPFTableBase<int, int>(desc) {
+    if(desc.type != BPF_MAP_TYPE_XSKMAP)
+      throw std::invalid_argument("Table '" + desc.name +
+                                  "' is not a xskmap table");
+}
+
+StatusTuple BPFXskmapTable::update_value(const int& index,
+                                         const int& value) {
+    if (!this->update(const_cast<int*>(&index), const_cast<int*>(&value)))
+      return StatusTuple(-1, "Error updating value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+}
+
+StatusTuple BPFXskmapTable::get_value(const int& index,
+                                      int& value) {
+    if (!this->lookup(const_cast<int*>(&index), &value))
+      return StatusTuple(-1, "Error getting value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+}
+
+StatusTuple BPFXskmapTable::remove_value(const int& index) {
+    if (!this->remove(const_cast<int*>(&index)))
+      return StatusTuple(-1, "Error removing value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+}
+
+BPFSockmapTable::BPFSockmapTable(const TableDesc& desc)
+    : BPFTableBase<int, int>(desc) {
+    if(desc.type != BPF_MAP_TYPE_SOCKMAP)
+      throw std::invalid_argument("Table '" + desc.name +
+                                  "' is not a sockmap table");
+}
+
+StatusTuple BPFSockmapTable::update_value(const int& index,
+                                         const int& value) {
+    if (!this->update(const_cast<int*>(&index), const_cast<int*>(&value)))
+      return StatusTuple(-1, "Error updating value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+}
+
+StatusTuple BPFSockmapTable::remove_value(const int& index) {
+    if (!this->remove(const_cast<int*>(&index)))
+      return StatusTuple(-1, "Error removing value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+}
+
+BPFSockhashTable::BPFSockhashTable(const TableDesc& desc)
+    : BPFTableBase<int, int>(desc) {
+    if(desc.type != BPF_MAP_TYPE_SOCKHASH)
+      throw std::invalid_argument("Table '" + desc.name +
+                                  "' is not a sockhash table");
+}
+
+StatusTuple BPFSockhashTable::update_value(const int& key,
+                                         const int& value) {
+    if (!this->update(const_cast<int*>(&key), const_cast<int*>(&value)))
+      return StatusTuple(-1, "Error updating value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+}
+
+StatusTuple BPFSockhashTable::remove_value(const int& key) {
+    if (!this->remove(const_cast<int*>(&key)))
+      return StatusTuple(-1, "Error removing value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+}
+
+}  // namespace ebpf

+ 670 - 0
api/BPFTable.h

@@ -0,0 +1,670 @@
+/*
+ * Copyright (c) 2016 Facebook, 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 <errno.h>
+#include <sys/epoll.h>
+#include <cstring>
+#include <exception>
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "bcc_exception.h"
+#include "bcc_syms.h"
+#include "bpf_module.h"
+#include "libbpf.h"
+#include "perf_reader.h"
+#include "table_desc.h"
+#include "linux/bpf.h"
+
+namespace ebpf {
+
+template<class ValueType>
+class BPFQueueStackTableBase {
+ public:
+  size_t capacity() const { return desc.max_entries; }
+
+  StatusTuple string_to_leaf(const std::string& value_str, ValueType* value) {
+    return desc.leaf_sscanf(value_str.c_str(), value);
+  }
+
+  StatusTuple leaf_to_string(const ValueType* value, std::string& value_str) {
+    char buf[8 * desc.leaf_size];
+    StatusTuple rc = desc.leaf_snprintf(buf, sizeof(buf), value);
+    if (rc.ok())
+      value_str.assign(buf);
+    return rc;
+  }
+
+  int get_fd() { return desc.fd; }
+
+ protected:
+  explicit BPFQueueStackTableBase(const TableDesc& desc) : desc(desc) {}
+
+  bool pop(void *value) {
+    return bpf_lookup_and_delete(desc.fd, nullptr, value) >= 0;
+  }
+  // Flags are extremely useful, since they completely changes extraction behaviour
+  // (eg. if flag BPF_EXIST, then if the queue/stack is full remove the oldest one)
+  bool push(void *value, unsigned long long int flags) {
+    return bpf_update_elem(desc.fd, nullptr, value, flags) >= 0;
+  }
+
+  bool peek(void *value) {
+    return bpf_lookup_elem(desc.fd, nullptr, value) >= 0;
+  }
+
+  const TableDesc& desc;
+};
+
+template <class KeyType, class ValueType>
+class BPFTableBase {
+ public:
+  size_t capacity() { return desc.max_entries; }
+
+  StatusTuple string_to_key(const std::string& key_str, KeyType* key) {
+    return desc.key_sscanf(key_str.c_str(), key);
+  }
+
+  StatusTuple string_to_leaf(const std::string& value_str, ValueType* value) {
+    return desc.leaf_sscanf(value_str.c_str(), value);
+  }
+
+  StatusTuple key_to_string(const KeyType* key, std::string& key_str) {
+    char buf[8 * desc.key_size];
+    StatusTuple rc = desc.key_snprintf(buf, sizeof(buf), key);
+    if (rc.ok())
+      key_str.assign(buf);
+    return rc;
+  }
+
+  StatusTuple leaf_to_string(const ValueType* value, std::string& value_str) {
+    char buf[8 * desc.leaf_size];
+    StatusTuple rc = desc.leaf_snprintf(buf, sizeof(buf), value);
+    if (rc.ok())
+      value_str.assign(buf);
+    return rc;
+  }
+
+  int get_fd() {
+    return desc.fd;
+  }
+
+ protected:
+  explicit BPFTableBase(const TableDesc& desc) : desc(desc) {}
+
+  bool lookup(void* key, void* value) {
+    return bpf_lookup_elem(desc.fd, key, value) >= 0;
+  }
+
+  bool first(void* key) {
+    return bpf_get_first_key(desc.fd, key, desc.key_size) >= 0;
+  }
+
+  bool next(void* key, void* next_key) {
+    return bpf_get_next_key(desc.fd, key, next_key) >= 0;
+  }
+
+  bool update(void* key, void* value) {
+    return bpf_update_elem(desc.fd, key, value, 0) >= 0;
+  }
+
+  bool remove(void* key) { return bpf_delete_elem(desc.fd, key) >= 0; }
+
+  const TableDesc& desc;
+};
+
+class BPFTable : public BPFTableBase<void, void> {
+ public:
+  BPFTable(const TableDesc& desc);
+
+  StatusTuple get_value(const std::string& key_str, std::string& value);
+  StatusTuple get_value(const std::string& key_str,
+                        std::vector<std::string>& value);
+
+  StatusTuple update_value(const std::string& key_str,
+                           const std::string& value_str);
+  StatusTuple update_value(const std::string& key_str,
+                           const std::vector<std::string>& value_str);
+
+  StatusTuple remove_value(const std::string& key_str);
+
+  StatusTuple clear_table_non_atomic();
+  StatusTuple get_table_offline(std::vector<std::pair<std::string, std::string>> &res);
+  StatusTuple get_table_offline_ptr(std::vector<std::pair<std::vector<char>, std::vector<char>>> &res);
+
+  static size_t get_possible_cpu_count();
+};
+
+template <class ValueType>
+void* get_value_addr(ValueType& t) {
+  return &t;
+}
+
+template <class ValueType>
+void* get_value_addr(std::vector<ValueType>& t) {
+  return t.data();
+}
+
+template<class ValueType>
+class BPFQueueStackTable : public BPFQueueStackTableBase<void> {
+ public:
+  explicit BPFQueueStackTable(const TableDesc& desc) : BPFQueueStackTableBase(desc) {
+    if (desc.type != BPF_MAP_TYPE_QUEUE &&
+        desc.type != BPF_MAP_TYPE_STACK)
+      throw std::invalid_argument("Table '" + desc.name +
+                                  "' is not a queue/stack table");
+  }
+
+  virtual StatusTuple pop_value(ValueType& value) {
+    if (!this->pop(get_value_addr(value)))
+      return StatusTuple(-1, "Error getting value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+
+  virtual StatusTuple push_value(const ValueType& value, unsigned long long int flags = 0) {
+    if (!this->push(get_value_addr(const_cast<ValueType&>(value)), flags))
+      return StatusTuple(-1, "Error updating value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+
+  virtual StatusTuple get_head(const ValueType& value) {
+    if (!this->peek(get_value_addr(const_cast<ValueType&>(value))))
+      return StatusTuple(-1, "Error peeking value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+};
+
+template <class ValueType>
+class BPFArrayTable : public BPFTableBase<int, ValueType> {
+ public:
+  BPFArrayTable(const TableDesc& desc) : BPFTableBase<int, ValueType>(desc) {
+    if (desc.type != BPF_MAP_TYPE_ARRAY &&
+        desc.type != BPF_MAP_TYPE_PERCPU_ARRAY)
+      throw std::invalid_argument("Table '" + desc.name +
+                                  "' is not an array table");
+  }
+
+  virtual StatusTuple get_value(const int& index, ValueType& value) {
+    if (!this->lookup(const_cast<int*>(&index), get_value_addr(value)))
+      return StatusTuple(-1, "Error getting value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+
+  virtual StatusTuple update_value(const int& index, const ValueType& value) {
+    if (!this->update(const_cast<int*>(&index),
+                      get_value_addr(const_cast<ValueType&>(value))))
+      return StatusTuple(-1, "Error updating value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+
+  ValueType operator[](const int& key) {
+    ValueType value;
+    get_value(key, value);
+    return value;
+  }
+
+  std::vector<ValueType> get_table_offline() {
+    std::vector<ValueType> res(this->capacity());
+
+    for (int i = 0; i < (int)this->capacity(); i++) {
+      get_value(i, res[i]);
+    }
+
+    return res;
+  }
+};
+
+template <class ValueType>
+class BPFPercpuArrayTable : public BPFArrayTable<std::vector<ValueType>> {
+ public:
+  BPFPercpuArrayTable(const TableDesc& desc)
+      : BPFArrayTable<std::vector<ValueType>>(desc),
+        ncpus(BPFTable::get_possible_cpu_count()) {
+    if (desc.type != BPF_MAP_TYPE_PERCPU_ARRAY)
+      throw std::invalid_argument("Table '" + desc.name +
+                                  "' is not a percpu array table");
+    // leaf structures have to be aligned to 8 bytes as hardcoded in the linux
+    // kernel.
+    if (sizeof(ValueType) % 8)
+      throw std::invalid_argument("leaf must be aligned to 8 bytes");
+  }
+
+  StatusTuple get_value(const int& index, std::vector<ValueType>& value) {
+    value.resize(ncpus);
+    return BPFArrayTable<std::vector<ValueType>>::get_value(index, value);
+  }
+
+  StatusTuple update_value(const int& index,
+                           const std::vector<ValueType>& value) {
+    if (value.size() != ncpus)
+      return StatusTuple(-1, "bad value size");
+    return BPFArrayTable<std::vector<ValueType>>::update_value(index, value);
+  }
+
+ private:
+  unsigned int ncpus;
+};
+
+template <class KeyType, class ValueType>
+class BPFHashTable : public BPFTableBase<KeyType, ValueType> {
+ public:
+  explicit BPFHashTable(const TableDesc& desc)
+      : BPFTableBase<KeyType, ValueType>(desc) {
+    if (desc.type != BPF_MAP_TYPE_HASH &&
+        desc.type != BPF_MAP_TYPE_PERCPU_HASH &&
+        desc.type != BPF_MAP_TYPE_LRU_HASH &&
+        desc.type != BPF_MAP_TYPE_LRU_PERCPU_HASH)
+      throw std::invalid_argument("Table '" + desc.name +
+                                  "' is not a hash table");
+  }
+
+  virtual StatusTuple get_value(const KeyType& key, ValueType& value) {
+    if (!this->lookup(const_cast<KeyType*>(&key), get_value_addr(value)))
+      return StatusTuple(-1, "Error getting value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+
+  virtual StatusTuple update_value(const KeyType& key, const ValueType& value) {
+    if (!this->update(const_cast<KeyType*>(&key),
+                      get_value_addr(const_cast<ValueType&>(value))))
+      return StatusTuple(-1, "Error updating value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+
+  virtual StatusTuple remove_value(const KeyType& key) {
+    if (!this->remove(const_cast<KeyType*>(&key)))
+      return StatusTuple(-1, "Error removing value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+
+  ValueType operator[](const KeyType& key) {
+    ValueType value;
+    get_value(key, value);
+    return value;
+  }
+
+  std::vector<std::pair<KeyType, ValueType>> get_table_offline() {
+    std::vector<std::pair<KeyType, ValueType>> res;
+    KeyType cur;
+    ValueType value;
+
+    StatusTuple r(0);
+
+    if (!this->first(&cur))
+      return res;
+
+    while (true) {
+      r = get_value(cur, value);
+      if (!r.ok())
+        break;
+      res.emplace_back(cur, value);
+      if (!this->next(&cur, &cur))
+        break;
+    }
+
+    return res;
+  }
+
+  StatusTuple clear_table_non_atomic() {
+    KeyType cur;
+    while (this->first(&cur))
+      TRY2(remove_value(cur));
+
+    return StatusTuple::OK();
+  }
+};
+
+template <class KeyType, class ValueType>
+class BPFPercpuHashTable
+    : public BPFHashTable<KeyType, std::vector<ValueType>> {
+ public:
+  explicit BPFPercpuHashTable(const TableDesc& desc)
+      : BPFHashTable<KeyType, std::vector<ValueType>>(desc),
+        ncpus(BPFTable::get_possible_cpu_count()) {
+    if (desc.type != BPF_MAP_TYPE_PERCPU_HASH &&
+        desc.type != BPF_MAP_TYPE_LRU_PERCPU_HASH)
+      throw std::invalid_argument("Table '" + desc.name +
+                                  "' is not a percpu hash table");
+    // leaf structures have to be aligned to 8 bytes as hardcoded in the linux
+    // kernel.
+    if (sizeof(ValueType) % 8)
+      throw std::invalid_argument("leaf must be aligned to 8 bytes");
+  }
+
+  StatusTuple get_value(const KeyType& key, std::vector<ValueType>& value) {
+    value.resize(ncpus);
+    return BPFHashTable<KeyType, std::vector<ValueType>>::get_value(key, value);
+  }
+
+  StatusTuple update_value(const KeyType& key,
+                           const std::vector<ValueType>& value) {
+    if (value.size() != ncpus)
+      return StatusTuple(-1, "bad value size");
+    return BPFHashTable<KeyType, std::vector<ValueType>>::update_value(key,
+                                                                       value);
+  }
+
+ private:
+  unsigned int ncpus;
+};
+
+// From src/cc/export/helpers.h
+static const int BPF_MAX_STACK_DEPTH = 127;
+struct stacktrace_t {
+  uintptr_t ip[BPF_MAX_STACK_DEPTH];
+};
+
+class BPFStackTable : public BPFTableBase<int, stacktrace_t> {
+ public:
+  BPFStackTable(const TableDesc& desc, bool use_debug_file,
+                bool check_debug_file_crc);
+  BPFStackTable(BPFStackTable&& that);
+  ~BPFStackTable();
+
+  void free_symcache(int pid);
+  void clear_table_non_atomic();
+  std::vector<uintptr_t> get_stack_addr(int stack_id);
+  std::vector<std::string> get_stack_symbol(int stack_id, int pid);
+
+ private:
+  bcc_symbol_option symbol_option_;
+  std::map<int, void*> pid_sym_;
+};
+
+// from src/cc/export/helpers.h
+struct stacktrace_buildid_t {
+  struct bpf_stack_build_id trace[BPF_MAX_STACK_DEPTH];
+};
+
+class BPFStackBuildIdTable : public BPFTableBase<int, stacktrace_buildid_t> {
+ public:
+  BPFStackBuildIdTable(const TableDesc& desc, bool use_debug_file,
+                       bool check_debug_file_crc, void *bsymcache);
+  ~BPFStackBuildIdTable() = default;
+
+  void clear_table_non_atomic();
+  std::vector<bpf_stack_build_id> get_stack_addr(int stack_id);
+  std::vector<std::string> get_stack_symbol(int stack_id);
+
+ private:
+  void *bsymcache_;
+  bcc_symbol_option symbol_option_;
+};
+
+class BPFPerfBuffer : public BPFTableBase<int, int> {
+ public:
+  BPFPerfBuffer(const TableDesc& desc);
+  ~BPFPerfBuffer();
+
+  StatusTuple open_all_cpu(perf_reader_raw_cb cb, perf_reader_lost_cb lost_cb,
+                           void* cb_cookie, int page_cnt);
+  StatusTuple open_all_cpu(perf_reader_raw_cb cb, perf_reader_lost_cb lost_cb,
+                           void* cb_cookie, int page_cnt, int wakeup_events);
+  StatusTuple close_all_cpu();
+  int poll(int timeout_ms);
+  int consume();
+
+ private:
+  StatusTuple open_on_cpu(perf_reader_raw_cb cb, perf_reader_lost_cb lost_cb,
+                          void* cb_cookie, int page_cnt, struct bcc_perf_buffer_opts& opts);
+  StatusTuple close_on_cpu(int cpu);
+
+  std::map<int, perf_reader*> cpu_readers_;
+
+  int epfd_;
+  std::unique_ptr<epoll_event[]> ep_events_;
+};
+
+class BPFPerfEventArray : public BPFTableBase<int, int> {
+ public:
+  BPFPerfEventArray(const TableDesc& desc);
+  ~BPFPerfEventArray();
+
+  StatusTuple open_all_cpu(uint32_t type, uint64_t config, int pid = -1);
+  StatusTuple close_all_cpu();
+
+ private:
+  StatusTuple open_on_cpu(int cpu, uint32_t type, uint64_t config, int pid = -1);
+  StatusTuple close_on_cpu(int cpu);
+
+  std::map<int, int> cpu_fds_;
+};
+
+class BPFProgTable : public BPFTableBase<int, int> {
+ public:
+  BPFProgTable(const TableDesc& desc);
+
+  StatusTuple update_value(const int& index, const int& prog_fd);
+  StatusTuple remove_value(const int& index);
+};
+
+class BPFCgroupArray : public BPFTableBase<int, int> {
+ public:
+  BPFCgroupArray(const TableDesc& desc);
+
+  StatusTuple update_value(const int& index, const int& cgroup2_fd);
+  StatusTuple update_value(const int& index, const std::string& cgroup2_path);
+  StatusTuple remove_value(const int& index);
+};
+
+class BPFDevmapTable : public BPFTableBase<int, int> {
+public:
+  BPFDevmapTable(const TableDesc& desc);
+
+  StatusTuple update_value(const int& index, const int& value);
+  StatusTuple get_value(const int& index, int& value);
+  StatusTuple remove_value(const int& index);
+};
+
+class BPFXskmapTable : public BPFTableBase<int, int> {
+public:
+  BPFXskmapTable(const TableDesc& desc);
+
+  StatusTuple update_value(const int& index, const int& value);
+  StatusTuple get_value(const int& index, int& value);
+  StatusTuple remove_value(const int& index);
+};
+
+template <class KeyType>
+class BPFMapInMapTable : public BPFTableBase<KeyType, int> {
+ public:
+  BPFMapInMapTable(const TableDesc& desc) : BPFTableBase<KeyType, int>(desc) {
+    if (desc.type != BPF_MAP_TYPE_ARRAY_OF_MAPS &&
+        desc.type != BPF_MAP_TYPE_HASH_OF_MAPS)
+      throw std::invalid_argument("Table '" + desc.name +
+                                  "' is not a map-in-map table");
+  }
+  virtual StatusTuple update_value(const KeyType& key, const int& inner_map_fd) {
+    if (!this->update(const_cast<KeyType*>(&key),
+                      const_cast<int*>(&inner_map_fd)))
+      return StatusTuple(-1, "Error updating value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+  virtual StatusTuple remove_value(const KeyType& key) {
+    if (!this->remove(const_cast<KeyType*>(&key)))
+      return StatusTuple(-1, "Error removing value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+};
+
+class BPFSockmapTable : public BPFTableBase<int, int> {
+public:
+  BPFSockmapTable(const TableDesc& desc);
+
+  StatusTuple update_value(const int& index, const int& value);
+  StatusTuple remove_value(const int& index);
+};
+
+class BPFSockhashTable : public BPFTableBase<int, int> {
+public:
+  BPFSockhashTable(const TableDesc& desc);
+
+  StatusTuple update_value(const int& key, const int& value);
+  StatusTuple remove_value(const int& key);
+};
+
+template <class ValueType>
+class BPFSkStorageTable : public BPFTableBase<int, ValueType> {
+ public:
+  BPFSkStorageTable(const TableDesc& desc) : BPFTableBase<int, ValueType>(desc) {
+    if (desc.type != BPF_MAP_TYPE_SK_STORAGE)
+      throw std::invalid_argument("Table '" + desc.name +
+                                  "' is not a sk_storage table");
+  }
+
+  virtual StatusTuple get_value(const int& sock_fd, ValueType& value) {
+    if (!this->lookup(const_cast<int*>(&sock_fd), get_value_addr(value)))
+      return StatusTuple(-1, "Error getting value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+
+  virtual StatusTuple update_value(const int& sock_fd, const ValueType& value) {
+    if (!this->update(const_cast<int*>(&sock_fd),
+                      get_value_addr(const_cast<ValueType&>(value))))
+      return StatusTuple(-1, "Error updating value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+
+  virtual StatusTuple remove_value(const int& sock_fd) {
+    if (!this->remove(const_cast<int*>(&sock_fd)))
+      return StatusTuple(-1, "Error removing value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+};
+
+template <class ValueType>
+class BPFInodeStorageTable : public BPFTableBase<int, ValueType> {
+ public:
+  BPFInodeStorageTable(const TableDesc& desc) : BPFTableBase<int, ValueType>(desc) {
+    if (desc.type != BPF_MAP_TYPE_INODE_STORAGE)
+      throw std::invalid_argument("Table '" + desc.name +
+                                  "' is not a inode_storage table");
+  }
+
+  virtual StatusTuple get_value(const int& fd, ValueType& value) {
+    if (!this->lookup(const_cast<int*>(&fd), get_value_addr(value)))
+      return StatusTuple(-1, "Error getting value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+
+  virtual StatusTuple update_value(const int& fd, const ValueType& value) {
+    if (!this->update(const_cast<int*>(&fd),
+                      get_value_addr(const_cast<ValueType&>(value))))
+      return StatusTuple(-1, "Error updating value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+
+  virtual StatusTuple remove_value(const int& fd) {
+    if (!this->remove(const_cast<int*>(&fd)))
+      return StatusTuple(-1, "Error removing value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+};
+
+template <class ValueType>
+class BPFTaskStorageTable : public BPFTableBase<int, ValueType> {
+ public:
+  BPFTaskStorageTable(const TableDesc& desc) : BPFTableBase<int, ValueType>(desc) {
+    if (desc.type != BPF_MAP_TYPE_TASK_STORAGE)
+      throw std::invalid_argument("Table '" + desc.name +
+                                  "' is not a task_storage table");
+  }
+
+  virtual StatusTuple get_value(const int& fd, ValueType& value) {
+    if (!this->lookup(const_cast<int*>(&fd), get_value_addr(value)))
+      return StatusTuple(-1, "Error getting value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+
+  virtual StatusTuple update_value(const int& fd, const ValueType& value) {
+    if (!this->update(const_cast<int*>(&fd),
+                      get_value_addr(const_cast<ValueType&>(value))))
+      return StatusTuple(-1, "Error updating value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+
+  virtual StatusTuple remove_value(const int& fd) {
+    if (!this->remove(const_cast<int*>(&fd)))
+      return StatusTuple(-1, "Error removing value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+};
+
+template <class ValueType>
+class BPFCgStorageTable : public BPFTableBase<int, ValueType> {
+ public:
+  BPFCgStorageTable(const TableDesc& desc) : BPFTableBase<int, ValueType>(desc) {
+    if (desc.type != BPF_MAP_TYPE_CGROUP_STORAGE)
+      throw std::invalid_argument("Table '" + desc.name +
+                                  "' is not a cgroup_storage table");
+  }
+
+  virtual StatusTuple get_value(struct bpf_cgroup_storage_key& key,
+                                ValueType& value) {
+    if (!this->lookup(const_cast<struct bpf_cgroup_storage_key*>(&key),
+                      get_value_addr(value)))
+      return StatusTuple(-1, "Error getting value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+
+  virtual StatusTuple update_value(struct bpf_cgroup_storage_key& key, const ValueType& value) {
+    if (!this->update(const_cast<struct bpf_cgroup_storage_key*>(&key),
+                      get_value_addr(const_cast<ValueType&>(value))))
+      return StatusTuple(-1, "Error updating value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+};
+
+template <class ValueType>
+class BPFPercpuCgStorageTable : public BPFTableBase<int, std::vector<ValueType>> {
+ public:
+  BPFPercpuCgStorageTable(const TableDesc& desc)
+      : BPFTableBase<int, std::vector<ValueType>>(desc) {
+    if (desc.type != BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE)
+      throw std::invalid_argument("Table '" + desc.name +
+                                  "' is not a percpu_cgroup_storage table");
+    if (sizeof(ValueType) % 8)
+      throw std::invalid_argument("leaf must be aligned to 8 bytes");
+    ncpus = BPFTable::get_possible_cpu_count();
+  }
+
+  virtual StatusTuple get_value(struct bpf_cgroup_storage_key& key,
+                                std::vector<ValueType>& value) {
+    value.resize(ncpus);
+    if (!this->lookup(const_cast<struct bpf_cgroup_storage_key*>(&key),
+                      get_value_addr(value)))
+      return StatusTuple(-1, "Error getting value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+
+  virtual StatusTuple update_value(struct bpf_cgroup_storage_key& key,
+                                   std::vector<ValueType>& value) {
+    value.resize(ncpus);
+    if (!this->update(const_cast<struct bpf_cgroup_storage_key*>(&key),
+                      get_value_addr(const_cast<std::vector<ValueType>&>(value))))
+      return StatusTuple(-1, "Error updating value: %s", std::strerror(errno));
+    return StatusTuple::OK();
+  }
+ private:
+  unsigned int ncpus;
+};
+
+}  // namespace ebpf

+ 4 - 0
api/CMakeLists.txt

@@ -0,0 +1,4 @@
+set(bcc_api_sources BPF.cc BPFTable.cc)
+add_library(api-static STATIC ${bcc_api_sources})
+add_library(api-objects OBJECT ${bcc_api_sources})
+install(FILES BPF.h BPFTable.h COMPONENT libbcc DESTINATION include/bcc)

+ 114 - 0
config.m4

@@ -0,0 +1,114 @@
+dnl $Id$
+dnl config.m4 for extension ebpf
+
+dnl Comments in this file start with the string 'dnl'.
+dnl Remove where necessary. This file will not work
+dnl without editing.
+
+dnl If your extension references something external, use with:
+
+dnl PHP_ARG_WITH(ebpf, for ebpf support,
+dnl Make sure that the comment is aligned:
+dnl [  --with-ebpf             Include ebpf support])
+
+dnl Otherwise use enable:
+
+PHP_ARG_ENABLE(ebpf, whether to enable ebpf support,
+dnl Make sure that the comment is aligned:
+[  --enable-ebpf           Enable ebpf support])
+
+if test "$PHP_EBPF" != "no"; then
+  dnl Write more examples of tests here...
+
+  dnl # --with-ebpf -> check with-path
+  dnl SEARCH_PATH="/usr/local /usr"     # you might want to change this
+  dnl SEARCH_FOR="/include/ebpf.h"  # you most likely want to change this
+  dnl if test -r $PHP_EBPF/$SEARCH_FOR; then # path given as parameter
+  dnl   EBPF_DIR=$PHP_EBPF
+  dnl else # search default path list
+  dnl   AC_MSG_CHECKING([for ebpf files in default path])
+  dnl   for i in $SEARCH_PATH ; do
+  dnl     if test -r $i/$SEARCH_FOR; then
+  dnl       EBPF_DIR=$i
+  dnl       AC_MSG_RESULT(found in $i)
+  dnl     fi
+  dnl   done
+  dnl fi
+  dnl
+  dnl if test -z "$EBPF_DIR"; then
+  dnl   AC_MSG_RESULT([not found])
+  dnl   AC_MSG_ERROR([Please reinstall the ebpf distribution])
+  dnl fi
+
+  dnl # --with-ebpf -> add include path
+  dnl PHP_ADD_INCLUDE($EBPF_DIR/include)
+
+
+  AC_ARG_WITH([ebpf],
+    [AS_HELP_STRING([--with-bcc=DIR], [Specify the path to bcc headers and libraries])],
+    [LIB_BCC="$withval"],
+    [LIB_BCC="./deps/bcc"]
+  )
+  PHP_ADD_INCLUDE($LIB_BCC/src/cc)
+
+  AC_ARG_WITH([ebpf],
+      [AS_HELP_STRING([--with-llvm=DIR], [Specify the path to llvm headers and libraries])],
+      [LIB_LLVM="$withval"],
+      [LIB_LLVM="/usr/lib/llvm-14"]
+  )
+  PHP_ADD_INCLUDE($LIB_LLVM/include)
+
+  dnl AC_ARG_WITH([ebpf],
+  dnl      [AS_HELP_STRING([--with-kernel=DIR], [Specify the path to llvm headers and libraries])],
+  dnl      [LIB_KERNEL="$withval"],
+  dnl      [LIB_KERNEL="/lib/modules/5.15.0-130-generic"]
+  dnl )
+  AC_DEFINE_UNQUOTED([KERNEL_MODULES_DIR], ["$LIB_KERNEL"], [Path to kernel modules])
+
+  PHP_ADD_INCLUDE(./api)
+
+  dnl PHP_ADD_LIBRARY_WITH_PATH(bcc, /usr/lib/x86_64-linux-gnu, EBPF_SHARED_LIBADD)
+
+  PHP_CHECK_LIBRARY(bcc, bpf_prog_load,
+  [
+    PHP_ADD_LIBRARY_WITH_PATH(bcc, /usr/lib/x86_64-linux-gnu, EBPF_SHARED_LIBADD)
+    AC_DEFINE(HAVE_EBPFLIB, 1, [Define if libbcc is available])
+  ],[
+    AC_MSG_ERROR([libbcc not found or missing required symbols])
+  ],[
+    -L/usr/lib/x86_64-linux-gnu
+  ])
+
+  dnl # --with-ebpf -> check for lib and symbol presence
+  dnl LIBNAME=ebpf # you may want to change this
+  dnl LIBSYMBOL=ebpf # you most likely want to change this
+
+  dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,
+  dnl [
+  dnl   PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $EBPF_DIR/$PHP_LIBDIR, EBPF_SHARED_LIBADD)
+
+  dnl   AC_DEFINE(HAVE_EBPFLIB,1,[ ])
+  dnl ],[
+  dnl   AC_MSG_ERROR([wrong ebpf lib version or lib not found])
+  dnl ],[
+  dnl   -L$EBPF_DIR/$PHP_LIBDIR -lm
+  dnl ])
+  dnl
+
+  PHP_SUBST(EBPF_SHARED_LIBADD)
+
+  PHP_REQUIRE_CXX()
+  PHP_ADD_LIBRARY(stdc++, 1, EBPF_SHARED_LIBADD)
+  CXXFLAGS="$CXXFLAGS -Wall -Wno-unused-function -Wno-deprecated -Wno-deprecated-declarations -std=c++11"
+
+  dnl PHP_NEW_EXTENSION(ebpf, ebpf.cpp, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
+
+
+  source_file="ebpf.cpp \
+        api/BPF.cc \
+        api/BPFTable.cc"
+
+  PHP_NEW_EXTENSION(ebpf, $source_file, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
+
+
+fi

+ 13 - 0
config.w32

@@ -0,0 +1,13 @@
+// $Id$
+// vim:ft=javascript
+
+// If your extension references something external, use ARG_WITH
+// ARG_WITH("ebpf", "for ebpf support", "no");
+
+// Otherwise, use ARG_ENABLE
+ARG_ENABLE("ebpf", "enable ebpf support", "no");
+
+if (PHP_EBPF != "no") {
+	EXTENSION("ebpf", "ebpf.c", PHP_EXTNAME_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
+}
+

+ 1 - 0
deps/bcc

@@ -0,0 +1 @@
+Subproject commit 6e7683262d53677e680ca4ae77d12f768155eff7

+ 1683 - 0
ebpf.cpp

@@ -0,0 +1,1683 @@
+/*
+  +----------------------------------------------------------------------+
+  | PHP Version 7                                                        |
+  +----------------------------------------------------------------------+
+  | Copyright (c) 1997-2018 The PHP Group                                |
+  +----------------------------------------------------------------------+
+  | This source file is subject to version 3.01 of the PHP license,      |
+  | that is bundled with this package in the file LICENSE, and is        |
+  | available through the world-wide-web at the following url:           |
+  | http://www.php.net/license/3_01.txt                                  |
+  | If you did not receive a copy of the PHP license and are unable to   |
+  | obtain it through the world-wide-web, please send a note to          |
+  | [email protected] so we can mail you a copy immediately.               |
+  +----------------------------------------------------------------------+
+  | Author:                                                              |
+  +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+
+extern "C" {
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+}
+
+#include "BPF.h"
+#include "php_ebpf.h"
+#include "bcc_common.h"
+#include <string>
+#include <fstream>
+#include <sstream>
+#include <regex>
+
+/* Handlers */
+zend_object_handlers bpf_object_handlers;
+zend_object_handlers table_object_handlers;
+
+/* Class entries */
+zend_class_entry *bpf_ce;
+zend_class_entry *perf_event_array_table_ce;
+zend_class_entry *hash_table_ce;
+zend_class_entry *array_table_ce;
+zend_class_entry *prog_array_table_ce;
+zend_class_entry *per_cpu_hash_table_ce;
+zend_class_entry *per_cpu_array_table_ce;
+zend_class_entry *lpm_trie_table_ce;
+zend_class_entry *stack_trace_table_ce;
+zend_class_entry *lru_hash_table_ce;
+zend_class_entry *lru_per_cpu_hash_table_ce;
+zend_class_entry *cgroup_array_table_ce;
+zend_class_entry *dev_map_table_ce;
+zend_class_entry *cpu_map_table_ce;
+zend_class_entry *xsk_map_table_ce;
+zend_class_entry *map_in_map_array_table_ce;
+zend_class_entry *map_in_map_hash_table_ce;
+zend_class_entry *queue_stack_table_ce;
+zend_class_entry *ring_buf_table_ce;
+zend_class_entry *bpf_prog_func_ce;
+
+/* Objects */
+typedef struct _bpf_object {
+	EbpfExtension *ebpf_cpp_cls;
+	zend_object std;
+} bpf_object;
+
+typedef struct _sub_object {
+	ebpf::BPF *bpf;
+	zend_object std;
+} sub_object;
+
+std::string cb_fn;
+
+void callbackfn(void *cookie, void *data, int data_size) {
+	zval params[3];
+	zval retval;
+
+	ZVAL_LONG(&params[0], 0);
+	ZVAL_STRINGL(&params[1], (const char *) data, data_size);
+	ZVAL_LONG(&params[2], data_size);
+	zval function_name;
+	ZVAL_STRING(&function_name, cb_fn.c_str());
+	if (call_user_function(EG(function_table), nullptr, &function_name, &retval, 3, params) == SUCCESS) {
+		zval_ptr_dtor(&retval);
+	} else {
+		php_error_docref(NULL, E_WARNING, "Failed to call callback function '%s'", cb_fn.c_str());
+	}
+
+
+	zval_ptr_dtor(&params[0]);
+	zval_ptr_dtor(&params[1]);
+	zval_ptr_dtor(&params[2]);
+	zval_ptr_dtor(&function_name);
+}
+
+static inline bpf_object *bpf_fetch_object(zend_object *obj) {
+	return (bpf_object *) ((char *) (obj) - XtOffsetOf(bpf_object, std));
+}
+
+static inline sub_object *table_fetch_object(zend_object *obj) {
+	return (sub_object *) ((char *) (obj) - XtOffsetOf(sub_object, std));
+}
+
+void EbpfExtension::_trace_autoload() {
+	size_t num_funcs = bpf.get_num_functions();
+	for (size_t i = 0; i < num_funcs; i++) {
+		const char *func_name = bpf.get_function_name(i);
+		std::string fn_name(func_name);
+		if (fn_name.rfind("kprobe__", 0) == 0) {
+			std::string kernel_func = fix_syscall_fnname(fn_name.substr(8));
+			bpf.attach_kprobe(kernel_func, fn_name);
+		} else if (fn_name.rfind("kretprobe__", 0) == 0) {
+			std::string kernel_func = fix_syscall_fnname(fn_name.substr(11));
+			bpf.attach_kprobe(kernel_func, fn_name, 0, BPF_PROBE_RETURN);
+		} else if (fn_name.rfind("tracepoint__", 0) == 0) {
+			std::string tp_name = fn_name.substr(12);
+			size_t sep = tp_name.find("__");
+			if (sep != std::string::npos) {
+				tp_name.replace(sep, 2, ":");
+			}
+			bpf.attach_tracepoint(tp_name, fn_name);
+		} else if (fn_name.rfind("raw_tracepoint__", 0) == 0) {
+			std::string tp_name = fn_name.substr(16);
+			bpf.attach_raw_tracepoint(tp_name, fn_name);
+		} else if (fn_name.rfind("kfunc__", 0) == 0) {
+			fn_name = add_prefix("kfunc__", fn_name);
+			attach_kfunc(fn_name);
+		} else if (fn_name.rfind("kretfunc__", 0) == 0) {
+			fn_name = add_prefix("kretfunc__", fn_name);
+			this->attach_kfunc(fn_name);
+		} else if (fn_name.rfind("lsm__", 0) == 0) {
+			fn_name = add_prefix("lsm__", fn_name);
+			this->attach_lsm(fn_name);
+		}
+	}
+}
+
+std::string EbpfExtension::add_prefix(const std::string &prefix, const std::string &name) {
+	if (name.rfind(prefix, 0) != 0) {
+		return prefix + name;
+	}
+	return name;
+}
+
+std::string EbpfExtension::fix_syscall_fnname(const std::string &name) {
+	for (const auto &prefix: syscall_prefixes) {
+		if (name.rfind(prefix, 0) == 0) {
+			return bpf.get_syscall_fnname(name.substr(prefix.length()));
+		}
+	}
+	return name;
+}
+
+zval EbpfExtension::get_table_cls(const char *table_name, int from_attr) {
+	zval retval;
+	ZVAL_NULL(&retval);
+
+	int ttype = bpf_table_type(this->mod, table_name);
+
+	switch (ttype) {
+		case BPF_MAP_TYPE_HASH: {
+			if (!hash_table_ce) {
+				zend_throw_error(NULL, "HashTable class not found");
+				return retval;
+			}
+			object_init_ex(&retval, hash_table_ce);
+			zend_update_property_string(hash_table_ce, &retval, "name", sizeof("name") - 1, table_name);
+			sub_object *table_obj = table_fetch_object(Z_OBJ(retval));
+			table_obj->bpf = &this->bpf;
+			Z_ADDREF(retval);
+			break;
+		}
+		case BPF_MAP_TYPE_ARRAY: {
+			if (!array_table_ce) {
+				zend_throw_error(NULL, "ArrayTable class not found");
+				return retval;
+			}
+			object_init_ex(&retval, array_table_ce);
+			zend_update_property_string(array_table_ce, &retval, "name", sizeof("name") - 1, table_name);
+			sub_object *table_obj = table_fetch_object(Z_OBJ(retval));
+			table_obj->bpf = &this->bpf;
+			Z_ADDREF(retval);
+			break;
+		}
+		case BPF_MAP_TYPE_PROG_ARRAY: {
+			if (!prog_array_table_ce) {
+				zend_throw_error(NULL, "ProgArrayTable class not found");
+				return retval;
+			}
+			object_init_ex(&retval, prog_array_table_ce);
+			zend_update_property_string(prog_array_table_ce, &retval, "name", sizeof("name") - 1, table_name);
+			sub_object *table_obj = table_fetch_object(Z_OBJ(retval));
+			table_obj->bpf = &this->bpf;
+			Z_ADDREF(retval);
+			break;
+		}
+		case BPF_MAP_TYPE_PERF_EVENT_ARRAY: {
+			if (Z_TYPE(_class_perf_event_obj) != IS_UNDEF) {
+				ZVAL_COPY(&retval, &_class_perf_event_obj);
+				return retval;
+			}
+			if (!perf_event_array_table_ce) {
+				zend_throw_error(NULL, "PerfEventArrayTable class not found");
+				return retval;
+			}
+			object_init_ex(&retval, perf_event_array_table_ce);
+			zend_update_property_string(perf_event_array_table_ce, &retval, "name", sizeof("name") - 1, table_name);
+			sub_object *table_obj = table_fetch_object(Z_OBJ(retval));
+			table_obj->bpf = &this->bpf;
+			Z_ADDREF(retval);
+			ZVAL_COPY(&_class_perf_event_obj, &retval);
+			return retval;
+		}
+		case BPF_MAP_TYPE_PERCPU_HASH: {
+			if (!per_cpu_hash_table_ce) {
+				zend_throw_error(NULL, "PerCpuHashTable class not found");
+				return retval;
+			}
+			object_init_ex(&retval, per_cpu_hash_table_ce);
+			zend_update_property_string(per_cpu_hash_table_ce, &retval, "name", sizeof("name") - 1, table_name);
+			sub_object *table_obj = table_fetch_object(Z_OBJ(retval));
+			table_obj->bpf = &this->bpf;
+			Z_ADDREF(retval);
+			break;
+		}
+		case BPF_MAP_TYPE_PERCPU_ARRAY: {
+			if (!per_cpu_array_table_ce) {
+				zend_throw_error(NULL, "PerCpuArrayTable class not found");
+				return retval;
+			}
+			object_init_ex(&retval, per_cpu_array_table_ce);
+			zend_update_property_string(per_cpu_array_table_ce, &retval, "name", sizeof("name") - 1, table_name);
+			sub_object *table_obj = table_fetch_object(Z_OBJ(retval));
+			table_obj->bpf = &this->bpf;
+			Z_ADDREF(retval);
+			break;
+		}
+		case BPF_MAP_TYPE_LPM_TRIE: {
+			if (!lpm_trie_table_ce) {
+				zend_throw_error(NULL, "LpmTrieTable class not found");
+				return retval;
+			}
+			object_init_ex(&retval, lpm_trie_table_ce);
+			zend_update_property_string(lpm_trie_table_ce, &retval, "name", sizeof("name") - 1, table_name);
+			sub_object *table_obj = table_fetch_object(Z_OBJ(retval));
+			table_obj->bpf = &this->bpf;
+			Z_ADDREF(retval);
+			break;
+		}
+		case BPF_MAP_TYPE_STACK_TRACE: {
+			if (!stack_trace_table_ce) {
+				zend_throw_error(NULL, "StackTraceTable class not found");
+				return retval;
+			}
+			object_init_ex(&retval, stack_trace_table_ce);
+			zend_update_property_string(stack_trace_table_ce, &retval, "name", sizeof("name") - 1, table_name);
+			sub_object *table_obj = table_fetch_object(Z_OBJ(retval));
+			table_obj->bpf = &this->bpf;
+			Z_ADDREF(retval);
+			break;
+		}
+		case BPF_MAP_TYPE_LRU_HASH: {
+			if (!lru_hash_table_ce) {
+				zend_throw_error(NULL, "LruHashTable class not found");
+				return retval;
+			}
+			object_init_ex(&retval, lru_hash_table_ce);
+			zend_update_property_string(lru_hash_table_ce, &retval, "name", sizeof("name") - 1, table_name);
+			sub_object *table_obj = table_fetch_object(Z_OBJ(retval));
+			table_obj->bpf = &this->bpf;
+			Z_ADDREF(retval);
+			break;
+		}
+		case BPF_MAP_TYPE_LRU_PERCPU_HASH: {
+			if (!lru_per_cpu_hash_table_ce) {
+				zend_throw_error(NULL, "LruPerCpuHashTable class not found");
+				return retval;
+			}
+			object_init_ex(&retval, lru_per_cpu_hash_table_ce);
+			zend_update_property_string(lru_per_cpu_hash_table_ce, &retval, "name", sizeof("name") - 1, table_name);
+			sub_object *table_obj = table_fetch_object(Z_OBJ(retval));
+			table_obj->bpf = &this->bpf;
+			Z_ADDREF(retval);
+			break;
+		}
+		case BPF_MAP_TYPE_CGROUP_ARRAY: {
+			if (!cgroup_array_table_ce) {
+				zend_throw_error(NULL, "CgroupArrayTable class not found");
+				return retval;
+			}
+			object_init_ex(&retval, cgroup_array_table_ce);
+			zend_update_property_string(cgroup_array_table_ce, &retval, "name", sizeof("name") - 1, table_name);
+			sub_object *table_obj = table_fetch_object(Z_OBJ(retval));
+			table_obj->bpf = &this->bpf;
+			Z_ADDREF(retval);
+			break;
+		}
+		case BPF_MAP_TYPE_DEVMAP: {
+			if (!dev_map_table_ce) {
+				zend_throw_error(NULL, "DevMapTable class not found");
+				return retval;
+			}
+			object_init_ex(&retval, dev_map_table_ce);
+			zend_update_property_string(dev_map_table_ce, &retval, "name", sizeof("name") - 1, table_name);
+			sub_object *table_obj = table_fetch_object(Z_OBJ(retval));
+			table_obj->bpf = &this->bpf;
+			Z_ADDREF(retval);
+			break;
+		}
+		case BPF_MAP_TYPE_CPUMAP: {
+			if (!cpu_map_table_ce) {
+				zend_throw_error(NULL, "CpuMapTable class not found");
+				return retval;
+			}
+			object_init_ex(&retval, cpu_map_table_ce);
+			zend_update_property_string(cpu_map_table_ce, &retval, "name", sizeof("name") - 1, table_name);
+			sub_object *table_obj = table_fetch_object(Z_OBJ(retval));
+			table_obj->bpf = &this->bpf;
+			Z_ADDREF(retval);
+			break;
+		}
+		case BPF_MAP_TYPE_XSKMAP: {
+			if (!xsk_map_table_ce) {
+				zend_throw_error(NULL, "XskMapTable class not found");
+				return retval;
+			}
+			object_init_ex(&retval, xsk_map_table_ce);
+			zend_update_property_string(xsk_map_table_ce, &retval, "name", sizeof("name") - 1, table_name);
+			sub_object *table_obj = table_fetch_object(Z_OBJ(retval));
+			table_obj->bpf = &this->bpf;
+			Z_ADDREF(retval);
+			break;
+		}
+		case BPF_MAP_TYPE_ARRAY_OF_MAPS: {
+			if (!map_in_map_array_table_ce) {
+				zend_throw_error(NULL, "MapInMapArrayTable class not found");
+				return retval;
+			}
+			object_init_ex(&retval, map_in_map_array_table_ce);
+			zend_update_property_string(map_in_map_array_table_ce, &retval, "name", sizeof("name") - 1, table_name);
+			sub_object *table_obj = table_fetch_object(Z_OBJ(retval));
+			table_obj->bpf = &this->bpf;
+			Z_ADDREF(retval);
+			break;
+		}
+		case BPF_MAP_TYPE_HASH_OF_MAPS: {
+			if (!map_in_map_hash_table_ce) {
+				zend_throw_error(NULL, "MapInMapHashTable class not found");
+				return retval;
+			}
+			object_init_ex(&retval, map_in_map_hash_table_ce);
+			zend_update_property_string(map_in_map_hash_table_ce, &retval, "name", sizeof("name") - 1, table_name);
+			sub_object *table_obj = table_fetch_object(Z_OBJ(retval));
+			table_obj->bpf = &this->bpf;
+			Z_ADDREF(retval);
+			break;
+		}
+		case BPF_MAP_TYPE_QUEUE:
+		case BPF_MAP_TYPE_STACK: {
+			if (!queue_stack_table_ce) {
+				zend_throw_error(NULL, "QueueStackTable class not found");
+				return retval;
+			}
+			object_init_ex(&retval, queue_stack_table_ce);
+			zend_update_property_string(queue_stack_table_ce, &retval, "name", sizeof("name") - 1, table_name);
+			sub_object *table_obj = table_fetch_object(Z_OBJ(retval));
+			table_obj->bpf = &this->bpf;
+			Z_ADDREF(retval);
+			break;
+		}
+		case BPF_MAP_TYPE_RINGBUF: {
+			if (!ring_buf_table_ce) {
+				zend_throw_error(NULL, "RingBufTable class not found");
+				return retval;
+			}
+			object_init_ex(&retval, ring_buf_table_ce);
+			zend_update_property_string(ring_buf_table_ce, &retval, "name", sizeof("name") - 1, table_name);
+			sub_object *table_obj = table_fetch_object(Z_OBJ(retval));
+			table_obj->bpf = &this->bpf;
+			Z_ADDREF(retval);
+			break;
+		}
+		default:
+			if (from_attr) {
+				ZVAL_LONG(&retval, ttype);
+			}
+			zend_throw_error(NULL, "Unknown table type %d", ttype);
+			return retval;
+	}
+
+	return retval;
+}
+
+std::unordered_set<std::string> EbpfExtension::get_kprobe_functions(const std::string &event_re) {
+	std::unordered_set<std::string> blacklist;
+	std::unordered_set<std::string> avail_filter;
+	std::unordered_set<std::string> fns;
+
+	std::string blacklist_file = std::string(DEBUGFS) + "/kprobes/blacklist";
+	std::ifstream blacklist_f(blacklist_file);
+	if (blacklist_f.is_open()) {
+		std::string line;
+		while (std::getline(blacklist_f, line)) {
+			std::istringstream iss(line);
+			std::string addr, func_name;
+			if (iss >> addr >> func_name) {
+				blacklist.insert(func_name);
+			}
+		}
+		blacklist_f.close();
+	}
+
+	std::string avail_filter_file = std::string(DEBUGFS) + "/tracing/available_filter_functions";
+	std::ifstream avail_filter_f(avail_filter_file);
+	if (avail_filter_f.is_open()) {
+		std::string line;
+		while (std::getline(avail_filter_f, line)) {
+			std::istringstream iss(line);
+			std::string func_name;
+			if (iss >> func_name) {
+				avail_filter.insert(func_name);
+			}
+		}
+		avail_filter_f.close();
+	}
+
+	std::ifstream kallsyms_f("/proc/kallsyms");
+	if (!kallsyms_f.is_open()) {
+		std::cerr << "Failed to open /proc/kallsyms\n";
+		return fns;
+	}
+
+	std::string line;
+	bool in_init_section = false;
+	bool in_irq_section = false;
+	std::regex cold_regex(".*\\.cold(\\.\\d+)?$");
+
+	while (std::getline(kallsyms_f, line)) {
+		std::istringstream iss(line);
+		std::string addr, type, func_name;
+		if (!(iss >> addr >> type >> func_name)) {
+			continue;
+		}
+
+		if (!in_init_section) {
+			if (func_name == "__init_begin") {
+				in_init_section = true;
+				continue;
+			}
+		} else if (func_name == "__init_end") {
+			in_init_section = false;
+			continue;
+		}
+
+		if (!in_irq_section) {
+			if (func_name == "__irqentry_text_start") {
+				in_irq_section = true;
+				continue;
+			} else if (func_name == "__irqentry_text_end") {
+				in_irq_section = false;
+				continue;
+			}
+		} else if (func_name == "__irqentry_text_end") {
+			in_irq_section = false;
+			continue;
+		}
+
+		if (func_name.rfind("_kbl_addr_", 0) == 0) {
+			continue;
+		}
+		if (func_name.rfind("__perf", 0) == 0 || func_name.rfind("perf_", 0) == 0) {
+			continue;
+		}
+		if (func_name.rfind("__SCT__", 0) == 0) {
+			continue;
+		}
+		if (std::regex_match(func_name, cold_regex)) {
+			continue;
+		}
+
+		if ((type == "t" || type == "T" || type == "w" || type == "W") &&
+		    func_name == event_re &&
+		    blacklist.find(func_name) == blacklist.end() &&
+		    avail_filter.find(func_name) != avail_filter.end()) {
+			fns.insert(func_name);
+		}
+	}
+
+	return fns;
+}
+
+ebpf::StatusTuple EbpfExtension::attach_kfunc(const std::string &kfn) {
+	int probe_fd;
+	auto fn = bpf.load_func(kfn, BPF_PROG_TYPE_TRACING, probe_fd);
+
+	int res_fd = bpf_attach_kfunc(probe_fd);
+	if (res_fd < 0) {
+		TRY2(bpf.unload_func(kfn));
+		return ebpf::StatusTuple(-1, "Unable to attach kfunc using %s",
+		                         kfn.c_str());
+	}
+	return ebpf::StatusTuple::OK();
+}
+
+ebpf::StatusTuple EbpfExtension::attach_lsm(const std::string &lsm) {
+	int probe_fd;
+	auto fn = bpf.load_func(lsm, BPF_PROG_TYPE_LSM, probe_fd);
+
+	int res_fd = bpf_attach_lsm(probe_fd);
+	if (res_fd < 0) {
+		TRY2(bpf.unload_func(lsm));
+		return ebpf::StatusTuple(-1, "Unable to attach lsm using %s",
+		                         lsm.c_str());
+	}
+	return ebpf::StatusTuple::OK();
+}
+
+
+zend_object *bpf_create_object(zend_class_entry *ce) {
+	bpf_object *intern = (bpf_object *) ecalloc(1, sizeof(bpf_object) + zend_object_properties_size(ce));
+	intern->ebpf_cpp_cls = new EbpfExtension();
+	zend_object_std_init(&intern->std, ce);
+	object_properties_init(&intern->std, ce);
+	intern->std.handlers = &bpf_object_handlers;
+	return &intern->std;
+}
+
+void bpf_free_object(zend_object *object) {
+	bpf_object *intern = bpf_fetch_object(object);
+	zend_object_std_dtor(&intern->std);
+}
+
+zend_object *table_create_object(zend_class_entry *ce TSRMLS_DC) {
+	sub_object *intern = (sub_object *) ecalloc(1, sizeof(sub_object) + zend_object_properties_size(ce));
+	zend_object_std_init(&intern->std, ce);
+	object_properties_init(&intern->std, ce);
+	intern->std.handlers = &table_object_handlers;
+	return &intern->std;
+}
+
+void table_free_object(zend_object *object) {
+	sub_object *intern = table_fetch_object(object);
+	zend_object_std_dtor(&intern->std);
+}
+
+/* {{{ PHP_INI
+ */
+/* Remove comments and fill if you need to have entries in php.ini
+PHP_INI_BEGIN()
+    STD_PHP_INI_ENTRY("ebpf.global_value",      "42", PHP_INI_ALL, OnUpdateLong, global_value, zend_ebpf_globals, ebpf_globals)
+    STD_PHP_INI_ENTRY("ebpf.global_string", "foobar", PHP_INI_ALL, OnUpdateString, global_string, zend_ebpf_globals, ebpf_globals)
+PHP_INI_END()
+*/
+/* }}} */
+
+/* Remove the following function when you have successfully modified config.m4
+   so that your module can be compiled into PHP, it exists only for testing
+   purposes. */
+
+
+
+/* Every user-visible function in PHP should document itself in the source */
+/* {{{ proto string confirm_ebpf_compiled(string arg)
+   Return a string to confirm that the module is compiled in */
+
+PHP_METHOD (Bpf, __construct) {
+	zval *opts;
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "a", &opts) == FAILURE) {
+		zend_throw_error(NULL, "Expected options array");
+		return;
+	}
+
+	if (Z_OBJCE_P(getThis()) != bpf_ce) {
+		zend_throw_error(NULL, "Invalid object type");
+		return;
+	}
+
+	bpf_object *obj = bpf_fetch_object(Z_OBJ_P(getThis()));
+	zval *text = zend_hash_str_find(Z_ARRVAL_P(opts), "text", strlen("text"));
+
+	if (text && Z_TYPE_P(text) == IS_STRING) {
+		std::string source(Z_STRVAL_P(text), Z_STRLEN_P(text));
+		if (!obj->ebpf_cpp_cls) {
+			zend_throw_error(NULL, "Invalid internal C++ object");
+			RETURN_NULL();
+		}
+
+		auto res = obj->ebpf_cpp_cls->init(source);
+		if (!res.ok()) {
+			zend_throw_error(NULL, "BPF init failed: %s", res.msg().c_str());
+			RETURN_FALSE;
+		}
+		obj->ebpf_cpp_cls->_trace_autoload();
+	}
+
+	RETURN_TRUE;
+}
+
+PHP_METHOD (Bpf, __get) {
+	char *name;
+	size_t name_len;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &name_len) == FAILURE) {
+		RETURN_NULL();
+	}
+
+	bpf_object *obj = bpf_fetch_object(Z_OBJ_P(getThis()));
+	if (!obj || !obj->ebpf_cpp_cls) {
+		zend_throw_error(NULL, "Invalid object state");
+		RETURN_NULL();
+	}
+
+	int from_attr = 1;
+	zval table = obj->ebpf_cpp_cls->get_table_cls(name, from_attr);
+
+	RETURN_ZVAL(&table, 1, 0);
+}
+
+PHP_METHOD (Bpf, get_kprobe_functions) {
+	char *fn;
+	size_t fn_len;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &fn, &fn_len) == FAILURE) {
+		RETURN_NULL();
+	}
+
+	bpf_object *obj = bpf_fetch_object(Z_OBJ_P(getThis()));
+	if (!obj || !obj->ebpf_cpp_cls) {
+		zend_throw_error(NULL, "Invalid object state");
+		RETURN_NULL();
+	}
+
+	auto res = obj->ebpf_cpp_cls->get_kprobe_functions(std::string(fn, fn_len));
+
+	array_init(return_value);
+	for (const auto &item: res) {
+		add_next_index_string(return_value, item.c_str());
+	}
+}
+
+PHP_METHOD (Bpf, attach_kprobe) {
+	char *kernel_func, *probe_func;
+	size_t kernel_func_len, probe_func_len;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &kernel_func, &kernel_func_len,
+	                          &probe_func, &probe_func_len) == FAILURE) {
+		RETURN_NULL();
+	}
+
+	bpf_object *obj = bpf_fetch_object(Z_OBJ_P(getThis()));
+	if (!obj || !obj->ebpf_cpp_cls) {
+		zend_throw_error(NULL, "Invalid object state");
+		RETURN_NULL();
+	}
+
+	auto attach_res = obj->ebpf_cpp_cls->bpf.attach_kprobe(
+			std::string(kernel_func, kernel_func_len),
+			std::string(probe_func, probe_func_len)
+	);
+
+	if (!attach_res.ok()) {
+		zend_throw_error(NULL, "attach error: %s", attach_res.msg().c_str());
+		RETURN_NULL();
+	}
+
+	RETURN_TRUE;
+}
+
+PHP_METHOD (Bpf, attach_tracepoint) {
+	char *tp_func, *probe_func;
+	size_t tp_func_len, probe_func_len;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &tp_func, &tp_func_len,
+	                          &probe_func, &probe_func_len) == FAILURE) {
+		RETURN_NULL();
+	}
+
+	bpf_object *obj = bpf_fetch_object(Z_OBJ_P(getThis()));
+	if (!obj || !obj->ebpf_cpp_cls) {
+		zend_throw_error(NULL, "Invalid object state");
+		RETURN_NULL();
+	}
+
+	auto attach_res = obj->ebpf_cpp_cls->bpf.attach_tracepoint(
+			std::string(tp_func, tp_func_len),
+			std::string(probe_func, probe_func_len)
+	);
+
+	if (!attach_res.ok()) {
+		zend_throw_error(NULL, "attach error: %s", attach_res.msg().c_str());
+		RETURN_NULL();
+	}
+
+	RETURN_TRUE;
+}
+
+PHP_METHOD (Bpf, attach_raw_tracepoint) {
+	char *tp_func, *probe_func;
+	size_t tp_func_len, probe_func_len;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &tp_func, &tp_func_len,
+	                          &probe_func, &probe_func_len) == FAILURE) {
+		RETURN_NULL();
+	}
+
+	bpf_object *obj = bpf_fetch_object(Z_OBJ_P(getThis()));
+	if (!obj || !obj->ebpf_cpp_cls) {
+		zend_throw_error(NULL, "Invalid object state");
+		RETURN_NULL();
+	}
+
+	auto attach_res = obj->ebpf_cpp_cls->bpf.attach_raw_tracepoint(
+			std::string(tp_func, tp_func_len),
+			std::string(probe_func, probe_func_len)
+	);
+
+	if (!attach_res.ok()) {
+		zend_throw_error(NULL, "attach error: %s", attach_res.msg().c_str());
+		RETURN_NULL();
+	}
+
+	RETURN_TRUE;
+}
+
+PHP_METHOD (Bpf, attach_kfunc) {
+	char *kfunc;
+	size_t kfunc_len;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &kfunc, &kfunc_len) == FAILURE) {
+		RETURN_NULL();
+	}
+
+	bpf_object *obj = bpf_fetch_object(Z_OBJ_P(getThis()));
+	if (!obj || !obj->ebpf_cpp_cls) {
+		zend_throw_error(NULL, "Invalid object state");
+		RETURN_NULL();
+	}
+
+	auto attach_res = obj->ebpf_cpp_cls->attach_kfunc(std::string(kfunc, kfunc_len));
+
+	if (!attach_res.ok()) {
+		zend_throw_error(NULL, "attach error: %s", attach_res.msg().c_str());
+		RETURN_NULL();
+	}
+
+	RETURN_TRUE;
+}
+
+PHP_METHOD (Bpf, attach_lsm) {
+	char *lsm;
+	size_t lsm_len;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &lsm, &lsm_len) == FAILURE) {
+		RETURN_NULL();
+	}
+
+	bpf_object *obj = bpf_fetch_object(Z_OBJ_P(getThis()));
+	if (!obj || !obj->ebpf_cpp_cls) {
+		zend_throw_error(NULL, "Invalid object state");
+		RETURN_NULL();
+	}
+
+	auto attach_res = obj->ebpf_cpp_cls->attach_lsm(std::string(lsm, lsm_len));
+
+	if (!attach_res.ok()) {
+		zend_throw_error(NULL, "attach error: %s", attach_res.msg().c_str());
+		RETURN_NULL();
+	}
+
+	RETURN_TRUE;
+}
+
+PHP_METHOD (Bpf, attach_uprobe) {
+	char *binary_path, *symbol, *probe_func;
+	size_t binary_path_len, symbol_len, probe_func_len;
+	zval *options = NULL;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|a",
+	                          &binary_path, &binary_path_len,
+	                          &symbol, &symbol_len,
+	                          &probe_func, &probe_func_len,
+	                          &options) == FAILURE) {
+		RETURN_NULL();
+	}
+
+	bpf_object *obj = bpf_fetch_object(Z_OBJ_P(getThis()));
+	if (!obj || !obj->ebpf_cpp_cls) {
+		zend_throw_error(NULL, "Invalid object state");
+		RETURN_NULL();
+	}
+
+	int64_t symbol_addr = 0, symbol_offset = 0, pid_param = 0;
+	uint32_t ref_ctr_offset = 0;
+	pid_t pid = -1;
+
+	if (options && Z_TYPE_P(options) == IS_ARRAY) {
+		zval *tmp;
+
+		if ((tmp = zend_hash_str_find(Z_ARRVAL_P(options), "symbol_addr", strlen("symbol_addr"))) != NULL) {
+			symbol_addr = zval_get_long(tmp);
+		}
+
+		if ((tmp = zend_hash_str_find(Z_ARRVAL_P(options), "symbol_offset", strlen("symbol_offset"))) != NULL) {
+			symbol_offset = zval_get_long(tmp);
+		}
+
+		if ((tmp = zend_hash_str_find(Z_ARRVAL_P(options), "ref_ctr_offset", strlen("ref_ctr_offset"))) != NULL) {
+			ref_ctr_offset = (uint32_t) zval_get_long(tmp);
+		}
+
+		if ((tmp = zend_hash_str_find(Z_ARRVAL_P(options), "pid", strlen("pid"))) != NULL) {
+			pid_param = zval_get_long(tmp);
+			if (pid_param > 0) {
+				pid = static_cast<pid_t>(pid_param);
+			}
+		}
+	}
+
+	auto attach_res = obj->ebpf_cpp_cls->bpf.attach_uprobe(
+			std::string(binary_path, binary_path_len),
+			std::string(symbol, symbol_len),
+			std::string(probe_func, probe_func_len),
+			symbol_addr,
+			BPF_PROBE_ENTRY,
+			pid,
+			symbol_offset,
+			ref_ctr_offset
+	);
+
+	if (!attach_res.ok()) {
+		zend_throw_error(NULL, "attach_uprobe error: %s", attach_res.msg().c_str());
+		RETURN_NULL();
+	}
+	RETURN_TRUE;
+}
+
+PHP_METHOD (Bpf, detach_kprobe) {
+	char *fn;
+	size_t fn_len;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &fn, &fn_len) == FAILURE) {
+		RETURN_NULL();
+	}
+
+	bpf_object *obj = bpf_fetch_object(Z_OBJ_P(getThis()));
+	if (!obj || !obj->ebpf_cpp_cls) {
+		zend_throw_error(NULL, "Invalid object state");
+		RETURN_NULL();
+	}
+
+	auto detach_res = obj->ebpf_cpp_cls->bpf.detach_kprobe(std::string(fn, fn_len));
+
+	if (!detach_res.ok()) {
+		zend_throw_error(NULL, "detach_kprobe error: %s", detach_res.msg().c_str());
+		RETURN_NULL();
+	}
+
+	RETURN_TRUE;
+}
+
+PHP_METHOD (Bpf, detach_uprobe) {
+	char *binary_path, *symbol;
+	size_t binary_path_len, symbol_len;
+	zval *options = NULL;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|a",
+	                          &binary_path, &binary_path_len,
+	                          &symbol, &symbol_len,
+	                          &options) == FAILURE) {
+		RETURN_NULL();
+	}
+
+	bpf_object *obj = bpf_fetch_object(Z_OBJ_P(getThis()));
+	if (!obj || !obj->ebpf_cpp_cls) {
+		zend_throw_error(NULL, "Invalid object state");
+		RETURN_NULL();
+	}
+
+	int64_t symbol_addr = 0, symbol_offset = 0, pid_param = 0;
+	pid_t pid = -1;
+
+	if (options && Z_TYPE_P(options) == IS_ARRAY) {
+		zval *tmp;
+
+		if ((tmp = zend_hash_str_find(Z_ARRVAL_P(options), "symbol_addr", strlen("symbol_addr"))) != NULL) {
+			symbol_addr = zval_get_long(tmp);
+		}
+
+		if ((tmp = zend_hash_str_find(Z_ARRVAL_P(options), "symbol_offset", strlen("symbol_offset"))) != NULL) {
+			symbol_offset = zval_get_long(tmp);
+		}
+
+		if ((tmp = zend_hash_str_find(Z_ARRVAL_P(options), "pid", strlen("pid"))) != NULL) {
+			pid_param = zval_get_long(tmp);
+			if (pid_param > 0) {
+				pid = static_cast<pid_t>(pid_param);
+			}
+		}
+	}
+
+	auto detach_res = obj->ebpf_cpp_cls->bpf.detach_uprobe(
+			std::string(binary_path, binary_path_len),
+			std::string(symbol, symbol_len),
+			symbol_addr,
+			BPF_PROBE_ENTRY,
+			pid,
+			symbol_offset
+	);
+
+	if (!detach_res.ok()) {
+		zend_throw_error(NULL, "detach_uprobe error: %s", detach_res.msg().c_str());
+		RETURN_NULL();
+	}
+	RETURN_TRUE;
+}
+
+PHP_METHOD (Bpf, trace_print) {
+	char *fmt = NULL;
+	size_t fmt_len = 0;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &fmt, &fmt_len) == FAILURE) {
+		RETURN_NULL();
+	}
+
+	bpf_object *obj = bpf_fetch_object(Z_OBJ_P(getThis()));
+	if (!obj || !obj->ebpf_cpp_cls) {
+		zend_throw_error(NULL, "Invalid object state");
+		RETURN_NULL();
+	}
+
+	std::ifstream pipe(TRACE_PIPE_PATH);
+	if (!pipe.is_open()) {
+		zend_throw_error(NULL, "Failed to open trace_pipe");
+		RETURN_NULL();
+	}
+
+	std::string line;
+	while (true) {
+		if (!std::getline(pipe, line)) {
+			continue;
+		}
+		if (line.empty() || line.rfind("CPU:", 0) == 0) {
+			continue;
+		}
+
+		std::string task = line.substr(0, 16);
+		task.erase(0, task.find_first_not_of(" "));
+
+		std::istringstream iss(line.substr(17));
+		std::string pid, cpu, flags, ts, msg;
+		char delim;
+
+		if (!(iss >> pid >> delim >> cpu >> delim >> flags >> ts)) {
+			continue;
+		}
+
+		size_t sym_end = iss.str().find(": ", iss.tellg());
+		if (sym_end != std::string::npos) {
+			msg = iss.str().substr(sym_end + 2);
+		}
+
+		std::vector<std::string> fields = {task, pid, cpu, flags, ts, msg};
+		if (fmt == NULL) {
+			php_printf("%s\n", line.c_str());
+		} else {
+			std::string output(fmt, fmt_len);
+			std::regex pattern(R"(\{(\d+)\})");
+			std::smatch match;
+			while (std::regex_search(output, match, pattern)) {
+				int index = std::stoi(match[1]);
+				std::string replacement = (index >= 0 && index < (int) fields.size()) ? fields[index] : "";
+				output.replace(match.position(0), match.length(0), replacement);
+			}
+			php_printf("%s\n", output.c_str());
+		}
+	}
+}
+
+PHP_METHOD (Bpf, trace_fields) {
+	std::ifstream traceFile(TRACE_PIPE_PATH);
+	if (!traceFile.is_open()) {
+		zend_throw_error(NULL, "Failed to open trace_pipe");
+		RETURN_NULL();
+	}
+
+	std::string line;
+	while (std::getline(traceFile, line)) {
+		if (line.empty() || line.rfind("CPU:", 0) == 0) {
+			continue;
+		}
+
+		std::string task = line.substr(0, 16);
+		task.erase(0, task.find_first_not_of(' '));
+
+		std::istringstream iss(line.substr(17));
+		std::string pid, cpu, flags, ts, msg;
+		char delim;
+
+		if (!(iss >> pid >> delim >> cpu >> delim >> flags >> ts)) {
+			continue;
+		}
+
+		size_t sym_end = iss.str().find(": ", iss.tellg());
+		if (sym_end != std::string::npos) {
+			msg = iss.str().substr(sym_end + 2);
+		}
+
+		array_init(return_value);
+		add_index_string(return_value, 0, task.c_str());
+		add_index_long(return_value, 1, std::stoi(pid));
+		add_index_long(return_value, 2, std::stoi(cpu.substr(1, cpu.size() - 2)));
+		add_index_string(return_value, 3, flags.c_str());
+		add_index_double(return_value, 4, std::stod(ts));
+		add_index_stringl(return_value, 5, msg.c_str(), msg.size());
+
+		return;
+	}
+
+	RETURN_NULL();
+}
+
+PHP_METHOD (Bpf, get_table) {
+	char *table_name;
+	size_t table_name_len;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &table_name, &table_name_len) == FAILURE) {
+		RETURN_NULL();
+	}
+
+	bpf_object *obj = bpf_fetch_object(Z_OBJ_P(getThis()));
+	if (!obj || !obj->ebpf_cpp_cls) {
+		zend_throw_error(NULL, "Invalid object state");
+		RETURN_NULL();
+	}
+
+	int from_fn = 0;
+	auto table = obj->ebpf_cpp_cls->get_table_cls(table_name, from_fn);
+
+	if (Z_TYPE(table) == IS_NULL) {
+		RETURN_NULL();
+	}
+
+	RETURN_ZVAL(&table, 1, 0);
+}
+
+PHP_METHOD (Bpf, perf_buffer_poll) {
+	bpf_object *obj = bpf_fetch_object(Z_OBJ_P(getThis()));
+	if (!obj || !obj->ebpf_cpp_cls) {
+		zend_throw_error(NULL, "Invalid object state");
+		RETURN_NULL();
+	}
+	zval name_rv;
+	zval *name_zv = zend_read_property(
+			perf_event_array_table_ce,
+			&obj->ebpf_cpp_cls->_class_perf_event_obj,
+			"name", strlen("name"),
+			1,
+			&name_rv
+	);
+	if (!name_zv || Z_TYPE_P(name_zv) != IS_STRING) {
+		zend_throw_error(NULL, "Invalid or missing name property");
+		RETURN_NULL();
+	}
+	int timeout_ms = -1;
+	int res = obj->ebpf_cpp_cls->bpf.poll_perf_buffer(std::string(Z_STRVAL_P(name_zv), Z_STRLEN_P(name_zv)),
+	                                                  timeout_ms);
+	if (res < 0) {
+		zend_throw_error(NULL, "perf buffer poll error.");
+	}
+}
+
+PHP_METHOD (Bpf, get_syscall_fnname) {
+	char *name;
+	size_t name_len;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &name_len) == FAILURE) {
+		RETURN_NULL();
+	}
+
+	bpf_object *obj = bpf_fetch_object(Z_OBJ_P(getThis()));
+	if (!obj || !obj->ebpf_cpp_cls) {
+		zend_throw_error(NULL, "Invalid object state");
+		RETURN_NULL();
+	}
+
+	std::string result = obj->ebpf_cpp_cls->bpf.get_syscall_fnname(std::string(name, name_len));
+
+	RETURN_STRING(result.c_str());
+}
+
+PHP_METHOD (Bpf, load_func) {
+	char *fn;
+	size_t fn_len;
+	zend_long prog_type;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "sl", &fn, &fn_len, &prog_type) == FAILURE) {
+		RETURN_NULL();
+	}
+
+	bpf_object *obj = bpf_fetch_object(Z_OBJ_P(getThis()));
+	if (!obj || !obj->ebpf_cpp_cls) {
+		zend_throw_error(NULL, "Invalid object state");
+		RETURN_NULL();
+	}
+
+	int probe_fd;
+	auto res = obj->ebpf_cpp_cls->bpf.load_func(
+			std::string(fn, fn_len),
+			static_cast<bpf_prog_type>(prog_type),
+			probe_fd
+	);
+
+	if (!res.ok()) {
+		zend_throw_error(NULL, "Failed to load function: %s", res.msg().c_str());
+		RETURN_NULL();
+	}
+
+	object_init_ex(return_value, bpf_prog_func_ce);
+
+	zval name_zv;
+	ZVAL_STRINGL(&name_zv, fn, fn_len);
+	zend_update_property(bpf_prog_func_ce, return_value, "name", sizeof("name") - 1, &name_zv);
+	zval_ptr_dtor(&name_zv);
+
+	zval fd_zv;
+	ZVAL_LONG(&fd_zv, probe_fd);
+	zend_update_property(bpf_prog_func_ce, return_value, "fd", sizeof("fd") - 1, &fd_zv);
+}
+
+PHP_METHOD (Bpf, attach_raw_socket) {
+	zval *prog_fn;
+	char *interface;
+	size_t interface_len;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "os", &prog_fn, &interface, &interface_len) == FAILURE) {
+		RETURN_NULL();
+	}
+
+	bpf_object *obj = bpf_fetch_object(Z_OBJ_P(getThis()));
+	if (!obj || !obj->ebpf_cpp_cls) {
+		zend_throw_error(NULL, "Invalid object state");
+		RETURN_NULL();
+	}
+
+	if (Z_TYPE_P(prog_fn) != IS_OBJECT) {
+		zend_throw_error(NULL, "First parameter must be a BPFProgFunction object");
+		RETURN_NULL();
+	}
+
+	zval rv;
+	zval *fd = zend_read_property(Z_OBJCE_P(prog_fn), prog_fn, "fd", strlen("fd"), 1, &rv);
+	if (!fd || Z_TYPE_P(fd) != IS_LONG) {
+		zend_throw_error(NULL, "Invalid BPFProgFunction object: missing or invalid fd property");
+		RETURN_NULL();
+	}
+
+	int sock = bpf_open_raw_sock(interface);
+	if (sock < 0) {
+		zend_throw_error(NULL, "Failed to open raw socket on interface: %s", interface);
+		RETURN_NULL();
+	}
+
+	int res = bpf_attach_socket(sock, Z_LVAL_P(fd));
+	if (res < 0) {
+		close(sock);
+		zend_throw_error(NULL, "Failed to attach BPF program to socket");
+		RETURN_NULL();
+	}
+
+	RETURN_TRUE;
+}
+
+PHP_METHOD (PerfEventArrayTable, open_perf_buffer) {
+	char *cb_fn_str = NULL;
+	size_t cb_fn_len = 0;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &cb_fn_str, &cb_fn_len) == FAILURE) {
+		RETURN_NULL();
+	}
+
+	zval name_rv;
+	zval *name_zv = zend_read_property(
+			Z_OBJCE_P(getThis()),
+			getThis(),
+			"name",
+			sizeof("name") - 1,
+			1,
+			&name_rv
+	);
+
+	if (!name_zv || Z_TYPE_P(name_zv) != IS_STRING) {
+		zend_throw_error(NULL, "Invalid or missing name property");
+		RETURN_NULL();
+	}
+
+	const char *name = Z_STRVAL_P(name_zv);
+	cb_fn = std::string(cb_fn_str, cb_fn_len);
+
+	sub_object *obj = table_fetch_object(Z_OBJ_P(getThis()));
+	if (!obj || !obj->bpf) {
+		zend_throw_error(NULL, "Invalid object state");
+		RETURN_NULL();
+	}
+
+	auto res = obj->bpf->open_perf_buffer(name, callbackfn);
+	if (!res.ok()) {
+		zend_throw_error(NULL, "open_perf_buffer error: %s", res.msg().c_str());
+		RETURN_NULL();
+	}
+
+	RETURN_TRUE;
+}
+
+PHP_METHOD (HashTable, values) {
+	zval name_rv;
+	zval *name_zv;
+
+	name_zv = zend_read_property(
+			Z_OBJCE_P(getThis()),
+			getThis(),
+			"name",
+			sizeof("name") - 1,
+			1,
+			&name_rv
+	);
+
+	if (!name_zv || Z_TYPE_P(name_zv) != IS_STRING) {
+		zend_throw_error(NULL, "Invalid or missing name property");
+		RETURN_NULL();
+	}
+
+	sub_object *obj = table_fetch_object(Z_OBJ_P(getThis()));
+	if (!obj || !obj->bpf) {
+		zend_throw_error(NULL, "Invalid object state");
+		RETURN_NULL();
+	}
+
+	std::vector<std::pair<std::vector<char>, std::vector<char>>> entries;
+	auto table = obj->bpf->get_table(Z_STRVAL_P(name_zv));
+	auto status = table.get_table_offline_ptr(entries);
+
+	if (status.code() != 0) {
+		zend_throw_error(NULL, "Failed to get table values: %s", status.msg().c_str());
+		RETURN_NULL();
+	}
+
+	array_init(return_value);
+
+	for (const auto &pair: entries) {
+		const auto &key_data = pair.first;
+		const auto &val_data = pair.second;
+		zval entry;
+		array_init(&entry);
+		add_assoc_stringl(&entry, "key", key_data.data(), key_data.size());
+		add_assoc_stringl(&entry, "value", val_data.data(), val_data.size());
+		add_next_index_zval(return_value, &entry);
+	}
+}
+
+PHP_METHOD (HashTable, clear) {
+	zval name_rv;
+	zval *name_zv;
+
+	name_zv = zend_read_property(
+			Z_OBJCE_P(getThis()),
+			getThis(),
+			"name",
+			sizeof("name") - 1,
+			1,
+			&name_rv
+	);
+
+	if (!name_zv || Z_TYPE_P(name_zv) != IS_STRING) {
+		zend_throw_error(NULL, "Invalid or missing name property");
+		RETURN_NULL();
+	}
+	sub_object *obj = table_fetch_object(Z_OBJ_P(getThis()));
+	if (!obj || !obj->bpf) {
+		zend_throw_error(NULL, "Invalid object state");
+		RETURN_NULL();
+	}
+
+	auto table = obj->bpf->get_table(Z_STRVAL_P(name_zv));
+	auto res = table.clear_table_non_atomic();
+
+	if (!res.ok()) {
+		zend_throw_error(NULL, "Failed to clear table: %s", res.msg().c_str());
+		RETURN_NULL();
+	}
+
+	RETURN_TRUE;
+}
+
+PHP_METHOD (ArrayTable, get_value) {
+	return;
+}
+
+PHP_METHOD (ArrayTable, print_log2_hist) {
+	return;
+}
+
+PHP_METHOD (ArrayTable, print_linear_hist) {
+	return;
+}
+
+PHP_METHOD (PerCpuArrayTable, sum_value) {
+	int64_t index;
+	zval name_rv;
+	zval *name_zv;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) == FAILURE) {
+		RETURN_NULL();
+	}
+
+	name_zv = zend_read_property(
+			Z_OBJCE_P(getThis()),
+			getThis(),
+			"name",
+			sizeof("name") - 1,
+			1,
+			&name_rv
+	);
+
+	if (!name_zv || Z_TYPE_P(name_zv) != IS_STRING) {
+		zend_throw_error(NULL, "Invalid or missing name property");
+		RETURN_NULL();
+	}
+
+	sub_object *obj = table_fetch_object(Z_OBJ_P(getThis()));
+
+	if (!obj || !obj->bpf) {
+		zend_throw_error(NULL, "Invalid object state");
+		RETURN_NULL();
+	}
+
+
+	std::vector<unsigned long> val;
+	try {
+		auto table = obj->bpf->get_percpu_array_table<uint64_t>(Z_STRVAL_P(name_zv));
+		auto res = table.get_value(index, val);
+		if (!res.ok()) {
+			zend_throw_error(NULL, "Get value error in %s", Z_STRVAL_P(name_zv));
+			RETURN_NULL();
+		}
+
+		unsigned long long sum = 0;
+		for (const auto &v: val) {
+			sum += v;
+		}
+
+		RETURN_LONG(sum);
+	} catch (const std::exception &e) {
+		zend_throw_error(NULL, "Exception: %s", e.what());
+		RETURN_NULL();
+	}
+}
+
+PHP_METHOD (StackTraceTable, values) {
+	zend_long stack_id;
+	zend_long pid = -1;
+	zval name_rv;
+	zval *name_zv;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|l", &stack_id, &pid) == FAILURE) {
+		RETURN_NULL();
+	}
+
+	name_zv = zend_read_property(
+			Z_OBJCE_P(getThis()),
+			getThis(),
+			"name",
+			sizeof("name") - 1,
+			1,
+			&name_rv
+	);
+
+	if (!name_zv || Z_TYPE_P(name_zv) != IS_STRING) {
+		zend_throw_error(NULL, "Invalid or missing name property");
+		RETURN_NULL();
+	}
+
+	sub_object *obj = table_fetch_object(Z_OBJ_P(getThis()));
+	if (!obj || !obj->bpf) {
+		zend_throw_error(NULL, "Invalid object state");
+		RETURN_NULL();
+	}
+
+	auto table = obj->bpf->get_stack_table(Z_STRVAL_P(name_zv));
+	auto symbols = table.get_stack_symbol((int) stack_id, (int) pid);
+
+	array_init(return_value);
+
+	for (const auto &str: symbols) {
+		add_next_index_string(return_value, str.c_str());
+	}
+}
+
+
+/* }}} */
+/* The previous line is meant for vim and emacs, so it can correctly fold and
+   unfold functions in source code. See the corresponding marks just before
+   function definition, where the functions purpose is also documented. Please
+   follow this convention for the convenience of others editing your code.
+*/
+
+
+/* {{{ php_ebpf_init_globals
+ */
+/* Uncomment this function if you have INI entries
+static void php_ebpf_init_globals(zend_ebpf_globals *ebpf_globals)
+{
+	ebpf_globals->global_value = 0;
+	ebpf_globals->global_string = NULL;
+}
+*/
+/* }}} */
+
+/* {{{ bpf_class_methods */
+ZEND_BEGIN_ARG_INFO_EX(arginfo_bpf_get, 0, 0, 1)
+				ZEND_ARG_INFO(0, name)
+ZEND_END_ARG_INFO()
+static const zend_function_entry bpf_class_methods[] = {
+		PHP_ME(Bpf, __construct, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
+		PHP_ME(Bpf, __get, arginfo_bpf_get, ZEND_ACC_PUBLIC)
+		PHP_ME(Bpf, get_kprobe_functions, NULL, ZEND_ACC_PUBLIC)
+		PHP_ME(Bpf, attach_kprobe, NULL, ZEND_ACC_PUBLIC)
+		PHP_ME(Bpf, attach_tracepoint, NULL, ZEND_ACC_PUBLIC)
+		PHP_ME(Bpf, attach_raw_tracepoint, NULL, ZEND_ACC_PUBLIC)
+		PHP_ME(Bpf, attach_kfunc, NULL, ZEND_ACC_PUBLIC)
+		PHP_ME(Bpf, attach_lsm, NULL, ZEND_ACC_PUBLIC)
+		PHP_ME(Bpf, attach_uprobe, NULL, ZEND_ACC_PUBLIC)
+		PHP_ME(Bpf, detach_kprobe, NULL, ZEND_ACC_PUBLIC)
+		PHP_ME(Bpf, detach_uprobe, NULL, ZEND_ACC_PUBLIC)
+		PHP_ME(Bpf, trace_print, NULL, ZEND_ACC_PUBLIC)
+		PHP_ME(Bpf, trace_fields, NULL, ZEND_ACC_PUBLIC)
+		PHP_ME(Bpf, get_table, NULL, ZEND_ACC_PUBLIC)
+		PHP_ME(Bpf, perf_buffer_poll, NULL, ZEND_ACC_PUBLIC)
+		PHP_ME(Bpf, get_syscall_fnname, NULL, ZEND_ACC_PUBLIC)
+		PHP_ME(Bpf, load_func, NULL, ZEND_ACC_PUBLIC)
+		PHP_ME(Bpf, attach_raw_socket, NULL, ZEND_ACC_PUBLIC)
+		PHP_FE_END
+};
+/* }}} */
+
+/* {{{ table methods */
+static const zend_function_entry perf_event_array_table_methods[] = {
+		PHP_ME(PerfEventArrayTable, open_perf_buffer, NULL, ZEND_ACC_PUBLIC)
+		PHP_FE_END
+};
+
+static const zend_function_entry hash_table_methods[] = {
+		PHP_ME(HashTable, values, NULL, ZEND_ACC_PUBLIC)
+		PHP_ME(HashTable, clear, NULL, ZEND_ACC_PUBLIC)
+		PHP_FE_END
+};
+
+static const zend_function_entry array_table_methods[] = {
+		PHP_ME(ArrayTable, get_value, NULL, ZEND_ACC_PUBLIC)
+		PHP_ME(ArrayTable, print_log2_hist, NULL, ZEND_ACC_PUBLIC)
+		PHP_ME(ArrayTable, print_linear_hist, NULL, ZEND_ACC_PUBLIC)
+		PHP_FE_END
+};
+
+static const zend_function_entry prog_array_table_methods[] = {
+		PHP_FE_END
+};
+
+static const zend_function_entry per_cpu_hash_table_methods[] = {
+		PHP_FE_END
+};
+
+static const zend_function_entry per_cpu_array_table_methods[] = {
+		PHP_ME(PerCpuArrayTable, sum_value, NULL, ZEND_ACC_PUBLIC)
+		PHP_FE_END
+};
+
+static const zend_function_entry lpm_trie_table_methods[] = {
+		PHP_FE_END
+};
+
+static const zend_function_entry stack_trace_table_methods[] = {
+		PHP_ME(StackTraceTable, values, NULL, ZEND_ACC_PUBLIC)
+		PHP_FE_END
+};
+
+static const zend_function_entry lru_hash_table_methods[] = {
+		PHP_FE_END
+};
+
+static const zend_function_entry lru_per_cpu_hash_table_methods[] = {
+		PHP_FE_END
+};
+
+static const zend_function_entry cgroup_array_table_methods[] = {
+		PHP_FE_END
+};
+
+static const zend_function_entry dev_map_table_methods[] = {
+		PHP_FE_END
+};
+
+static const zend_function_entry cpu_map_table_methods[] = {
+		PHP_FE_END
+};
+
+static const zend_function_entry xsk_map_table_methods[] = {
+		PHP_FE_END
+};
+
+static const zend_function_entry map_in_map_array_table_methods[] = {
+		PHP_FE_END
+};
+
+static const zend_function_entry map_in_map_hash_table_methods[] = {
+		PHP_FE_END
+};
+
+static const zend_function_entry queue_stack_table_methods[] = {
+		PHP_FE_END
+};
+
+static const zend_function_entry ring_buf_table_methods[] = {
+		PHP_FE_END
+};
+
+static const zend_function_entry bpf_prog_func_methods[] = {
+		PHP_FE_END
+};
+/* }}} */
+
+
+PHP_MINIT_FUNCTION (ebpf) {
+	zend_class_entry ce;
+
+	memcpy(&bpf_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+	bpf_object_handlers.offset = XtOffsetOf(bpf_object, std);
+	bpf_object_handlers.free_obj = bpf_free_object;
+
+	REGISTER_BPF_CLASS(ce, bpf_create_object, "Bpf", bpf_ce, bpf_class_methods)
+
+	memcpy(&table_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+	table_object_handlers.offset = XtOffsetOf(sub_object, std);
+	table_object_handlers.free_obj = table_free_object;
+
+	REGISTER_BPF_CLASS(ce, table_create_object, "PerCpuArrayTable", per_cpu_array_table_ce, per_cpu_array_table_methods)
+	REGISTER_BPF_CLASS(ce, table_create_object, "PerfEventArrayTable", perf_event_array_table_ce,
+	                   perf_event_array_table_methods)
+	REGISTER_BPF_CLASS(ce, table_create_object, "HashTable", hash_table_ce, hash_table_methods)
+	REGISTER_BPF_CLASS(ce, table_create_object, "ArrayTable", array_table_ce, array_table_methods)
+	REGISTER_BPF_CLASS(ce, table_create_object, "ProgArrayTable", prog_array_table_ce, prog_array_table_methods)
+	REGISTER_BPF_CLASS(ce, table_create_object, "PerCpuHashTable", per_cpu_hash_table_ce, per_cpu_hash_table_methods)
+	REGISTER_BPF_CLASS(ce, table_create_object, "LpmTrieTable", lpm_trie_table_ce, lpm_trie_table_methods)
+	REGISTER_BPF_CLASS(ce, table_create_object, "StackTraceTable", stack_trace_table_ce, stack_trace_table_methods)
+	REGISTER_BPF_CLASS(ce, table_create_object, "LruHashTable", lru_hash_table_ce, lru_hash_table_methods)
+	REGISTER_BPF_CLASS(ce, table_create_object, "LruPerCpuHashTable", lru_per_cpu_hash_table_ce,
+	                   lru_per_cpu_hash_table_methods)
+	REGISTER_BPF_CLASS(ce, table_create_object, "CgroupArrayTable", cgroup_array_table_ce, cgroup_array_table_methods)
+	REGISTER_BPF_CLASS(ce, table_create_object, "DevMapTable", dev_map_table_ce, dev_map_table_methods)
+	REGISTER_BPF_CLASS(ce, table_create_object, "CpuMapTable", cpu_map_table_ce, cpu_map_table_methods)
+	REGISTER_BPF_CLASS(ce, table_create_object, "XskMapTable", xsk_map_table_ce, xsk_map_table_methods)
+	REGISTER_BPF_CLASS(ce, table_create_object, "MapInMapArrayTable", map_in_map_array_table_ce,
+	                   map_in_map_array_table_methods)
+	REGISTER_BPF_CLASS(ce, table_create_object, "MapInMapHashTable", map_in_map_hash_table_ce,
+	                   map_in_map_hash_table_methods)
+	REGISTER_BPF_CLASS(ce, table_create_object, "QueueStackTable", queue_stack_table_ce, queue_stack_table_methods)
+	REGISTER_BPF_CLASS(ce, table_create_object, "RingBufTable", ring_buf_table_ce, ring_buf_table_methods)
+	REGISTER_BPF_CLASS(ce, table_create_object, "BPFProgFunction", bpf_prog_func_ce, bpf_prog_func_methods)
+
+	/* Register constants */
+	REGISTER_BPF_CONST(SOCKET_FILTER);
+	REGISTER_BPF_CONST(KPROBE);
+	REGISTER_BPF_CONST(SCHED_CLS);
+	REGISTER_BPF_CONST(SCHED_ACT);
+	REGISTER_BPF_CONST(TRACEPOINT);
+	REGISTER_BPF_CONST(XDP);
+	REGISTER_BPF_CONST(PERF_EVENT);
+	REGISTER_BPF_CONST(CGROUP_SKB);
+	REGISTER_BPF_CONST(CGROUP_SOCK);
+	REGISTER_BPF_CONST(LWT_IN);
+	REGISTER_BPF_CONST(LWT_OUT);
+	REGISTER_BPF_CONST(LWT_XMIT);
+	REGISTER_BPF_CONST(SOCK_OPS);
+	REGISTER_BPF_CONST(SK_SKB);
+	REGISTER_BPF_CONST(CGROUP_DEVICE);
+	REGISTER_BPF_CONST(SK_MSG);
+	REGISTER_BPF_CONST(RAW_TRACEPOINT);
+	REGISTER_BPF_CONST(CGROUP_SOCK_ADDR);
+	REGISTER_BPF_CONST(CGROUP_SOCKOPT);
+	REGISTER_BPF_CONST(TRACING);
+	REGISTER_BPF_CONST(LSM);
+	return SUCCESS;
+}
+
+
+/* }}} */
+
+/* {{{ PHP_MSHUTDOWN_FUNCTION
+ */
+PHP_MSHUTDOWN_FUNCTION (ebpf) {
+	/* uncomment this line if you have INI entries
+	UNREGISTER_INI_ENTRIES();
+	*/
+	return SUCCESS;
+}
+/* }}} */
+
+/* Remove if there's nothing to do at request start */
+/* {{{ PHP_RINIT_FUNCTION
+ */
+PHP_RINIT_FUNCTION (ebpf) {
+#if defined(COMPILE_DL_EBPF) && defined(ZTS)
+	ZEND_TSRMLS_CACHE_UPDATE();
+#endif
+	return SUCCESS;
+}
+/* }}} */
+
+/* Remove if there's nothing to do at request end */
+/* {{{ PHP_RSHUTDOWN_FUNCTION
+ */
+PHP_RSHUTDOWN_FUNCTION (ebpf) {
+	return SUCCESS;
+}
+/* }}} */
+
+/* {{{ PHP_MINFO_FUNCTION
+ */
+PHP_MINFO_FUNCTION (ebpf) {
+	php_info_print_table_start();
+	php_info_print_table_header(2, "ebpf support", "enabled");
+	php_info_print_table_end();
+
+	/* Remove comments if you have entries in php.ini
+	DISPLAY_INI_ENTRIES();
+	*/
+}
+/* }}} */
+
+/* {{{ ebpf_functions[]
+ *
+ * Every user visible function must have an entry in ebpf_functions[].
+ */
+const zend_function_entry ebpf_functions[] = {
+		PHP_FE_END    /* Must be the last line in ebpf_functions[] */
+};
+/* }}} */
+
+/* {{{ ebpf_module_entry
+ */
+zend_module_entry ebpf_module_entry = {
+		STANDARD_MODULE_HEADER,
+		"ebpf",
+		ebpf_functions,
+		PHP_MINIT(ebpf),
+		PHP_MSHUTDOWN(ebpf),
+		PHP_RINIT(ebpf),        /* Replace with NULL if there's nothing to do at request start */
+		PHP_RSHUTDOWN(ebpf),    /* Replace with NULL if there's nothing to do at request end */
+		PHP_MINFO(ebpf),
+		PHP_EBPF_VERSION,
+		STANDARD_MODULE_PROPERTIES
+};
+/* }}} */
+
+#ifdef COMPILE_DL_EBPF
+#ifdef ZTS
+ZEND_TSRMLS_CACHE_DEFINE()
+#endif
+ZEND_GET_MODULE(ebpf)
+#endif
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */

+ 21 - 0
ebpf.php

@@ -0,0 +1,21 @@
+<?php
+$br = (php_sapi_name() == "cli")? "":"<br>";
+
+if(!extension_loaded('ebpf')) {
+	dl('ebpf.' . PHP_SHLIB_SUFFIX);
+}
+$module = 'ebpf';
+$functions = get_extension_funcs($module);
+echo "Functions available in the test extension:$br\n";
+foreach($functions as $func) {
+    echo $func."$br\n";
+}
+echo "$br\n";
+$function = 'confirm_' . $module . '_compiled';
+if (extension_loaded($module)) {
+	$str = $function($module);
+} else {
+	$str = "Module $module is not compiled into PHP";
+}
+echo "$str\n";
+?>

+ 212 - 0
php_ebpf.h

@@ -0,0 +1,212 @@
+/*
+  +----------------------------------------------------------------------+
+  | PHP Version 7                                                        |
+  +----------------------------------------------------------------------+
+  | Copyright (c) 1997-2018 The PHP Group                                |
+  +----------------------------------------------------------------------+
+  | This source file is subject to version 3.01 of the PHP license,      |
+  | that is bundled with this package in the file LICENSE, and is        |
+  | available through the world-wide-web at the following url:           |
+  | http://www.php.net/license/3_01.txt                                  |
+  | If you did not receive a copy of the PHP license and are unable to   |
+  | obtain it through the world-wide-web, please send a note to          |
+  | [email protected] so we can mail you a copy immediately.               |
+  +----------------------------------------------------------------------+
+  | Author:                                                              |
+  +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#ifndef PHP_EBPF_H
+#define PHP_EBPF_H
+
+#include <iostream>
+#include <unordered_set>
+
+extern zend_module_entry ebpf_module_entry;
+#define phpext_ebpf_ptr &ebpf_module_entry
+
+#define PHP_EBPF_VERSION "1.0.0" /* Replace with version number for your extension */
+
+#ifdef PHP_WIN32
+#	define PHP_EBPF_API __declspec(dllexport)
+#elif defined(__GNUC__) && __GNUC__ >= 4
+#	define PHP_EBPF_API __attribute__ ((visibility("default")))
+#else
+#	define PHP_EBPF_API
+#endif
+
+extern "C" {
+
+#ifdef ZTS
+#include "TSRM.h"
+#endif
+}
+
+// Common definitions
+#define TRACE_PIPE_PATH "/sys/kernel/debug/tracing/trace_pipe"
+#define DEBUGFS "/sys/kernel/debug"
+#define EXT_NAME "ebpf"
+#define EXT_VERSION "1.0.0"
+
+#define REGISTER_BPF_CLASS(ce, create_obj, php_class_name, cls, method) \
+    INIT_CLASS_ENTRY(ce, php_class_name, method); \
+    ce.create_object = create_obj; \
+    cls = zend_register_internal_class(&ce);
+
+// Common constants
+const std::vector<std::string> syscall_prefixes = {
+		"sys_",
+		"__x64_sys_",
+		"__x32_compat_sys_",
+		"__ia32_compat_sys_",
+		"__arm64_sys_",
+		"__s390x_sys_",
+		"__s390_sys_",
+		"__riscv_sys_"
+};
+
+void callbackfn(void *cookie, void *data, int data_size);
+
+class EbpfExtension {
+private:
+	void *mod;
+
+public:
+	zval _class_perf_event_obj;
+	ebpf::BPF bpf;
+
+	/**
+	 * @brief Default constructor for EbpfExtension
+	 */
+	EbpfExtension() {
+		ZVAL_UNDEF(&_class_perf_event_obj);
+	};
+
+	/**
+	 * @brief Virtual destructor for EbpfExtension
+	 */
+	virtual ~EbpfExtension() {
+		if (Z_TYPE(_class_perf_event_obj) != IS_UNDEF) {
+			zval_ptr_dtor(&_class_perf_event_obj);
+		}
+	};
+
+	ebpf::StatusTuple init(const std::string &bpf_program) {
+		auto res = this->bpf.init(bpf_program);
+		if (res.ok())
+			this->mod = (void *) this->bpf.get_mod();
+		return res;
+	}
+
+	/**
+	 * @brief Add a prefix to a function name
+	 * @param prefix The prefix to add
+	 * @param name The original function name
+	 * @return The function name with prefix added
+	 */
+	static std::string add_prefix(const std::string &prefix, const std::string &name);
+
+	/**
+	 * @brief Fix syscall function name by adding appropriate prefix
+	 * @param name The original syscall function name
+	 * @return The fixed syscall function name with proper prefix
+	 */
+	std::string fix_syscall_fnname(const std::string &name);
+
+	/**
+	 * @brief Automatically load trace functions
+	 * This method is responsible for loading and initializing trace-related functions
+	 */
+	void _trace_autoload();
+
+	/**
+	 * @brief Get kprobe functions matching a regular expression
+	 * @param event_re Regular expression to match function names
+	 * @return Set of matching function names
+	 */
+	std::unordered_set<std::string> get_kprobe_functions(const std::string &event_re);
+
+	/**
+	 * @brief Attach a kfunc (kernel function) probe
+	 * @param kfn The kernel function name to attach to
+	 * @return Status tuple indicating success or failure
+	 */
+	ebpf::StatusTuple attach_kfunc(const std::string &kfn);
+
+	/**
+	 * @brief Attach an LSM (Linux Security Module) probe
+	 * @param lsm The LSM hook name to attach to
+	 * @return Status tuple indicating success or failure
+	 */
+	ebpf::StatusTuple attach_lsm(const std::string &lsm);
+
+	/**
+	 * @brief Get a BPF table class
+	 * @param table_name Name of the BPF table
+	 * @param from_attr Attribute to get from the table
+	 * @return The table class object
+	 */
+	zval get_table_cls(const char *table_name, int from_attr);
+};
+
+class BPFProgType {
+public:
+	static constexpr int SOCKET_FILTER = 1;
+	static constexpr int KPROBE = 2;
+	static constexpr int SCHED_CLS = 3;
+	static constexpr int SCHED_ACT = 4;
+	static constexpr int TRACEPOINT = 5;
+	static constexpr int XDP = 6;
+	static constexpr int PERF_EVENT = 7;
+	static constexpr int CGROUP_SKB = 8;
+	static constexpr int CGROUP_SOCK = 9;
+	static constexpr int LWT_IN = 10;
+	static constexpr int LWT_OUT = 11;
+	static constexpr int LWT_XMIT = 12;
+	static constexpr int SOCK_OPS = 13;
+	static constexpr int SK_SKB = 14;
+	static constexpr int CGROUP_DEVICE = 15;
+	static constexpr int SK_MSG = 16;
+	static constexpr int RAW_TRACEPOINT = 17;
+	static constexpr int CGROUP_SOCK_ADDR = 18;
+	static constexpr int CGROUP_SOCKOPT = 25;
+	static constexpr int TRACING = 26;
+	static constexpr int LSM = 29;
+};
+
+
+#define REGISTER_BPF_CONST(name) \
+    zend_declare_class_constant_long(bpf_ce, #name, sizeof(#name) - 1, BPFProgType::name)
+
+/*
+  	Declare any global variables you may need between the BEGIN
+	and END macros here:
+
+ZEND_BEGIN_MODULE_GLOBALS(ebpf)
+	zend_long  global_value;
+	char *global_string;
+ZEND_END_MODULE_GLOBALS(ebpf)
+*/
+
+/* Always refer to the globals in your function as EBPF_G(variable).
+   You are encouraged to rename these macros something shorter, see
+   examples in any other php module directory.
+*/
+#define EBPF_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(ebpf, v)
+
+#if defined(ZTS) && defined(COMPILE_DL_EBPF)
+ZEND_TSRMLS_CACHE_EXTERN()
+#endif
+
+#endif    /* PHP_EBPF_H */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */

+ 21 - 0
tests/001.phpt

@@ -0,0 +1,21 @@
+--TEST--
+Check for ebpf presence
+--SKIPIF--
+<?php if (!extension_loaded("ebpf")) print "skip"; ?>
+--FILE--
+<?php
+echo "ebpf extension is available";
+/*
+	you can add regression tests for your extension here
+
+  the output of your test code has to be equal to the
+  text in the --EXPECT-- section below for the tests
+  to pass, differences between the output and the
+  expected text are interpreted as failure
+
+	see php7/README.TESTING for further information on
+  writing regression tests
+*/
+?>
+--EXPECT--
+ebpf extension is available