8 Commits

10 changed files with 105 additions and 74 deletions

View File

@ -1,6 +1,10 @@
cmake_minimum_required(VERSION 4.0)
project(pylibbpf)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# pybind11
include_directories(${CMAKE_SOURCE_DIR}/src)
add_subdirectory(pybind11)
@ -9,9 +13,13 @@ pybind11_add_module(
src/core/bpf_program.h
src/core/bpf_exception.h
src/core/bpf_map.h
src/core/bpf_object.h
src/core/bpf_perf_buffer.h
src/bindings/main.cpp
src/core/bpf_program.cpp
src/core/bpf_map.cpp)
src/core/bpf_map.cpp
src/core/bpf_object.cpp
src/core/bpf_perf_buffer.cpp)
# --- libbpf build rules ---
set(LIBBPF_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libbpf/src)

View File

@ -6,9 +6,11 @@ extern "C" {
#include <libbpf.h>
}
#include "core/bpf_object.h"
#include "core/bpf_program.h"
#include "core/bpf_exception.h"
#include "core/bpf_map.h"
#include "core/bpf_perf_buffer.h"
namespace py = pybind11;
@ -30,33 +32,48 @@ PYBIND11_MODULE(pylibbpf, m) {
// Register the custom exception
py::register_exception<BpfException>(m, "BpfException");
py::class_<BpfProgram>(m, "BpfProgram")
.def(py::init<const std::string &>())
.def(py::init<const std::string &, const std::string &>())
.def("load", &BpfProgram::load)
.def("attach", &BpfProgram::attach)
.def("destroy", &BpfProgram::destroy)
.def("load_and_attach", &BpfProgram::load_and_attach)
.def("is_loaded", &BpfProgram::is_loaded)
.def("is_attached", &BpfProgram::is_attached);
// BpfObject
py::class_<BpfObject, std::shared_ptr<BpfObject>>(m, "BpfObject")
.def(py::init<std::string>(), py::arg("object_path"))
.def("load", &BpfObject::load)
.def("is_loaded", &BpfObject::is_loaded)
.def("get_program_names", &BpfObject::get_program_names)
.def("get_program", &BpfObject::get_program, py::arg("name"))
.def("attach_all", &BpfObject::attach_all)
.def("get_map_names", &BpfObject::get_map_names)
.def("get_map", &BpfObject::get_map, py::arg("name"));
py::class_<BpfMap>(m, "BpfMap")
.def(py::init<BpfProgram *, py::object &>())
.def("lookup", &BpfMap::lookup)
.def("update", &BpfMap::update)
.def("delete", &BpfMap::delete_elem)
.def("get_next_key", &BpfMap::get_next_key, py::arg("key") = py::none())
.def("items", &BpfMap::items)
.def("keys", &BpfMap::keys)
.def("values", &BpfMap::values)
.def("get_name", &BpfMap::get_name)
.def("get_type", &BpfMap::get_type)
.def("get_key_size", &BpfMap::get_key_size)
.def("get_value_size", &BpfMap::get_value_size)
.def("get_max_entries", &BpfMap::get_max_entries)
.def("__getitem__", &BpfMap::lookup)
.def("__setitem__", &BpfMap::update)
.def("__delitem__", &BpfMap::delete_elem);
// BpfProgram
py::class_<BpfProgram, std::shared_ptr<BpfProgram>>(m, "BpfProgram")
.def("attach", &BpfProgram::attach)
.def("detach", &BpfProgram::detach)
.def("is_attached", &BpfProgram::is_attached)
.def("get_name", &BpfProgram::get_name);
// BpfMap
py::class_<BpfMap, std::shared_ptr<BpfMap>>(m, "BpfMap")
.def("lookup", &BpfMap::lookup, py::arg("key"))
.def("update", &BpfMap::update, py::arg("key"), py::arg("value"))
.def("delete_elem", &BpfMap::delete_elem, py::arg("key"))
.def("get_next_key", &BpfMap::get_next_key, py::arg("key") = py::none())
.def("items", &BpfMap::items)
.def("keys", &BpfMap::keys)
.def("values", &BpfMap::values)
.def("get_name", &BpfMap::get_name)
.def("get_fd", &BpfMap::get_fd)
.def("get_type", &BpfMap::get_type)
.def("get_key_size", &BpfMap::get_key_size)
.def("get_value_size", &BpfMap::get_value_size)
.def("get_max_entries", &BpfMap::get_max_entries);
py::class_<BpfPerfBuffer>(m, "BpfPerfBuffer")
.def(py::init<int, int, py::function, py::object>(),
py::arg("map_fd"),
py::arg("page_cnt") = 8,
py::arg("callback"),
py::arg("lost_callback") = py::none())
.def("poll", &BpfPerfBuffer::poll, py::arg("timeout_ms") = -1)
.def("consume", &BpfPerfBuffer::consume);
#ifdef VERSION_INFO

View File

@ -6,13 +6,10 @@
class BpfException final : public std::runtime_error {
public:
explicit BpfException(const std::string &message)
: std::runtime_error(message) {
}
explicit BpfException(const std::string &message)
: std::runtime_error(message) {}
explicit BpfException(const char *message)
: std::runtime_error(message) {
}
explicit BpfException(const char *message) : std::runtime_error(message) {}
};
#endif // PYLIBBPF_BPF_EXCEPTION_H

View File

@ -74,7 +74,7 @@ void BpfObject::load() {
// ==================== Program Methods ====================
py::list BpfObject::get_program_names() const {
py::list BpfObject::get_program_names() {
if (!loaded_) {
throw BpfException("BPF object not loaded");
}
@ -106,7 +106,8 @@ BpfObject::_get_or_create_program(struct bpf_program *prog) {
}
// Create and cache
auto bpf_prog = std::make_shared<BpfProgram>(this, prog, prog_name);
auto bpf_prog =
std::make_shared<BpfProgram>(shared_from_this(), prog, prog_name);
prog_cache_[prog_name] = bpf_prog;
return bpf_prog;
@ -125,7 +126,7 @@ std::shared_ptr<BpfProgram> BpfObject::get_program(const std::string &name) {
// Create and cache
struct bpf_program *raw_prog = find_program_by_name(name);
auto prog = std::make_shared<BpfProgram>(this, raw_prog, name);
auto prog = std::make_shared<BpfProgram>(shared_from_this(), raw_prog, name);
prog_cache_[name] = prog;
return prog;
@ -148,8 +149,8 @@ BpfObject::find_program_by_name(const std::string &name) const {
py::dict BpfObject::get_cached_programs() const {
py::dict programs;
for (const auto &[name, prog] : prog_cache_) {
programs[name] = prog;
for (const auto &entry : prog_cache_) {
programs[entry.first.c_str()] = entry.second;
}
return programs;
}
@ -178,7 +179,7 @@ py::dict BpfObject::attach_all() {
// ==================== Map Methods ====================
py::list BpfObject::get_map_names() const {
py::list BpfObject::get_map_names() {
if (!loaded_) {
throw BpfException("BPF object not loaded");
}
@ -249,8 +250,8 @@ struct bpf_map *BpfObject::find_map_by_name(const std::string &name) const {
py::dict BpfObject::get_cached_maps() const {
py::dict maps;
for (const auto &[name, map] : maps_cache_) {
maps[name] = map;
for (const auto &entry : maps_cache_) {
maps[entry.first.c_str()] = entry.second;
}
return maps;
}

View File

@ -65,7 +65,7 @@ public:
py::dict attach_all();
// Program access
[[nodiscard]] py::list get_program_names() const;
[[nodiscard]] py::list get_program_names();
[[nodiscard]] std::shared_ptr<BpfProgram>
get_program(const std::string &name);
[[nodiscard]] struct bpf_program *
@ -73,7 +73,7 @@ public:
[[nodiscard]] py::dict get_cached_programs() const;
// Map access
[[nodiscard]] py::list get_map_names() const;
[[nodiscard]] py::list get_map_names();
[[nodiscard]] std::shared_ptr<BpfMap> get_map(const std::string &name);
[[nodiscard]] struct bpf_map *find_map_by_name(const std::string &name) const;
[[nodiscard]] py::dict get_cached_maps() const;

View File

@ -1,6 +1,38 @@
#include "bpf_perf_buffer.h"
#include "bpf_exception.h"
BpfPerfBuffer::BpfPerfBuffer(int map_fd, int page_cnt, py::function callback,
py::object lost_callback)
: pb_(nullptr), callback_(std::move(callback)),
lost_callback_(lost_callback) {
if (page_cnt <= 0 || (page_cnt & (page_cnt - 1)) != 0) {
throw BpfException("page_cnt must be a positive power of 2");
}
struct perf_buffer_opts pb_opts = {};
pb_opts.sz = sizeof(pb_opts); // Required for forward compatibility
pb_ = perf_buffer__new(
map_fd, page_cnt,
sample_callback_wrapper, // sample_cb
lost_callback.is_none() ? nullptr : lost_callback_wrapper, // lost_cb
this, // ctx
&pb_opts // opts
);
if (!pb_) {
throw BpfException("Failed to create perf buffer: " +
std::string(std::strerror(errno)));
}
}
BpfPerfBuffer::~BpfPerfBuffer() {
if (pb_) {
perf_buffer__free(pb_);
}
}
void BpfPerfBuffer::sample_callback_wrapper(void *ctx, int cpu, void *data,
unsigned int size) {
auto *self = static_cast<BpfPerfBuffer *>(ctx);
@ -36,33 +68,6 @@ void BpfPerfBuffer::lost_callback_wrapper(void *ctx, int cpu,
}
}
BpfPerfBuffer::BpfPerfBuffer(int map_fd, int page_cnt, py::function callback,
py::object lost_callback)
: pb_(nullptr), callback_(std::move(callback)) {
if (!lost_callback.is_none()) {
lost_callback_ = lost_callback.cast<py::function>();
}
// Setup perf buffer options
perf_buffer_opts pb_opts = {};
pb_opts.sample_cb = sample_callback_wrapper;
pb_opts.lost_cb = lost_callback.is_none() ? nullptr : lost_callback_wrapper;
pb_opts.ctx = this;
// Create perf buffer
pb_ = perf_buffer__new(map_fd, page_cnt, &pb_opts);
if (!pb_) {
throw BpfException("Failed to create perf buffer");
}
}
BpfPerfBuffer::~BpfPerfBuffer() {
if (pb_) {
perf_buffer__free(pb_);
}
}
int BpfPerfBuffer::poll(int timeout_ms) {
// Release GIL during blocking poll
py::gil_scoped_release release;

View File

@ -20,11 +20,12 @@ private:
public:
BpfPerfBuffer(int map_fd, int page_cnt, py::function callback,
py::object lost_callback);
py::object lost_callback = py::none());
~BpfPerfBuffer();
int poll(int timeout_ms);
int consume();
[[nodiscard]] int fd() const;
};
#endif // PYLIBBPF_BPF_PERF_BUFFER_H

View File

@ -1,5 +1,6 @@
#include "bpf_program.h"
#include "bpf_exception.h"
#include "bpf_object.h"
#include <cerrno>
#include <utility>

View File

@ -1,6 +1,7 @@
#ifndef PYLIBBPF_BPF_PROGRAM_H
#define PYLIBBPF_BPF_PROGRAM_H
#include <cstring>
#include <libbpf.h>
#include <memory>
#include <string>
@ -26,8 +27,8 @@ public:
BpfProgram(BpfProgram &&) noexcept;
BpfProgram &operator=(BpfProgram &&) noexcept;
bool attach();
bool detach();
void attach();
void detach();
[[nodiscard]] bool is_attached() const { return link_ != nullptr; }
[[nodiscard]] std::string get_name() const { return program_name_; }

View File

@ -3,5 +3,5 @@ import pylibbpf as m
def test_main():
assert m.__version__ == "0.0.5"
prog = m.BpfProgram("tests/execve2.o")
prog = m.BpfObject("tests/execve2.o")
print(prog)