From 8d27a351e09a984accdf5805b6b83f50a4f6fb58 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Fri, 17 Oct 2025 03:47:31 +0530 Subject: [PATCH 01/49] Add src/core/bpf_perf_buffer --- src/core/bpf_perf_buffer.cpp | 72 ++++++++++++++++++++++++++++++++++++ src/core/bpf_perf_buffer.h | 28 ++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 src/core/bpf_perf_buffer.cpp create mode 100644 src/core/bpf_perf_buffer.h diff --git a/src/core/bpf_perf_buffer.cpp b/src/core/bpf_perf_buffer.cpp new file mode 100644 index 0000000..e0f56f9 --- /dev/null +++ b/src/core/bpf_perf_buffer.cpp @@ -0,0 +1,72 @@ +#include "bpf_perf_buffer.h" +#include "bpf_exception.h" + +void BpfPerfBuffer::sample_callback_wrapper(void *ctx, int cpu, void *data, unsigned int size) { + auto *self = static_cast(ctx); + + // Acquire GIL for Python calls + py::gil_scoped_acquire acquire; + + try { + // Convert data to Python bytes + py::bytes py_data(static_cast(data), size); + + // Call Python callback: callback(cpu, data, size) + self->callback_(cpu, py_data, size); + } catch (const py::error_already_set &e) { + PyErr_Print(); + } +} + +void BpfPerfBuffer::lost_callback_wrapper(void *ctx, int cpu, unsigned long long cnt) { + auto *self = static_cast(ctx); + + if (self->lost_callback_.is_none()) { + return; + } + + py::gil_scoped_acquire acquire; + + try { + self->lost_callback_(cpu, cnt); + } catch (const py::error_already_set &e) { + PyErr_Print(); + } +} + +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(); + } + + // 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; + return perf_buffer__poll(pb_, timeout_ms); +} + +int BpfPerfBuffer::consume() { + py::gil_scoped_release release; + return perf_buffer__consume(pb_); +} diff --git a/src/core/bpf_perf_buffer.h b/src/core/bpf_perf_buffer.h new file mode 100644 index 0000000..23b7db8 --- /dev/null +++ b/src/core/bpf_perf_buffer.h @@ -0,0 +1,28 @@ +#ifndef PYLIBBPF_BPF_PERF_BUFFER_H +#define PYLIBBPF_BPF_PERF_BUFFER_H + +#include +#include +#include + +namespace py = pybind11; + +class BpfPerfBuffer { +private: + struct perf_buffer *pb_; + py::function callback_; + py::function lost_callback_; + + // Static callback wrappers for C API + static void sample_callback_wrapper(void *ctx, int cpu, void *data, unsigned int size); + static void lost_callback_wrapper(void *ctx, int cpu, unsigned long long cnt); + +public: + BpfPerfBuffer(int map_fd, int page_cnt, py::function callback, py::object lost_callback); + ~BpfPerfBuffer(); + + int poll(int timeout_ms); + int consume(); +}; + +#endif // PYLIBBPF_BPF_PERF_BUFFER_H From 528a54247d70ecc4d77bac2294eb3c31057fa722 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Fri, 17 Oct 2025 18:12:26 +0530 Subject: [PATCH 02/49] Create bpf_object.h as a container for the object file having bpf progs and maps --- src/core/bpf_object.h | 76 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/core/bpf_object.h diff --git a/src/core/bpf_object.h b/src/core/bpf_object.h new file mode 100644 index 0000000..a774bf0 --- /dev/null +++ b/src/core/bpf_object.h @@ -0,0 +1,76 @@ +#ifndef PYLIBBPF_BPF_OBJECT_H +#define PYLIBBPF_BPF_OBJECT_H + +#include +#include +#include +#include +#include +#include + +namespace py = pybind11; + +class BpfProgram; +class BpfMap; + +/** + * BpfObject - Represents a loaded BPF object file. + * + * This is the main entry point for loading BPF programs. + * Owns the bpf_object* and manages all programs and maps within it. + */ +class BpfObject { +private: + struct bpf_object *obj_; + std::string object_path_; + bool loaded_; + + mutable std::unordered_map> maps_cache_; + mutable std::unordered_map> prog_cache_; + +public: + explicit BpfObject(std::string object_path); + ~BpfObject(); + + // Disable copy, allow move + BpfObject(const BpfObject&) = delete; + BpfObject& operator=(const BpfObject&) = delete; + BpfObject(BpfObject&&) noexcept; + BpfObject& operator=(BpfObject&&) noexcept; + + /** + * Load the BPF object into the kernel. + * Must be called before accessing programs or maps. + */ + void load(); + + /** + * Check if object is loaded. + */ + [[nodiscard]] bool is_loaded() const { return loaded_; } + + /** + * Get the underlying bpf_object pointer. + * Only for internal use by BpfProgram and BpfMap. + */ + [[nodiscard]] struct bpf_object* get_obj() const { return obj_; } + + /** + * Attach all programs in the object. + */ + py::dict attach_all(); + + // Program access + [[nodiscard]] py::list get_program_names() const; + [[nodiscard]] std::shared_ptr get_program(const std::string& name); + [[nodiscard]] struct bpf_program* find_program_by_name(const std::string& name) const; + [[nodiscard]] py::dict get_programs() const; + + // Map access + [[nodiscard]] py::list get_map_names() const; + [[nodiscard]] std::shared_ptr get_map(const std::string& name); + [[nodiscard]] struct bpf_map* find_map_by_name(const std::string& name) const; + [[nodiscard]] py::dict get_maps() const; +}; + +#endif // PYLIBBPF_BPF_OBJECT_H From 763c188fa935eae76e687e0cbe0325d7a260c89f Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sat, 18 Oct 2025 04:59:14 +0530 Subject: [PATCH 03/49] Implement BpfObject class --- src/core/bpf_object.cpp | 255 ++++++++++++++++++++++++++++++++++++++++ src/core/bpf_object.h | 7 +- 2 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 src/core/bpf_object.cpp diff --git a/src/core/bpf_object.cpp b/src/core/bpf_object.cpp new file mode 100644 index 0000000..e2340f9 --- /dev/null +++ b/src/core/bpf_object.cpp @@ -0,0 +1,255 @@ +#include "bpf_object.h" +#include "bpf_program.h" +#include "bpf_map.h" +#include "bpf_exception.h" +#include + +BpfObject::BpfObject(std::string object_path) + : obj_(nullptr), object_path_(std::move(object_path)), loaded_(false) { +} + +BpfObject::~BpfObject() { + // Clear caches first (order matters!) + prog_cache_.clear(); // Detaches programs + maps_cache_.clear(); // Closes maps + + // Then close object + if (obj_) { + bpf_object__close(obj_); + obj_ = nullptr; + } +} + +BpfObject::BpfObject(BpfObject&& other) noexcept + : obj_(other.obj_), + object_path_(std::move(other.object_path_)), + loaded_(other.loaded_), + prog_cache_(std::move(other.prog_cache_)), + maps_cache_(std::move(other.maps_cache_)) { + + other.obj_ = nullptr; + other.loaded_ = false; +} + +BpfObject& BpfObject::operator=(BpfObject&& other) noexcept { + if (this != &other) { + prog_cache_.clear(); + maps_cache_.clear(); + if (obj_) { + bpf_object__close(obj_); + } + + obj_ = other.obj_; + object_path_ = std::move(other.object_path_); + loaded_ = other.loaded_; + prog_cache_ = std::move(other.prog_cache_); + maps_cache_ = std::move(other.maps_cache_); + + other.obj_ = nullptr; + other.loaded_ = false; + } + return *this; +} + +void BpfObject::load() { + if (loaded_) { + throw BpfException("BPF object already loaded"); + } + + std::string error_msg = "Failed to open BPF object"; + obj_ = bpf_object__open_file(object_path_.c_str(), nullptr); + + if (!obj_) { + error_msg += " file '" + object_path_ + "': " + std::strerror(errno); + throw BpfException(error_msg); + } + + if (bpf_object__load(obj_)) { + std::string error_msg = " object from file '" + object_path_ + "': " + std::strerror(errno); + bpf_object__close(obj_); + obj_ = nullptr; + throw BpfException(error_msg); + } + + loaded_ = true; +} + +// ==================== Program Methods ==================== + +py::list BpfObject::get_program_names() const { + if (!loaded_) { + throw BpfException("BPF object not loaded"); + } + + py::list names; + struct bpf_program *prog = nullptr; + + bpf_object__for_each_program(prog, obj_) { + _get_or_create_program(prog); // Ensure cached + names.append(bpf_program__name(prog)); + } + + return names; +} + +std::shared_ptr BpfObject::_get_or_create_program(struct bpf_program *prog) { + if (!prog) { + throw BpfException("bpf_program pointer is null"); + } + + const char *name = bpf_program__name(prog); + std::string prog_name(name ? name : ""); + + // Check cache + auto it = prog_cache_.find(prog_name); + if (it != prog_cache_.end()) { + return it->second; + } + + // Create and cache + auto bpf_prog = std::make_shared(this, prog, prog_name); + prog_cache_[prog_name] = bpf_prog; + + return bpf_prog; +} + +std::shared_ptr BpfObject::get_program(const std::string& name) { + if (!loaded_) { + throw BpfException("BPF object not loaded"); + } + + // Check cache + auto it = prog_cache_.find(name); + if (it != prog_cache_.end()) { + return it->second; + } + + // Create and cache + struct bpf_program *raw_prog = find_program_by_name(name); + auto prog = std::make_shared(this, raw_prog, name); + prog_cache_[name] = prog; + + return prog; +} + +struct bpf_program* BpfObject::find_program_by_name(const std::string& name) const { + if (!loaded_) { + throw BpfException("BPF object not loaded"); + } + + struct bpf_program *prog = bpf_object__find_program_by_name(obj_, name.c_str()); + if (!prog) { + throw BpfException("Program '" + name + "' not found"); + } + + return prog; +} + +py::dict BpfObject::get_cached_programs() const { + py::dict programs; + for (const auto& [name, prog] : prog_cache_) { + programs[name] = prog; + } + return programs; +} + +py::dict BpfObject::attach_all() { + if (!loaded_) { + throw BpfException("BPF object not loaded"); + } + + py::dict attached_programs; + struct bpf_program *prog = nullptr; + + bpf_object__for_each_program(prog, obj_) { + auto bpf_prog = _get_or_create_program(prog); + + if (!bpf_prog->is_attached()) { + bpf_prog->attach(); + } + + const char *name = bpf_program__name(prog); + attached_programs[name] = bpf_prog; + } + + return attached_programs; +} + +// ==================== Map Methods ==================== + +py::list BpfObject::get_map_names() const { + if (!loaded_) { + throw BpfException("BPF object not loaded"); + } + + py::list names; + struct bpf_map *map = nullptr; + + bpf_object__for_each_map(map, obj_) { + _get_or_create_map(map); // Ensure cached + names.append(bpf_map__name(map)); + } + + return names; +} + +std::shared_ptr BpfObject::get_map(const std::string& name) { + if (!loaded_) { + throw BpfException("BPF object not loaded"); + } + + // Check cache + auto it = maps_cache_.find(name); + if (it != maps_cache_.end()) { + return it->second; + } + + // Create and cache + struct bpf_map *raw_map = find_map_by_name(name); + auto map = std::make_shared(this, raw_map, name); + maps_cache_[name] = map; + + return map; +} + +std::shared_ptr BpfObject::_get_or_create_map(struct bpf_map *map) { + if (!map) { + throw BpfException("bpf_map pointer is null"); + } + + const char *name = bpf_map__name(map); + std::string map_name(name ? name : ""); + + // Check cache + auto it = maps_cache_.find(map_name); + if (it != maps_cache_.end()) { + return it->second; + } + + // Create and cache + auto bpf_map = std::make_shared(this, map, map_name); + maps_cache_[map_name] = bpf_map; + + return bpf_map; +} + +struct bpf_map* BpfObject::find_map_by_name(const std::string& name) const { + if (!loaded_) { + throw BpfException("BPF object not loaded"); + } + + struct bpf_map *map = bpf_object__find_map_by_name(obj_, name.c_str()); + if (!map) { + throw BpfException("Map '" + name + "' not found"); + } + + return map; +} + +py::dict BpfObject::get_cached_maps() const { + py::dict maps; + for (const auto& [name, map] : maps_cache_) { + maps[name] = map; + } + return maps; +} diff --git a/src/core/bpf_object.h b/src/core/bpf_object.h index a774bf0..d6df1bf 100644 --- a/src/core/bpf_object.h +++ b/src/core/bpf_object.h @@ -28,6 +28,9 @@ class BpfObject { mutable std::unordered_map> maps_cache_; mutable std::unordered_map> prog_cache_; + std::shared_ptr _get_or_create_program(struct bpf_program *prog); + std::shared_ptr _get_or_create_map(struct bpf_map *map); + public: explicit BpfObject(std::string object_path); ~BpfObject(); @@ -64,13 +67,13 @@ class BpfObject { [[nodiscard]] py::list get_program_names() const; [[nodiscard]] std::shared_ptr get_program(const std::string& name); [[nodiscard]] struct bpf_program* find_program_by_name(const std::string& name) const; - [[nodiscard]] py::dict get_programs() const; + [[nodiscard]] py::dict get_cached_programs() const; // Map access [[nodiscard]] py::list get_map_names() const; [[nodiscard]] std::shared_ptr get_map(const std::string& name); [[nodiscard]] struct bpf_map* find_map_by_name(const std::string& name) const; - [[nodiscard]] py::dict get_maps() const; + [[nodiscard]] py::dict get_cached_maps() const; }; #endif // PYLIBBPF_BPF_OBJECT_H From 2b99f01b02d2fdbca65a7d67d65b4b9744b1f023 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sat, 18 Oct 2025 12:36:15 +0530 Subject: [PATCH 04/49] Rework BpfProgram.h, pass BpfObject as shared_ptr to BpfPrograms --- src/core/bpf_object.cpp | 4 ++-- src/core/bpf_object.h | 2 +- src/core/bpf_program.h | 21 ++++++++++----------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/core/bpf_object.cpp b/src/core/bpf_object.cpp index e2340f9..c9fe31f 100644 --- a/src/core/bpf_object.cpp +++ b/src/core/bpf_object.cpp @@ -206,7 +206,7 @@ std::shared_ptr BpfObject::get_map(const std::string& name) { // Create and cache struct bpf_map *raw_map = find_map_by_name(name); - auto map = std::make_shared(this, raw_map, name); + auto map = std::make_shared(shared_from_this(), raw_map, name); maps_cache_[name] = map; return map; @@ -227,7 +227,7 @@ std::shared_ptr BpfObject::_get_or_create_map(struct bpf_map *map) { } // Create and cache - auto bpf_map = std::make_shared(this, map, map_name); + auto bpf_map = std::make_shared(shared_from_this(), map, map_name); maps_cache_[map_name] = bpf_map; return bpf_map; diff --git a/src/core/bpf_object.h b/src/core/bpf_object.h index d6df1bf..a82474d 100644 --- a/src/core/bpf_object.h +++ b/src/core/bpf_object.h @@ -19,7 +19,7 @@ class BpfMap; * This is the main entry point for loading BPF programs. * Owns the bpf_object* and manages all programs and maps within it. */ -class BpfObject { +class BpfObject : public std::enable_shared_from_this { private: struct bpf_object *obj_; std::string object_path_; diff --git a/src/core/bpf_program.h b/src/core/bpf_program.h index 93532da..f55b1c4 100644 --- a/src/core/bpf_program.h +++ b/src/core/bpf_program.h @@ -3,36 +3,35 @@ #include #include +#include #include -namespace py = pybind11; +class BpfObject; class BpfProgram { private: - struct bpf_object *obj_; + std::weak_ptr parent_obj_; struct bpf_program *prog_; struct bpf_link *link_; - std::string object_path_; std::string program_name_; - std::vector > programs; public: - explicit BpfProgram(std::string object_path, std::string program_name = ""); + explicit BpfProgram(std::shared_ptr parent, struct bpf_program *raw_prog, std::string program_name = ""); ~BpfProgram(); - struct bpf_object *get_obj() const; - - bool load(); + BpfProgram(const BpfProgram&) = delete; + BpfProgram& operator=(const BpfProgram&) = delete; + BpfProgram(BpfProgram&&) noexcept; + BpfProgram& operator=(BpfProgram&&) noexcept; bool attach(); - - bool destroy(); + bool detach(); void load_and_attach(); - [[nodiscard]] bool is_loaded() const { return obj_ != nullptr; } [[nodiscard]] bool is_attached() const { return link_ != nullptr; } + [[nodiscard]] std::string get_name() const { return program_name_; } }; #endif //PYLIBBPF_BPF_PROGRAM_H From 5c74be041ee8f03ff0f50c1e7b043703b460a9c0 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sat, 18 Oct 2025 13:24:03 +0530 Subject: [PATCH 05/49] Reimplement BpfProgram --- src/core/bpf_object.cpp | 2 +- src/core/bpf_program.cpp | 117 ++++++++++++++++++--------------------- src/core/bpf_program.h | 4 +- 3 files changed, 55 insertions(+), 68 deletions(-) diff --git a/src/core/bpf_object.cpp b/src/core/bpf_object.cpp index c9fe31f..babaa92 100644 --- a/src/core/bpf_object.cpp +++ b/src/core/bpf_object.cpp @@ -65,7 +65,7 @@ void BpfObject::load() { } if (bpf_object__load(obj_)) { - std::string error_msg = " object from file '" + object_path_ + "': " + std::strerror(errno); + error_msg += " object from file '" + object_path_ + "': " + std::strerror(errno); bpf_object__close(obj_); obj_ = nullptr; throw BpfException(error_msg); diff --git a/src/core/bpf_program.cpp b/src/core/bpf_program.cpp index 5b9fbb2..c606b5d 100644 --- a/src/core/bpf_program.cpp +++ b/src/core/bpf_program.cpp @@ -1,84 +1,73 @@ #include "bpf_program.h" #include "bpf_exception.h" -#include #include +#include -BpfProgram::BpfProgram(std::string object_path, std::string program_name) - : obj_(nullptr), prog_(nullptr), link_(nullptr), - object_path_(std::move(object_path)), program_name_(std::move(program_name)) { +BpfProgram::BpfProgram(std::shared_ptr parent, struct bpf_program *raw_prog, const std::string& program_name) + : parent_obj_(parent), + prog_(raw_prog), + link_(nullptr), + program_name_(program_name) { + if (!parent) + throw BpfException("Parent BpfObject is null"); + if (!raw_prog) + throw BpfException("bpf_program pointer is null"); } BpfProgram::~BpfProgram() { - destroy(); - if (obj_) { - bpf_object__close(obj_); - } + detach(); } -struct bpf_object * BpfProgram::get_obj() const { - return obj_; +BpfProgram::BpfProgram(BpfProgram&& other) noexcept + : parent_obj_(std::move(other.parent_obj_)), + prog_(other.prog_), + link_(other.link_), + program_name_(std::move(other.program_name_)) { + + other.prog_ = nullptr; + other.link_ = nullptr; } -bool BpfProgram::load() { - // Open the eBPF object file - obj_ = bpf_object__open_file(object_path_.c_str(), nullptr); - if (libbpf_get_error(obj_)) { - throw BpfException("Failed to open BPF object file: " + object_path_); +BpfProgram& BpfProgram::operator=(BpfProgram&& other) noexcept { + if (this != &other) { + detach(); + + parent_obj_ = std::move(other.parent_obj_); + prog_ = other.prog_; + link_ = other.link_; + program_name_ = std::move(other.program_name_); + + other.prog_ = nullptr; + other.link_ = nullptr; } + return *this; +} - // Find the program by name (if specified) - if (!program_name_.empty()) { - prog_ = bpf_object__find_program_by_name(obj_, program_name_.c_str()); - if (!prog_) { - throw BpfException("Program '" + program_name_ + "' not found in object"); - } - } else { - while ((prog_ = bpf_object__next_program(obj_, prog_)) != nullptr) { - programs.emplace_back(prog_, nullptr); - } - - // throw if no programs found - if (programs.empty()) { - throw BpfException("No programs found in object file"); - } +void BpfProgram::attach() { + // Check if parent is still alive + auto parent = parent_obj_.lock(); + if (!parent) { + throw BpfException("Parent BpfObject has been destroyed"); } - - // Load the eBPF object into the kernel - if (bpf_object__load(obj_)) { - throw BpfException("Failed to load BPF object into kernel"); + + if (link_) { + throw BpfException("Program '" + program_name_ + "' already attached"); } - - return true; -} - -bool BpfProgram::attach() { - for (auto [prog, link]: programs) { - if (!prog) { - throw BpfException("Program not loaded"); - } - - link = bpf_program__attach(prog); - if (libbpf_get_error(link)) { - link = nullptr; - throw BpfException("Failed to attach BPF program"); - } + + if (!prog_) { + throw BpfException("Program '" + program_name_ + "' not initialized"); } - - return true; -} - -bool BpfProgram::destroy() { - bool success = true; - for (auto [prog, link]: programs) { - if (!prog) { - throw BpfException("Program not loaded"); - } - success = success & bpf_link__destroy(link); + + link_ = bpf_program__attach(prog_); + if (!link_) { + std::string err_msg = "bpf_program__attach failed for program '" + program_name_ + "': " + std::strerror(errno); + throw BpfException(err_msg); } - return success; } -void BpfProgram::load_and_attach() { - load(); - attach(); +void BpfProgram::detach() { + if (link_) { + bpf_link__destroy(link_); + link_ = nullptr; + } } diff --git a/src/core/bpf_program.h b/src/core/bpf_program.h index f55b1c4..fdbea4d 100644 --- a/src/core/bpf_program.h +++ b/src/core/bpf_program.h @@ -16,7 +16,7 @@ class BpfProgram { std::string program_name_; public: - explicit BpfProgram(std::shared_ptr parent, struct bpf_program *raw_prog, std::string program_name = ""); + explicit BpfProgram(std::shared_ptr parent, struct bpf_program *raw_prog, const std::string& program_name); ~BpfProgram(); @@ -28,8 +28,6 @@ class BpfProgram { bool attach(); bool detach(); - void load_and_attach(); - [[nodiscard]] bool is_attached() const { return link_ != nullptr; } [[nodiscard]] std::string get_name() const { return program_name_; } }; From 54acc2c15dac6ec62f738db7e146fe1570f8e7ce Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sat, 18 Oct 2025 14:51:11 +0530 Subject: [PATCH 06/49] Redesign BpfMap --- src/core/bpf_map.h | 36 +++++++++++++----------------------- src/core/bpf_program.h | 1 - 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/src/core/bpf_map.h b/src/core/bpf_map.h index e6ad13a..550e25d 100644 --- a/src/core/bpf_map.h +++ b/src/core/bpf_map.h @@ -1,54 +1,44 @@ -#ifndef PYLIBBPF_MAPS_H -#define PYLIBBPF_MAPS_H +#ifndef PYLIBBPF_BPF_MAP_H +#define PYLIBBPF_BPF_MAP_H #include #include #include #include -#include "bpf_program.h" +class BpfObject; namespace py = pybind11; class BpfMap { private: + std::weak_ptr parent_obj_; struct bpf_map *map_; - int map_fd = -1; - //TODO: turn below into a shared pointer and ref count it so that there is no resource leakage - BpfProgram *bpf_program; + int map_fd_; + std::string map_name_; public: - BpfMap(BpfProgram *program_, const py::object &map_from_python); + BpfMap(std::shared_ptr, struct bpf_map *raw_map, const std::string &map_name); ~BpfMap() = default; [[nodiscard]] py::object lookup(const py::object &key) const; - void update(const py::object &key, const py::object &value) const; - void delete_elem(const py::object &key) const; - py::list get_next_key(const py::object &key = py::none()) const; - py::dict items() const; - py::list keys() const; - py::list values() const; - [[nodiscard]] std::string get_name() const; - - int get_type() const; - - int get_key_size() const; - - int get_value_size() const; - - int get_max_entries() const; + [[nodiscard]] std::string get_name() const { return map_name_; } + [[nodiscard]] int get_fd() const { return map_fd_; } + [[nodiscard]] int get_type() const; + [[nodiscard]] int get_key_size() const; + [[nodiscard]] int get_value_size() const; + [[nodiscard]] int get_max_entries() const; private: static std::vector python_to_bytes(const py::object &obj, size_t size); - static py::object bytes_to_python(const std::vector &data); }; diff --git a/src/core/bpf_program.h b/src/core/bpf_program.h index fdbea4d..dd0dce4 100644 --- a/src/core/bpf_program.h +++ b/src/core/bpf_program.h @@ -2,7 +2,6 @@ #define PYLIBBPF_BPF_PROGRAM_H #include -#include #include #include From c5a485b526ff4bba15d729208c6dca289e3c78df Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sat, 18 Oct 2025 20:59:31 +0530 Subject: [PATCH 07/49] Reimplement BpfMap --- src/core/bpf_map.cpp | 339 ++++++++++++++++++++++----------------- src/core/bpf_map.h | 38 ++++- src/core/bpf_program.cpp | 2 + 3 files changed, 223 insertions(+), 156 deletions(-) diff --git a/src/core/bpf_map.cpp b/src/core/bpf_map.cpp index 3e19aa0..b6f9069 100644 --- a/src/core/bpf_map.cpp +++ b/src/core/bpf_map.cpp @@ -1,211 +1,250 @@ #include "bpf_map.h" - +#include "bpf_object.h" #include "bpf_exception.h" -BpfMap::BpfMap(BpfProgram *program_, const py::object &map_from_python) { - if (py::isinstance(map_from_python)) { - const auto name = map_from_python.attr("__name__").cast(); - bpf_program = program_; - map_ = bpf_object__find_map_by_name(bpf_program->get_obj(), name.c_str()); - if (!map_) { - throw BpfException("Failed to find map by name"); - } - map_fd = bpf_map__fd(map_); - if (map_fd == -1) { - throw BpfException("Failed to open map File Descriptor"); - } - } else { - throw BpfException("Invalid map object passed to function."); - } +BpfMap::BpfMap(std::shared_ptr parent, struct bpf_map *raw_map, const std::string &map_name) + : parent_obj_(parent), + map_(raw_map), + map_fd_(-1), + map_name_(map_name), + key_size_(0), + value_size_(0) { + if (!parent) + throw BpfException("Parent BpfObject is null"); + if(!(parent->is_loaded())) + throw BpfException("Parent BpfObject is not loaded"); + if (!raw_map) + throw BpfException("bpf_map pointer is null"); + + map_fd_ = bpf_map__fd(map_); + if (map_fd_ < 0) + throw BpfException("Failed to get file descriptor for map '" + map_name_ + "'"); + + key_size_ = bpf_map__key_size(map_); + value_size_ = bpf_map__value_size(map_); } -std::vector BpfMap::python_to_bytes(const py::object &obj, size_t size) { - std::vector result(size, 0); - - if (py::isinstance(obj)) { - const auto value = obj.cast(); - std::memcpy(result.data(), &value, std::min(size, sizeof(uint64_t))); - } else if (py::isinstance(obj)) { - const auto bytes_str = obj.cast(); - std::memcpy(result.data(), bytes_str.data(), std::min(size, bytes_str.size())); - } else if (py::isinstance(obj)) { - const auto str_val = obj.cast(); - std::memcpy(result.data(), str_val.data(), std::min(size, str_val.size())); +py::object BpfMap::lookup(const py::object &key) const { + if (map_fd_ < 0) + throw BpfException("Map '" + map_name_ + "' is not initialized properly"); + + BufferManager<> key_buf, value_buf; + auto key_span = key_buf.get_span(key_size_); + auto value_span = value_buf.get_span(value_size_); + + // Convert Python → bytes + python_to_bytes_inplace(key, key_span); + + // The flags field here matters only when spin locks are used. + // Skipping it for now. + const int ret = bpf_map__lookup_elem( + map_, + key_span.data(), + key_size_, + value_span.data(), + value_size_, + BPF_ANY); + if (ret < 0) { + if (ret == -ENOENT) + throw py::key_error("Key not found in map '" + map_name_ + "'"); + throw BpfException( + "Failed to lookup key in map '" + map_name_ + "': " + + std::strerror(-ret) + ); } - return result; -} - -py::object BpfMap::bytes_to_python(const std::vector &data) { - // Try to interpret as integer if it's a common integer size - if (data.size() == 4) { - uint32_t value; - std::memcpy(&value, data.data(), 4); - return py::cast(value); - } else if (data.size() == 8) { - uint64_t value; - std::memcpy(&value, data.data(), 8); - return py::cast(value); - } else { - // Return as bytes - return py::bytes(reinterpret_cast(data.data()), data.size()); - } + return bytes_to_python(value_span); } void BpfMap::update(const py::object &key, const py::object &value) const { - const size_t key_size = bpf_map__key_size(map_); - const size_t value_size = bpf_map__value_size(map_); + if (map_fd_ < 0) + throw BpfException("Map '" + map_name_ + "' is not initialized properly"); + + BufferManager<> key_buf, value_buf; + auto key_span = key_buf.get_span(key_size_); + auto value_span = value_buf.get_span(value_size_); - const auto key_bytes = python_to_bytes(key, key_size); - const auto value_bytes = python_to_bytes(value, value_size); + python_to_bytes_inplace(key, key_span); + python_to_bytes_inplace(value, value_span); const int ret = bpf_map__update_elem( map_, - key_bytes.data(), - key_size, - value_bytes.data(), - value_size, + key_span.data(), + key_size_, + value_span.data(), + value_size_, BPF_ANY); - if (ret != 0) { - throw BpfException("Failed to update map element"); + if (ret < 0) { + throw BpfException( + "Failed to update key in map '" + map_name_ + "': " + + std::strerror(-ret) + ); } } void BpfMap::delete_elem(const py::object &key) const { - const size_t key_size = bpf_map__key_size(map_); - std::vector key_bytes; - key_bytes = python_to_bytes(key, key_size); + if (map_fd_ < 0) + throw BpfException("Map '" + map_name_ + "' is not initialized properly"); - if (const int ret = bpf_map__delete_elem(map_, key_bytes.data(), key_size, BPF_ANY); ret != 0) { - throw BpfException("Failed to delete map element"); + BufferManager<> key_buf; + auto key_span = key_buf.get_span(key_size_); + + // Convert Python → bytes + python_to_bytes_inplace(key, key_span); + + const int ret = bpf_map__delete_elem(map_, key_span.data(), key_size_, BPF_ANY); + + if (ret != 0) { + if (ret == -ENOENT) + throw py::key_error("Key not found in map '" + map_name_ + "'"); + throw BpfException( + "Failed to delete key from map '" + map_name_ + "': " + + std::strerror(-ret) + ); } } -py::list BpfMap::get_next_key(const py::object &key) const { - const size_t key_size = bpf_map__key_size(map_); - std::vector next_key(key_size); +py::object BpfMap::get_next_key(const py::object &key) const { + BufferManager<> next_key_buf; + auto next_key = next_key_buf.get_span(key_size_); int ret; if (key.is_none()) { - ret = bpf_map__get_next_key(map_, nullptr, next_key.data(), key_size); + ret = bpf_map__get_next_key(map_, nullptr, next_key.data(), key_size_); } else { - const auto key_bytes = python_to_bytes(key, key_size); - ret = bpf_map__get_next_key(map_, key_bytes.data(), next_key.data(), key_size); + BufferManager<> key_buf; + auto key_bytes = key_buf.get_span(key_size_); + python_to_bytes_inplace(key, key_bytes); + ret = bpf_map__get_next_key(map_, key_bytes.data(), next_key.data(), key_size_); } - py::list result; - if (ret == 0) { - result.append(bytes_to_python(next_key)); + if (ret < 0) { + if (ret == -ENOENT) { + // No more keys + return py::none(); + } + throw BpfException( + "Failed to get next key in map '" + map_name_ + "': " + + std::strerror(-ret) + ); } + + return bytes_to_python(next_key); +} + +py::dict BpfMap::items() const { + py::dict result; + + py::object current_key = get_next_key(py::none()); + if (current_key.is_none()) { + return result; + } + + while (!current_key.is_none()) { + try { + py::object value = lookup(current_key); + result[current_key] = value; + current_key = get_next_key(current_key); + } catch (const py::key_error&) { + break; + } + } + return result; } py::list BpfMap::keys() const { py::list result; - const size_t key_size = bpf_map__key_size(map_); - - std::vector key(key_size); - std::vector next_key(key_size); - - int ret = bpf_map__get_next_key(map_, nullptr, key.data(), key_size); - - while (ret == 0) { - result.append(bytes_to_python(key)); - ret = bpf_map__get_next_key(map_, key.data(), next_key.data(), key_size); - key = next_key; + + py::object current_key = get_next_key(py::none()); + if (current_key.is_none()) { + return result; } - + + while (!current_key.is_none()) { + result.append(current_key); + current_key = get_next_key(current_key); + } + return result; } py::list BpfMap::values() const { py::list result; - const size_t key_size = bpf_map__key_size(map_); - const size_t value_size = bpf_map__value_size(map_); - - std::vector key(key_size); - std::vector next_key(key_size); - std::vector value(value_size); - - int ret = bpf_map__get_next_key(map_, nullptr, key.data(), key_size); - - while (ret == 0) { - if (bpf_map__lookup_elem(map_, key.data(), key_size, value.data(), value_size, BPF_ANY) == 0) { - result.append(bytes_to_python(value)); + + py::object current_key = get_next_key(py::none()); + if (current_key.is_none()) { + return result; + } + + while (!current_key.is_none()) { + try { + py::object value = lookup(current_key); + result.append(value); + current_key = get_next_key(current_key); + } catch (const py::key_error&) { + break; } - ret = bpf_map__get_next_key(map_, key.data(), next_key.data(), key_size); - key = next_key; } - + return result; } -std::string BpfMap::get_name() const { - const char *name = bpf_map__name(map_); - return name ? std::string(name) : ""; -} - int BpfMap::get_type() const { return bpf_map__type(map_); } -int BpfMap::get_key_size() const { - return bpf_map__key_size(map_); -} - -int BpfMap::get_value_size() const { - return bpf_map__value_size(map_); -} - int BpfMap::get_max_entries() const { return bpf_map__max_entries(map_); } -py::dict BpfMap::items() const { - py::dict result; - const size_t key_size = bpf_map__key_size(map_); - const size_t value_size = bpf_map__value_size(map_); - - std::vector key(key_size); - std::vector next_key(key_size); - std::vector value(value_size); - // Get first key - int ret = bpf_map__get_next_key(map_, nullptr, key.data(), key_size); - - while (ret == 0) { - // Lookup value for current key - if (bpf_map__lookup_elem(map_, key.data(), key_size, value.data(), value_size, BPF_ANY) == 0) { - result[bytes_to_python(key)] = bytes_to_python(value); +// Helper functions +void BpfMap::python_to_bytes_inplace(const py::object &obj, std::span buffer) { + std::fill(buffer.begin(), buffer.end(), 0); + + if (py::isinstance(obj)) { + if (buffer.size() <= sizeof(uint64_t)) { + uint64_t value = obj.cast(); + std::memcpy(buffer.data(), &value, buffer.size()); + } else { + throw BpfException("Integer key/value size exceeds maximum (8 bytes)"); } - - // Get next key - ret = bpf_map__get_next_key(map_, key.data(), next_key.data(), key_size); - key = next_key; + } else if (py::isinstance(obj)) { + std::string bytes_str = obj.cast(); + + if (bytes_str.size() > buffer.size()) { + throw BpfException( + "Bytes size " + std::to_string(bytes_str.size()) + + " exceeds expected size " + std::to_string(buffer.size()) + ); + } + + std::memcpy(buffer.data(), bytes_str.data(), bytes_str.size()); + } else if (py::isinstance(obj)) { + std::string str_val = obj.cast(); + + if (str_val.size() >= buffer.size()) { + throw BpfException("String size exceeds expected size"); + } + + std::memcpy(buffer.data(), str_val.data(), str_val.size()); + buffer[str_val.size()] = '\0'; + } else { + throw BpfException("Unsupported type for BPF map key/value"); } - - return result; } -py::object BpfMap::lookup(const py::object &key) const { - const __u32 key_size = bpf_map__key_size(map_); - const __u32 value_size = bpf_map__value_size(map_); - - const auto key_bytes = python_to_bytes(key, key_size); - std::vector value_bytes(value_size); - - // The flags field here matters only when spin locks are used which is close to fucking never, so fuck no, - // im not adding it - const int ret = bpf_map__lookup_elem( - map_, - key_bytes.data(), - key_size, - value_bytes.data(), - value_size, - BPF_ANY); - if (ret != 0) { - return py::none(); +py::object BpfMap::bytes_to_python(std::span data) { + if (data.size() == 4) { + uint32_t value; + std::memcpy(&value, data.data(), 4); + return py::cast(value); + } else if (data.size() == 8) { + uint64_t value; + std::memcpy(&value, data.data(), 8); + return py::cast(value); + } else { + return py::bytes(reinterpret_cast(data.data()), data.size()); } - - return bytes_to_python(value_bytes); } diff --git a/src/core/bpf_map.h b/src/core/bpf_map.h index 550e25d..03302b5 100644 --- a/src/core/bpf_map.h +++ b/src/core/bpf_map.h @@ -5,6 +5,11 @@ #include #include #include +#include +#include +#include +#include +#include class BpfObject; @@ -16,16 +21,37 @@ class BpfMap { struct bpf_map *map_; int map_fd_; std::string map_name_; + __u32 key_size_, value_size_; + + template + struct BufferManager { + std::array stack_buf; + std::vector heap_buf; + + std::span get_span(size_t size) { + if (size <= StackSize) { + return std::span(stack_buf.data(), size); + } else { + heap_buf.resize(size); + return std::span(heap_buf); + } + } + }; public: - BpfMap(std::shared_ptr, struct bpf_map *raw_map, const std::string &map_name); + BpfMap(std::shared_ptr parent, struct bpf_map *raw_map, const std::string &map_name); ~BpfMap() = default; + BpfMap(const BpfMap&) = delete; + BpfMap& operator=(const BpfMap&) = delete; + BpfMap(BpfMap&&) noexcept = default; + BpfMap& operator=(BpfMap&&) noexcept = default; + [[nodiscard]] py::object lookup(const py::object &key) const; void update(const py::object &key, const py::object &value) const; void delete_elem(const py::object &key) const; - py::list get_next_key(const py::object &key = py::none()) const; + py::object get_next_key(const py::object &key = py::none()) const; py::dict items() const; py::list keys() const; py::list values() const; @@ -33,13 +59,13 @@ class BpfMap { [[nodiscard]] std::string get_name() const { return map_name_; } [[nodiscard]] int get_fd() const { return map_fd_; } [[nodiscard]] int get_type() const; - [[nodiscard]] int get_key_size() const; - [[nodiscard]] int get_value_size() const; + [[nodiscard]] int get_key_size() const { return key_size_; }; + [[nodiscard]] int get_value_size() const { return value_size_; }; [[nodiscard]] int get_max_entries() const; private: - static std::vector python_to_bytes(const py::object &obj, size_t size); - static py::object bytes_to_python(const std::vector &data); + static void python_to_bytes_inplace(const py::object &obj, std::span buffer); + static py::object bytes_to_python(std::span data); }; #endif //PYLIBBPF_MAPS_H diff --git a/src/core/bpf_program.cpp b/src/core/bpf_program.cpp index c606b5d..1d4202a 100644 --- a/src/core/bpf_program.cpp +++ b/src/core/bpf_program.cpp @@ -10,6 +10,8 @@ BpfProgram::BpfProgram(std::shared_ptr parent, struct bpf_program *ra program_name_(program_name) { if (!parent) throw BpfException("Parent BpfObject is null"); + if(!(parent->is_loaded())) + throw BpfException("Parent BpfObject is not loaded"); if (!raw_prog) throw BpfException("bpf_program pointer is null"); } From 4a5ff0c1c24c1a4ba1f48f426a5699e6061b43a0 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sat, 18 Oct 2025 21:00:16 +0530 Subject: [PATCH 08/49] Janitorial: clang-format --- src/core/bpf_map.cpp | 385 ++++++++++++++++----------------- src/core/bpf_map.h | 95 ++++---- src/core/bpf_object.cpp | 409 ++++++++++++++++++----------------- src/core/bpf_object.h | 103 ++++----- src/core/bpf_perf_buffer.cpp | 111 +++++----- src/core/bpf_perf_buffer.h | 24 +- src/core/bpf_program.cpp | 111 +++++----- src/core/bpf_program.h | 32 +-- 8 files changed, 628 insertions(+), 642 deletions(-) diff --git a/src/core/bpf_map.cpp b/src/core/bpf_map.cpp index b6f9069..2eafdec 100644 --- a/src/core/bpf_map.cpp +++ b/src/core/bpf_map.cpp @@ -1,250 +1,227 @@ #include "bpf_map.h" -#include "bpf_object.h" #include "bpf_exception.h" +#include "bpf_object.h" -BpfMap::BpfMap(std::shared_ptr parent, struct bpf_map *raw_map, const std::string &map_name) - : parent_obj_(parent), - map_(raw_map), - map_fd_(-1), - map_name_(map_name), - key_size_(0), - value_size_(0) { - if (!parent) - throw BpfException("Parent BpfObject is null"); - if(!(parent->is_loaded())) - throw BpfException("Parent BpfObject is not loaded"); - if (!raw_map) - throw BpfException("bpf_map pointer is null"); - - map_fd_ = bpf_map__fd(map_); - if (map_fd_ < 0) - throw BpfException("Failed to get file descriptor for map '" + map_name_ + "'"); - - key_size_ = bpf_map__key_size(map_); - value_size_ = bpf_map__value_size(map_); +BpfMap::BpfMap(std::shared_ptr parent, struct bpf_map *raw_map, + const std::string &map_name) + : parent_obj_(parent), map_(raw_map), map_fd_(-1), map_name_(map_name), + key_size_(0), value_size_(0) { + if (!parent) + throw BpfException("Parent BpfObject is null"); + if (!(parent->is_loaded())) + throw BpfException("Parent BpfObject is not loaded"); + if (!raw_map) + throw BpfException("bpf_map pointer is null"); + + map_fd_ = bpf_map__fd(map_); + if (map_fd_ < 0) + throw BpfException("Failed to get file descriptor for map '" + map_name_ + + "'"); + + key_size_ = bpf_map__key_size(map_); + value_size_ = bpf_map__value_size(map_); } py::object BpfMap::lookup(const py::object &key) const { - if (map_fd_ < 0) - throw BpfException("Map '" + map_name_ + "' is not initialized properly"); - - BufferManager<> key_buf, value_buf; - auto key_span = key_buf.get_span(key_size_); - auto value_span = value_buf.get_span(value_size_); - - // Convert Python → bytes - python_to_bytes_inplace(key, key_span); - - // The flags field here matters only when spin locks are used. - // Skipping it for now. - const int ret = bpf_map__lookup_elem( - map_, - key_span.data(), - key_size_, - value_span.data(), - value_size_, - BPF_ANY); - if (ret < 0) { - if (ret == -ENOENT) - throw py::key_error("Key not found in map '" + map_name_ + "'"); - throw BpfException( - "Failed to lookup key in map '" + map_name_ + "': " + - std::strerror(-ret) - ); - } - - return bytes_to_python(value_span); + if (map_fd_ < 0) + throw BpfException("Map '" + map_name_ + "' is not initialized properly"); + + BufferManager<> key_buf, value_buf; + auto key_span = key_buf.get_span(key_size_); + auto value_span = value_buf.get_span(value_size_); + + // Convert Python → bytes + python_to_bytes_inplace(key, key_span); + + // The flags field here matters only when spin locks are used. + // Skipping it for now. + const int ret = bpf_map__lookup_elem(map_, key_span.data(), key_size_, + value_span.data(), value_size_, BPF_ANY); + if (ret < 0) { + if (ret == -ENOENT) + throw py::key_error("Key not found in map '" + map_name_ + "'"); + throw BpfException("Failed to lookup key in map '" + map_name_ + + "': " + std::strerror(-ret)); + } + + return bytes_to_python(value_span); } void BpfMap::update(const py::object &key, const py::object &value) const { - if (map_fd_ < 0) - throw BpfException("Map '" + map_name_ + "' is not initialized properly"); - - BufferManager<> key_buf, value_buf; - auto key_span = key_buf.get_span(key_size_); - auto value_span = value_buf.get_span(value_size_); - - python_to_bytes_inplace(key, key_span); - python_to_bytes_inplace(value, value_span); - - const int ret = bpf_map__update_elem( - map_, - key_span.data(), - key_size_, - value_span.data(), - value_size_, - BPF_ANY); - if (ret < 0) { - throw BpfException( - "Failed to update key in map '" + map_name_ + "': " + - std::strerror(-ret) - ); - } + if (map_fd_ < 0) + throw BpfException("Map '" + map_name_ + "' is not initialized properly"); + + BufferManager<> key_buf, value_buf; + auto key_span = key_buf.get_span(key_size_); + auto value_span = value_buf.get_span(value_size_); + + python_to_bytes_inplace(key, key_span); + python_to_bytes_inplace(value, value_span); + + const int ret = bpf_map__update_elem(map_, key_span.data(), key_size_, + value_span.data(), value_size_, BPF_ANY); + if (ret < 0) { + throw BpfException("Failed to update key in map '" + map_name_ + + "': " + std::strerror(-ret)); + } } void BpfMap::delete_elem(const py::object &key) const { - if (map_fd_ < 0) - throw BpfException("Map '" + map_name_ + "' is not initialized properly"); + if (map_fd_ < 0) + throw BpfException("Map '" + map_name_ + "' is not initialized properly"); - BufferManager<> key_buf; - auto key_span = key_buf.get_span(key_size_); + BufferManager<> key_buf; + auto key_span = key_buf.get_span(key_size_); - // Convert Python → bytes - python_to_bytes_inplace(key, key_span); + // Convert Python → bytes + python_to_bytes_inplace(key, key_span); - const int ret = bpf_map__delete_elem(map_, key_span.data(), key_size_, BPF_ANY); + const int ret = + bpf_map__delete_elem(map_, key_span.data(), key_size_, BPF_ANY); - if (ret != 0) { - if (ret == -ENOENT) - throw py::key_error("Key not found in map '" + map_name_ + "'"); - throw BpfException( - "Failed to delete key from map '" + map_name_ + "': " + - std::strerror(-ret) - ); - } + if (ret != 0) { + if (ret == -ENOENT) + throw py::key_error("Key not found in map '" + map_name_ + "'"); + throw BpfException("Failed to delete key from map '" + map_name_ + + "': " + std::strerror(-ret)); + } } py::object BpfMap::get_next_key(const py::object &key) const { - BufferManager<> next_key_buf; - auto next_key = next_key_buf.get_span(key_size_); - - int ret; - if (key.is_none()) { - ret = bpf_map__get_next_key(map_, nullptr, next_key.data(), key_size_); - } else { - BufferManager<> key_buf; - auto key_bytes = key_buf.get_span(key_size_); - python_to_bytes_inplace(key, key_bytes); - ret = bpf_map__get_next_key(map_, key_bytes.data(), next_key.data(), key_size_); - } + BufferManager<> next_key_buf; + auto next_key = next_key_buf.get_span(key_size_); - if (ret < 0) { - if (ret == -ENOENT) { - // No more keys - return py::none(); - } - throw BpfException( - "Failed to get next key in map '" + map_name_ + "': " + - std::strerror(-ret) - ); + int ret; + if (key.is_none()) { + ret = bpf_map__get_next_key(map_, nullptr, next_key.data(), key_size_); + } else { + BufferManager<> key_buf; + auto key_bytes = key_buf.get_span(key_size_); + python_to_bytes_inplace(key, key_bytes); + ret = bpf_map__get_next_key(map_, key_bytes.data(), next_key.data(), + key_size_); + } + + if (ret < 0) { + if (ret == -ENOENT) { + // No more keys + return py::none(); } + throw BpfException("Failed to get next key in map '" + map_name_ + + "': " + std::strerror(-ret)); + } - return bytes_to_python(next_key); + return bytes_to_python(next_key); } py::dict BpfMap::items() const { - py::dict result; - - py::object current_key = get_next_key(py::none()); - if (current_key.is_none()) { - return result; - } - - while (!current_key.is_none()) { - try { - py::object value = lookup(current_key); - result[current_key] = value; - current_key = get_next_key(current_key); - } catch (const py::key_error&) { - break; - } - } - + py::dict result; + + py::object current_key = get_next_key(py::none()); + if (current_key.is_none()) { return result; + } + + while (!current_key.is_none()) { + try { + py::object value = lookup(current_key); + result[current_key] = value; + current_key = get_next_key(current_key); + } catch (const py::key_error &) { + break; + } + } + + return result; } py::list BpfMap::keys() const { - py::list result; - - py::object current_key = get_next_key(py::none()); - if (current_key.is_none()) { - return result; - } - - while (!current_key.is_none()) { - result.append(current_key); - current_key = get_next_key(current_key); - } - + py::list result; + + py::object current_key = get_next_key(py::none()); + if (current_key.is_none()) { return result; + } + + while (!current_key.is_none()) { + result.append(current_key); + current_key = get_next_key(current_key); + } + + return result; } py::list BpfMap::values() const { - py::list result; - - py::object current_key = get_next_key(py::none()); - if (current_key.is_none()) { - return result; - } - - while (!current_key.is_none()) { - try { - py::object value = lookup(current_key); - result.append(value); - current_key = get_next_key(current_key); - } catch (const py::key_error&) { - break; - } - } - + py::list result; + + py::object current_key = get_next_key(py::none()); + if (current_key.is_none()) { return result; -} + } + + while (!current_key.is_none()) { + try { + py::object value = lookup(current_key); + result.append(value); + current_key = get_next_key(current_key); + } catch (const py::key_error &) { + break; + } + } -int BpfMap::get_type() const { - return bpf_map__type(map_); + return result; } -int BpfMap::get_max_entries() const { - return bpf_map__max_entries(map_); -} +int BpfMap::get_type() const { return bpf_map__type(map_); } +int BpfMap::get_max_entries() const { return bpf_map__max_entries(map_); } // Helper functions -void BpfMap::python_to_bytes_inplace(const py::object &obj, std::span buffer) { - std::fill(buffer.begin(), buffer.end(), 0); - - if (py::isinstance(obj)) { - if (buffer.size() <= sizeof(uint64_t)) { - uint64_t value = obj.cast(); - std::memcpy(buffer.data(), &value, buffer.size()); - } else { - throw BpfException("Integer key/value size exceeds maximum (8 bytes)"); - } - } else if (py::isinstance(obj)) { - std::string bytes_str = obj.cast(); - - if (bytes_str.size() > buffer.size()) { - throw BpfException( - "Bytes size " + std::to_string(bytes_str.size()) + - " exceeds expected size " + std::to_string(buffer.size()) - ); - } - - std::memcpy(buffer.data(), bytes_str.data(), bytes_str.size()); - } else if (py::isinstance(obj)) { - std::string str_val = obj.cast(); - - if (str_val.size() >= buffer.size()) { - throw BpfException("String size exceeds expected size"); - } - - std::memcpy(buffer.data(), str_val.data(), str_val.size()); - buffer[str_val.size()] = '\0'; +void BpfMap::python_to_bytes_inplace(const py::object &obj, + std::span buffer) { + std::fill(buffer.begin(), buffer.end(), 0); + + if (py::isinstance(obj)) { + if (buffer.size() <= sizeof(uint64_t)) { + uint64_t value = obj.cast(); + std::memcpy(buffer.data(), &value, buffer.size()); } else { - throw BpfException("Unsupported type for BPF map key/value"); + throw BpfException("Integer key/value size exceeds maximum (8 bytes)"); } + } else if (py::isinstance(obj)) { + std::string bytes_str = obj.cast(); + + if (bytes_str.size() > buffer.size()) { + throw BpfException("Bytes size " + std::to_string(bytes_str.size()) + + " exceeds expected size " + + std::to_string(buffer.size())); + } + + std::memcpy(buffer.data(), bytes_str.data(), bytes_str.size()); + } else if (py::isinstance(obj)) { + std::string str_val = obj.cast(); + + if (str_val.size() >= buffer.size()) { + throw BpfException("String size exceeds expected size"); + } + + std::memcpy(buffer.data(), str_val.data(), str_val.size()); + buffer[str_val.size()] = '\0'; + } else { + throw BpfException("Unsupported type for BPF map key/value"); + } } py::object BpfMap::bytes_to_python(std::span data) { - if (data.size() == 4) { - uint32_t value; - std::memcpy(&value, data.data(), 4); - return py::cast(value); - } else if (data.size() == 8) { - uint64_t value; - std::memcpy(&value, data.data(), 8); - return py::cast(value); - } else { - return py::bytes(reinterpret_cast(data.data()), data.size()); - } + if (data.size() == 4) { + uint32_t value; + std::memcpy(&value, data.data(), 4); + return py::cast(value); + } else if (data.size() == 8) { + uint64_t value; + std::memcpy(&value, data.data(), 8); + return py::cast(value); + } else { + return py::bytes(reinterpret_cast(data.data()), data.size()); + } } diff --git a/src/core/bpf_map.h b/src/core/bpf_map.h index 03302b5..a7c2008 100644 --- a/src/core/bpf_map.h +++ b/src/core/bpf_map.h @@ -1,15 +1,15 @@ #ifndef PYLIBBPF_BPF_MAP_H #define PYLIBBPF_BPF_MAP_H +#include +#include +#include +#include #include #include -#include -#include #include -#include -#include -#include -#include +#include +#include class BpfObject; @@ -17,55 +17,56 @@ namespace py = pybind11; class BpfMap { private: - std::weak_ptr parent_obj_; - struct bpf_map *map_; - int map_fd_; - std::string map_name_; - __u32 key_size_, value_size_; + std::weak_ptr parent_obj_; + struct bpf_map *map_; + int map_fd_; + std::string map_name_; + __u32 key_size_, value_size_; + + template struct BufferManager { + std::array stack_buf; + std::vector heap_buf; - template - struct BufferManager { - std::array stack_buf; - std::vector heap_buf; - - std::span get_span(size_t size) { - if (size <= StackSize) { - return std::span(stack_buf.data(), size); - } else { - heap_buf.resize(size); - return std::span(heap_buf); - } - } - }; + std::span get_span(size_t size) { + if (size <= StackSize) { + return std::span(stack_buf.data(), size); + } else { + heap_buf.resize(size); + return std::span(heap_buf); + } + } + }; public: - BpfMap(std::shared_ptr parent, struct bpf_map *raw_map, const std::string &map_name); + BpfMap(std::shared_ptr parent, struct bpf_map *raw_map, + const std::string &map_name); - ~BpfMap() = default; + ~BpfMap() = default; - BpfMap(const BpfMap&) = delete; - BpfMap& operator=(const BpfMap&) = delete; - BpfMap(BpfMap&&) noexcept = default; - BpfMap& operator=(BpfMap&&) noexcept = default; + BpfMap(const BpfMap &) = delete; + BpfMap &operator=(const BpfMap &) = delete; + BpfMap(BpfMap &&) noexcept = default; + BpfMap &operator=(BpfMap &&) noexcept = default; - [[nodiscard]] py::object lookup(const py::object &key) const; - void update(const py::object &key, const py::object &value) const; - void delete_elem(const py::object &key) const; - py::object get_next_key(const py::object &key = py::none()) const; - py::dict items() const; - py::list keys() const; - py::list values() const; + [[nodiscard]] py::object lookup(const py::object &key) const; + void update(const py::object &key, const py::object &value) const; + void delete_elem(const py::object &key) const; + py::object get_next_key(const py::object &key = py::none()) const; + py::dict items() const; + py::list keys() const; + py::list values() const; - [[nodiscard]] std::string get_name() const { return map_name_; } - [[nodiscard]] int get_fd() const { return map_fd_; } - [[nodiscard]] int get_type() const; - [[nodiscard]] int get_key_size() const { return key_size_; }; - [[nodiscard]] int get_value_size() const { return value_size_; }; - [[nodiscard]] int get_max_entries() const; + [[nodiscard]] std::string get_name() const { return map_name_; } + [[nodiscard]] int get_fd() const { return map_fd_; } + [[nodiscard]] int get_type() const; + [[nodiscard]] int get_key_size() const { return key_size_; }; + [[nodiscard]] int get_value_size() const { return value_size_; }; + [[nodiscard]] int get_max_entries() const; private: - static void python_to_bytes_inplace(const py::object &obj, std::span buffer); - static py::object bytes_to_python(std::span data); + static void python_to_bytes_inplace(const py::object &obj, + std::span buffer); + static py::object bytes_to_python(std::span data); }; -#endif //PYLIBBPF_MAPS_H +#endif // PYLIBBPF_MAPS_H diff --git a/src/core/bpf_object.cpp b/src/core/bpf_object.cpp index babaa92..660d1b5 100644 --- a/src/core/bpf_object.cpp +++ b/src/core/bpf_object.cpp @@ -1,255 +1,256 @@ #include "bpf_object.h" -#include "bpf_program.h" -#include "bpf_map.h" #include "bpf_exception.h" +#include "bpf_map.h" +#include "bpf_program.h" #include BpfObject::BpfObject(std::string object_path) - : obj_(nullptr), object_path_(std::move(object_path)), loaded_(false) { -} + : obj_(nullptr), object_path_(std::move(object_path)), loaded_(false) {} BpfObject::~BpfObject() { - // Clear caches first (order matters!) - prog_cache_.clear(); // Detaches programs - maps_cache_.clear(); // Closes maps - - // Then close object - if (obj_) { - bpf_object__close(obj_); - obj_ = nullptr; - } + // Clear caches first (order matters!) + prog_cache_.clear(); // Detaches programs + maps_cache_.clear(); // Closes maps + + // Then close object + if (obj_) { + bpf_object__close(obj_); + obj_ = nullptr; + } } -BpfObject::BpfObject(BpfObject&& other) noexcept - : obj_(other.obj_), - object_path_(std::move(other.object_path_)), - loaded_(other.loaded_), - prog_cache_(std::move(other.prog_cache_)), +BpfObject::BpfObject(BpfObject &&other) noexcept + : obj_(other.obj_), object_path_(std::move(other.object_path_)), + loaded_(other.loaded_), prog_cache_(std::move(other.prog_cache_)), maps_cache_(std::move(other.maps_cache_)) { - - other.obj_ = nullptr; - other.loaded_ = false; + + other.obj_ = nullptr; + other.loaded_ = false; } -BpfObject& BpfObject::operator=(BpfObject&& other) noexcept { - if (this != &other) { - prog_cache_.clear(); - maps_cache_.clear(); - if (obj_) { - bpf_object__close(obj_); - } - - obj_ = other.obj_; - object_path_ = std::move(other.object_path_); - loaded_ = other.loaded_; - prog_cache_ = std::move(other.prog_cache_); - maps_cache_ = std::move(other.maps_cache_); - - other.obj_ = nullptr; - other.loaded_ = false; +BpfObject &BpfObject::operator=(BpfObject &&other) noexcept { + if (this != &other) { + prog_cache_.clear(); + maps_cache_.clear(); + if (obj_) { + bpf_object__close(obj_); } - return *this; + + obj_ = other.obj_; + object_path_ = std::move(other.object_path_); + loaded_ = other.loaded_; + prog_cache_ = std::move(other.prog_cache_); + maps_cache_ = std::move(other.maps_cache_); + + other.obj_ = nullptr; + other.loaded_ = false; + } + return *this; } void BpfObject::load() { - if (loaded_) { - throw BpfException("BPF object already loaded"); - } - - std::string error_msg = "Failed to open BPF object"; - obj_ = bpf_object__open_file(object_path_.c_str(), nullptr); + if (loaded_) { + throw BpfException("BPF object already loaded"); + } - if (!obj_) { - error_msg += " file '" + object_path_ + "': " + std::strerror(errno); - throw BpfException(error_msg); - } - - if (bpf_object__load(obj_)) { - error_msg += " object from file '" + object_path_ + "': " + std::strerror(errno); - bpf_object__close(obj_); - obj_ = nullptr; - throw BpfException(error_msg); - } - - loaded_ = true; + std::string error_msg = "Failed to open BPF object"; + obj_ = bpf_object__open_file(object_path_.c_str(), nullptr); + + if (!obj_) { + error_msg += " file '" + object_path_ + "': " + std::strerror(errno); + throw BpfException(error_msg); + } + + if (bpf_object__load(obj_)) { + error_msg += + " object from file '" + object_path_ + "': " + std::strerror(errno); + bpf_object__close(obj_); + obj_ = nullptr; + throw BpfException(error_msg); + } + + loaded_ = true; } // ==================== Program Methods ==================== py::list BpfObject::get_program_names() const { - if (!loaded_) { - throw BpfException("BPF object not loaded"); - } - - py::list names; - struct bpf_program *prog = nullptr; - - bpf_object__for_each_program(prog, obj_) { - _get_or_create_program(prog); // Ensure cached - names.append(bpf_program__name(prog)); - } - - return names; + if (!loaded_) { + throw BpfException("BPF object not loaded"); + } + + py::list names; + struct bpf_program *prog = nullptr; + + bpf_object__for_each_program(prog, obj_) { + _get_or_create_program(prog); // Ensure cached + names.append(bpf_program__name(prog)); + } + + return names; } -std::shared_ptr BpfObject::_get_or_create_program(struct bpf_program *prog) { - if (!prog) { - throw BpfException("bpf_program pointer is null"); - } - - const char *name = bpf_program__name(prog); - std::string prog_name(name ? name : ""); - - // Check cache - auto it = prog_cache_.find(prog_name); - if (it != prog_cache_.end()) { - return it->second; - } - - // Create and cache - auto bpf_prog = std::make_shared(this, prog, prog_name); - prog_cache_[prog_name] = bpf_prog; - - return bpf_prog; +std::shared_ptr +BpfObject::_get_or_create_program(struct bpf_program *prog) { + if (!prog) { + throw BpfException("bpf_program pointer is null"); + } + + const char *name = bpf_program__name(prog); + std::string prog_name(name ? name : ""); + + // Check cache + auto it = prog_cache_.find(prog_name); + if (it != prog_cache_.end()) { + return it->second; + } + + // Create and cache + auto bpf_prog = std::make_shared(this, prog, prog_name); + prog_cache_[prog_name] = bpf_prog; + + return bpf_prog; } -std::shared_ptr BpfObject::get_program(const std::string& name) { - if (!loaded_) { - throw BpfException("BPF object not loaded"); - } - - // Check cache - auto it = prog_cache_.find(name); - if (it != prog_cache_.end()) { - return it->second; - } - - // Create and cache - struct bpf_program *raw_prog = find_program_by_name(name); - auto prog = std::make_shared(this, raw_prog, name); - prog_cache_[name] = prog; - - return prog; +std::shared_ptr BpfObject::get_program(const std::string &name) { + if (!loaded_) { + throw BpfException("BPF object not loaded"); + } + + // Check cache + auto it = prog_cache_.find(name); + if (it != prog_cache_.end()) { + return it->second; + } + + // Create and cache + struct bpf_program *raw_prog = find_program_by_name(name); + auto prog = std::make_shared(this, raw_prog, name); + prog_cache_[name] = prog; + + return prog; } -struct bpf_program* BpfObject::find_program_by_name(const std::string& name) const { - if (!loaded_) { - throw BpfException("BPF object not loaded"); - } - - struct bpf_program *prog = bpf_object__find_program_by_name(obj_, name.c_str()); - if (!prog) { - throw BpfException("Program '" + name + "' not found"); - } - - return prog; +struct bpf_program * +BpfObject::find_program_by_name(const std::string &name) const { + if (!loaded_) { + throw BpfException("BPF object not loaded"); + } + + struct bpf_program *prog = + bpf_object__find_program_by_name(obj_, name.c_str()); + if (!prog) { + throw BpfException("Program '" + name + "' not found"); + } + + return prog; } py::dict BpfObject::get_cached_programs() const { - py::dict programs; - for (const auto& [name, prog] : prog_cache_) { - programs[name] = prog; - } - return programs; + py::dict programs; + for (const auto &[name, prog] : prog_cache_) { + programs[name] = prog; + } + return programs; } py::dict BpfObject::attach_all() { - if (!loaded_) { - throw BpfException("BPF object not loaded"); - } - - py::dict attached_programs; - struct bpf_program *prog = nullptr; - - bpf_object__for_each_program(prog, obj_) { - auto bpf_prog = _get_or_create_program(prog); - - if (!bpf_prog->is_attached()) { - bpf_prog->attach(); - } - - const char *name = bpf_program__name(prog); - attached_programs[name] = bpf_prog; + if (!loaded_) { + throw BpfException("BPF object not loaded"); + } + + py::dict attached_programs; + struct bpf_program *prog = nullptr; + + bpf_object__for_each_program(prog, obj_) { + auto bpf_prog = _get_or_create_program(prog); + + if (!bpf_prog->is_attached()) { + bpf_prog->attach(); } - - return attached_programs; + + const char *name = bpf_program__name(prog); + attached_programs[name] = bpf_prog; + } + + return attached_programs; } // ==================== Map Methods ==================== py::list BpfObject::get_map_names() const { - if (!loaded_) { - throw BpfException("BPF object not loaded"); - } - - py::list names; - struct bpf_map *map = nullptr; - - bpf_object__for_each_map(map, obj_) { - _get_or_create_map(map); // Ensure cached - names.append(bpf_map__name(map)); - } - - return names; + if (!loaded_) { + throw BpfException("BPF object not loaded"); + } + + py::list names; + struct bpf_map *map = nullptr; + + bpf_object__for_each_map(map, obj_) { + _get_or_create_map(map); // Ensure cached + names.append(bpf_map__name(map)); + } + + return names; } -std::shared_ptr BpfObject::get_map(const std::string& name) { - if (!loaded_) { - throw BpfException("BPF object not loaded"); - } - - // Check cache - auto it = maps_cache_.find(name); - if (it != maps_cache_.end()) { - return it->second; - } - - // Create and cache - struct bpf_map *raw_map = find_map_by_name(name); - auto map = std::make_shared(shared_from_this(), raw_map, name); - maps_cache_[name] = map; - - return map; +std::shared_ptr BpfObject::get_map(const std::string &name) { + if (!loaded_) { + throw BpfException("BPF object not loaded"); + } + + // Check cache + auto it = maps_cache_.find(name); + if (it != maps_cache_.end()) { + return it->second; + } + + // Create and cache + struct bpf_map *raw_map = find_map_by_name(name); + auto map = std::make_shared(shared_from_this(), raw_map, name); + maps_cache_[name] = map; + + return map; } std::shared_ptr BpfObject::_get_or_create_map(struct bpf_map *map) { - if (!map) { - throw BpfException("bpf_map pointer is null"); - } - - const char *name = bpf_map__name(map); - std::string map_name(name ? name : ""); - - // Check cache - auto it = maps_cache_.find(map_name); - if (it != maps_cache_.end()) { - return it->second; - } - - // Create and cache - auto bpf_map = std::make_shared(shared_from_this(), map, map_name); - maps_cache_[map_name] = bpf_map; - - return bpf_map; + if (!map) { + throw BpfException("bpf_map pointer is null"); + } + + const char *name = bpf_map__name(map); + std::string map_name(name ? name : ""); + + // Check cache + auto it = maps_cache_.find(map_name); + if (it != maps_cache_.end()) { + return it->second; + } + + // Create and cache + auto bpf_map = std::make_shared(shared_from_this(), map, map_name); + maps_cache_[map_name] = bpf_map; + + return bpf_map; } -struct bpf_map* BpfObject::find_map_by_name(const std::string& name) const { - if (!loaded_) { - throw BpfException("BPF object not loaded"); - } - - struct bpf_map *map = bpf_object__find_map_by_name(obj_, name.c_str()); - if (!map) { - throw BpfException("Map '" + name + "' not found"); - } - - return map; +struct bpf_map *BpfObject::find_map_by_name(const std::string &name) const { + if (!loaded_) { + throw BpfException("BPF object not loaded"); + } + + struct bpf_map *map = bpf_object__find_map_by_name(obj_, name.c_str()); + if (!map) { + throw BpfException("Map '" + name + "' not found"); + } + + return map; } py::dict BpfObject::get_cached_maps() const { - py::dict maps; - for (const auto& [name, map] : maps_cache_) { - maps[name] = map; - } - return maps; + py::dict maps; + for (const auto &[name, map] : maps_cache_) { + maps[name] = map; + } + return maps; } diff --git a/src/core/bpf_object.h b/src/core/bpf_object.h index a82474d..43ba7a2 100644 --- a/src/core/bpf_object.h +++ b/src/core/bpf_object.h @@ -2,8 +2,8 @@ #define PYLIBBPF_BPF_OBJECT_H #include -#include #include +#include #include #include #include @@ -15,65 +15,68 @@ class BpfMap; /** * BpfObject - Represents a loaded BPF object file. - * + * * This is the main entry point for loading BPF programs. * Owns the bpf_object* and manages all programs and maps within it. */ class BpfObject : public std::enable_shared_from_this { private: - struct bpf_object *obj_; - std::string object_path_; - bool loaded_; + struct bpf_object *obj_; + std::string object_path_; + bool loaded_; - mutable std::unordered_map> maps_cache_; - mutable std::unordered_map> prog_cache_; + mutable std::unordered_map> maps_cache_; + mutable std::unordered_map> + prog_cache_; - std::shared_ptr _get_or_create_program(struct bpf_program *prog); - std::shared_ptr _get_or_create_map(struct bpf_map *map); + std::shared_ptr _get_or_create_program(struct bpf_program *prog); + std::shared_ptr _get_or_create_map(struct bpf_map *map); public: - explicit BpfObject(std::string object_path); - ~BpfObject(); - - // Disable copy, allow move - BpfObject(const BpfObject&) = delete; - BpfObject& operator=(const BpfObject&) = delete; - BpfObject(BpfObject&&) noexcept; - BpfObject& operator=(BpfObject&&) noexcept; - - /** - * Load the BPF object into the kernel. - * Must be called before accessing programs or maps. - */ - void load(); - - /** - * Check if object is loaded. - */ - [[nodiscard]] bool is_loaded() const { return loaded_; } - - /** - * Get the underlying bpf_object pointer. - * Only for internal use by BpfProgram and BpfMap. - */ - [[nodiscard]] struct bpf_object* get_obj() const { return obj_; } + explicit BpfObject(std::string object_path); + ~BpfObject(); + + // Disable copy, allow move + BpfObject(const BpfObject &) = delete; + BpfObject &operator=(const BpfObject &) = delete; + BpfObject(BpfObject &&) noexcept; + BpfObject &operator=(BpfObject &&) noexcept; + + /** + * Load the BPF object into the kernel. + * Must be called before accessing programs or maps. + */ + void load(); + + /** + * Check if object is loaded. + */ + [[nodiscard]] bool is_loaded() const { return loaded_; } + + /** + * Get the underlying bpf_object pointer. + * Only for internal use by BpfProgram and BpfMap. + */ + [[nodiscard]] struct bpf_object *get_obj() const { return obj_; } + + /** + * Attach all programs in the object. + */ + py::dict attach_all(); + + // Program access + [[nodiscard]] py::list get_program_names() const; + [[nodiscard]] std::shared_ptr + get_program(const std::string &name); + [[nodiscard]] struct bpf_program * + find_program_by_name(const std::string &name) const; + [[nodiscard]] py::dict get_cached_programs() const; - /** - * Attach all programs in the object. - */ - py::dict attach_all(); - - // Program access - [[nodiscard]] py::list get_program_names() const; - [[nodiscard]] std::shared_ptr get_program(const std::string& name); - [[nodiscard]] struct bpf_program* find_program_by_name(const std::string& name) const; - [[nodiscard]] py::dict get_cached_programs() const; - - // Map access - [[nodiscard]] py::list get_map_names() const; - [[nodiscard]] std::shared_ptr 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; + // Map access + [[nodiscard]] py::list get_map_names() const; + [[nodiscard]] std::shared_ptr 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; }; #endif // PYLIBBPF_BPF_OBJECT_H diff --git a/src/core/bpf_perf_buffer.cpp b/src/core/bpf_perf_buffer.cpp index e0f56f9..a1e7276 100644 --- a/src/core/bpf_perf_buffer.cpp +++ b/src/core/bpf_perf_buffer.cpp @@ -1,72 +1,75 @@ #include "bpf_perf_buffer.h" #include "bpf_exception.h" -void BpfPerfBuffer::sample_callback_wrapper(void *ctx, int cpu, void *data, unsigned int size) { - auto *self = static_cast(ctx); - - // Acquire GIL for Python calls - py::gil_scoped_acquire acquire; - - try { - // Convert data to Python bytes - py::bytes py_data(static_cast(data), size); - - // Call Python callback: callback(cpu, data, size) - self->callback_(cpu, py_data, size); - } catch (const py::error_already_set &e) { - PyErr_Print(); - } +void BpfPerfBuffer::sample_callback_wrapper(void *ctx, int cpu, void *data, + unsigned int size) { + auto *self = static_cast(ctx); + + // Acquire GIL for Python calls + py::gil_scoped_acquire acquire; + + try { + // Convert data to Python bytes + py::bytes py_data(static_cast(data), size); + + // Call Python callback: callback(cpu, data, size) + self->callback_(cpu, py_data, size); + } catch (const py::error_already_set &e) { + PyErr_Print(); + } } -void BpfPerfBuffer::lost_callback_wrapper(void *ctx, int cpu, unsigned long long cnt) { - auto *self = static_cast(ctx); - - if (self->lost_callback_.is_none()) { - return; - } - - py::gil_scoped_acquire acquire; - - try { - self->lost_callback_(cpu, cnt); - } catch (const py::error_already_set &e) { - PyErr_Print(); - } +void BpfPerfBuffer::lost_callback_wrapper(void *ctx, int cpu, + unsigned long long cnt) { + auto *self = static_cast(ctx); + + if (self->lost_callback_.is_none()) { + return; + } + + py::gil_scoped_acquire acquire; + + try { + self->lost_callback_(cpu, cnt); + } catch (const py::error_already_set &e) { + PyErr_Print(); + } } -BpfPerfBuffer::BpfPerfBuffer(int map_fd, int page_cnt, py::function callback, py::object lost_callback) +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(); - } - - // 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"); - } + + if (!lost_callback.is_none()) { + lost_callback_ = lost_callback.cast(); + } + + // 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_); - } + if (pb_) { + perf_buffer__free(pb_); + } } int BpfPerfBuffer::poll(int timeout_ms) { - // Release GIL during blocking poll - py::gil_scoped_release release; - return perf_buffer__poll(pb_, timeout_ms); + // Release GIL during blocking poll + py::gil_scoped_release release; + return perf_buffer__poll(pb_, timeout_ms); } int BpfPerfBuffer::consume() { - py::gil_scoped_release release; - return perf_buffer__consume(pb_); + py::gil_scoped_release release; + return perf_buffer__consume(pb_); } diff --git a/src/core/bpf_perf_buffer.h b/src/core/bpf_perf_buffer.h index 23b7db8..3794a2d 100644 --- a/src/core/bpf_perf_buffer.h +++ b/src/core/bpf_perf_buffer.h @@ -2,27 +2,29 @@ #define PYLIBBPF_BPF_PERF_BUFFER_H #include -#include #include +#include namespace py = pybind11; class BpfPerfBuffer { private: - struct perf_buffer *pb_; - py::function callback_; - py::function lost_callback_; + struct perf_buffer *pb_; + py::function callback_; + py::function lost_callback_; - // Static callback wrappers for C API - static void sample_callback_wrapper(void *ctx, int cpu, void *data, unsigned int size); - static void lost_callback_wrapper(void *ctx, int cpu, unsigned long long cnt); + // Static callback wrappers for C API + static void sample_callback_wrapper(void *ctx, int cpu, void *data, + unsigned int size); + static void lost_callback_wrapper(void *ctx, int cpu, unsigned long long cnt); public: - BpfPerfBuffer(int map_fd, int page_cnt, py::function callback, py::object lost_callback); - ~BpfPerfBuffer(); + BpfPerfBuffer(int map_fd, int page_cnt, py::function callback, + py::object lost_callback); + ~BpfPerfBuffer(); - int poll(int timeout_ms); - int consume(); + int poll(int timeout_ms); + int consume(); }; #endif // PYLIBBPF_BPF_PERF_BUFFER_H diff --git a/src/core/bpf_program.cpp b/src/core/bpf_program.cpp index 1d4202a..1979e5e 100644 --- a/src/core/bpf_program.cpp +++ b/src/core/bpf_program.cpp @@ -1,75 +1,72 @@ #include "bpf_program.h" #include "bpf_exception.h" -#include #include +#include -BpfProgram::BpfProgram(std::shared_ptr parent, struct bpf_program *raw_prog, const std::string& program_name) - : parent_obj_(parent), - prog_(raw_prog), - link_(nullptr), +BpfProgram::BpfProgram(std::shared_ptr parent, + struct bpf_program *raw_prog, + const std::string &program_name) + : parent_obj_(parent), prog_(raw_prog), link_(nullptr), program_name_(program_name) { - if (!parent) - throw BpfException("Parent BpfObject is null"); - if(!(parent->is_loaded())) - throw BpfException("Parent BpfObject is not loaded"); - if (!raw_prog) - throw BpfException("bpf_program pointer is null"); + if (!parent) + throw BpfException("Parent BpfObject is null"); + if (!(parent->is_loaded())) + throw BpfException("Parent BpfObject is not loaded"); + if (!raw_prog) + throw BpfException("bpf_program pointer is null"); } -BpfProgram::~BpfProgram() { - detach(); +BpfProgram::~BpfProgram() { detach(); } + +BpfProgram::BpfProgram(BpfProgram &&other) noexcept + : parent_obj_(std::move(other.parent_obj_)), prog_(other.prog_), + link_(other.link_), program_name_(std::move(other.program_name_)) { + + other.prog_ = nullptr; + other.link_ = nullptr; } -BpfProgram::BpfProgram(BpfProgram&& other) noexcept - : parent_obj_(std::move(other.parent_obj_)), - prog_(other.prog_), - link_(other.link_), - program_name_(std::move(other.program_name_)) { - +BpfProgram &BpfProgram::operator=(BpfProgram &&other) noexcept { + if (this != &other) { + detach(); + + parent_obj_ = std::move(other.parent_obj_); + prog_ = other.prog_; + link_ = other.link_; + program_name_ = std::move(other.program_name_); + other.prog_ = nullptr; other.link_ = nullptr; -} - -BpfProgram& BpfProgram::operator=(BpfProgram&& other) noexcept { - if (this != &other) { - detach(); - - parent_obj_ = std::move(other.parent_obj_); - prog_ = other.prog_; - link_ = other.link_; - program_name_ = std::move(other.program_name_); - - other.prog_ = nullptr; - other.link_ = nullptr; - } - return *this; + } + return *this; } void BpfProgram::attach() { - // Check if parent is still alive - auto parent = parent_obj_.lock(); - if (!parent) { - throw BpfException("Parent BpfObject has been destroyed"); - } - - if (link_) { - throw BpfException("Program '" + program_name_ + "' already attached"); - } - - if (!prog_) { - throw BpfException("Program '" + program_name_ + "' not initialized"); - } - - link_ = bpf_program__attach(prog_); - if (!link_) { - std::string err_msg = "bpf_program__attach failed for program '" + program_name_ + "': " + std::strerror(errno); - throw BpfException(err_msg); - } + // Check if parent is still alive + auto parent = parent_obj_.lock(); + if (!parent) { + throw BpfException("Parent BpfObject has been destroyed"); + } + + if (link_) { + throw BpfException("Program '" + program_name_ + "' already attached"); + } + + if (!prog_) { + throw BpfException("Program '" + program_name_ + "' not initialized"); + } + + link_ = bpf_program__attach(prog_); + if (!link_) { + std::string err_msg = "bpf_program__attach failed for program '" + + program_name_ + "': " + std::strerror(errno); + throw BpfException(err_msg); + } } void BpfProgram::detach() { - if (link_) { - bpf_link__destroy(link_); - link_ = nullptr; - } + if (link_) { + bpf_link__destroy(link_); + link_ = nullptr; + } } diff --git a/src/core/bpf_program.h b/src/core/bpf_program.h index dd0dce4..3863ee6 100644 --- a/src/core/bpf_program.h +++ b/src/core/bpf_program.h @@ -9,26 +9,28 @@ class BpfObject; class BpfProgram { private: - std::weak_ptr parent_obj_; - struct bpf_program *prog_; - struct bpf_link *link_; - std::string program_name_; + std::weak_ptr parent_obj_; + struct bpf_program *prog_; + struct bpf_link *link_; + std::string program_name_; public: - explicit BpfProgram(std::shared_ptr parent, struct bpf_program *raw_prog, const std::string& program_name); + explicit BpfProgram(std::shared_ptr parent, + struct bpf_program *raw_prog, + const std::string &program_name); - ~BpfProgram(); + ~BpfProgram(); - BpfProgram(const BpfProgram&) = delete; - BpfProgram& operator=(const BpfProgram&) = delete; - BpfProgram(BpfProgram&&) noexcept; - BpfProgram& operator=(BpfProgram&&) noexcept; + BpfProgram(const BpfProgram &) = delete; + BpfProgram &operator=(const BpfProgram &) = delete; + BpfProgram(BpfProgram &&) noexcept; + BpfProgram &operator=(BpfProgram &&) noexcept; - bool attach(); - bool detach(); + bool attach(); + bool detach(); - [[nodiscard]] bool is_attached() const { return link_ != nullptr; } - [[nodiscard]] std::string get_name() const { return program_name_; } + [[nodiscard]] bool is_attached() const { return link_ != nullptr; } + [[nodiscard]] std::string get_name() const { return program_name_; } }; -#endif //PYLIBBPF_BPF_PROGRAM_H +#endif // PYLIBBPF_BPF_PROGRAM_H From 744a50925ea44f5034be08869fea190b0d8c152b Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 19 Oct 2025 02:40:06 +0530 Subject: [PATCH 09/49] Modify bindings for newly designed classes --- src/bindings/main.cpp | 69 +++++++++++++++++++++++++--------------- src/core/bpf_program.cpp | 1 + src/core/bpf_program.h | 5 +-- 3 files changed, 47 insertions(+), 28 deletions(-) diff --git a/src/bindings/main.cpp b/src/bindings/main.cpp index ea3a6e3..b6bb779 100644 --- a/src/bindings/main.cpp +++ b/src/bindings/main.cpp @@ -6,9 +6,11 @@ extern "C" { #include } +#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(m, "BpfException"); - py::class_(m, "BpfProgram") - .def(py::init()) - .def(py::init()) - .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_>(m, "BpfObject") + .def(py::init(), 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_(m, "BpfMap") - .def(py::init()) - .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_>(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_>(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_(m, "BpfPerfBuffer") + .def(py::init(), + 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 diff --git a/src/core/bpf_program.cpp b/src/core/bpf_program.cpp index 1979e5e..3da1b78 100644 --- a/src/core/bpf_program.cpp +++ b/src/core/bpf_program.cpp @@ -1,3 +1,4 @@ +#include "bpf_object.h" #include "bpf_program.h" #include "bpf_exception.h" #include diff --git a/src/core/bpf_program.h b/src/core/bpf_program.h index 3863ee6..4f7dfc6 100644 --- a/src/core/bpf_program.h +++ b/src/core/bpf_program.h @@ -4,6 +4,7 @@ #include #include #include +#include class BpfObject; @@ -26,8 +27,8 @@ class BpfProgram { 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_; } From f233cf2134f2a9d59904cb00ead711a5cf80b397 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 19 Oct 2025 02:41:55 +0530 Subject: [PATCH 10/49] Allow C++20 in CMakeLists.txt --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bc7371..e08ccfd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) From 1c2e170bab87bfd3d5d4ecf24a0a0ccb08a26896 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 19 Oct 2025 03:02:53 +0530 Subject: [PATCH 11/49] Add BpfObject and BpfPerfBuffer to pybind11 --- CMakeLists.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e08ccfd..07bf4dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 4.0) +cmake_minimum_required(VERSION 3.31) project(pylibbpf) set(CMAKE_CXX_STANDARD 20) @@ -13,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) From c0b982a514c66c470762250216260b3cc6d66a04 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 19 Oct 2025 03:04:11 +0530 Subject: [PATCH 12/49] Use shared_from_this while creating BpfProgram or BpfMap, make get_map_names and get_program_names non-const --- src/core/bpf_object.cpp | 16 ++++++++-------- src/core/bpf_object.h | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/core/bpf_object.cpp b/src/core/bpf_object.cpp index 660d1b5..5b328bd 100644 --- a/src/core/bpf_object.cpp +++ b/src/core/bpf_object.cpp @@ -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,7 @@ BpfObject::_get_or_create_program(struct bpf_program *prog) { } // Create and cache - auto bpf_prog = std::make_shared(this, prog, prog_name); + auto bpf_prog = std::make_shared(shared_from_this(), prog, prog_name); prog_cache_[prog_name] = bpf_prog; return bpf_prog; @@ -125,7 +125,7 @@ std::shared_ptr 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(this, raw_prog, name); + auto prog = std::make_shared(shared_from_this(), raw_prog, name); prog_cache_[name] = prog; return prog; @@ -148,8 +148,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 +178,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 +249,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; } diff --git a/src/core/bpf_object.h b/src/core/bpf_object.h index 43ba7a2..49fdb09 100644 --- a/src/core/bpf_object.h +++ b/src/core/bpf_object.h @@ -65,7 +65,7 @@ class BpfObject : public std::enable_shared_from_this { py::dict attach_all(); // Program access - [[nodiscard]] py::list get_program_names() const; + [[nodiscard]] py::list get_program_names(); [[nodiscard]] std::shared_ptr get_program(const std::string &name); [[nodiscard]] struct bpf_program * @@ -73,7 +73,7 @@ class BpfObject : public std::enable_shared_from_this { [[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 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; From fc4d9a51e73938bbb6198334a2a9be93a9c22a74 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 19 Oct 2025 04:06:35 +0530 Subject: [PATCH 13/49] Fix perf_buffer__new call --- src/core/bpf_perf_buffer.cpp | 59 +++++++++++++++++++----------------- src/core/bpf_perf_buffer.h | 3 +- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/core/bpf_perf_buffer.cpp b/src/core/bpf_perf_buffer.cpp index a1e7276..dceb747 100644 --- a/src/core/bpf_perf_buffer.cpp +++ b/src/core/bpf_perf_buffer.cpp @@ -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(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(); - } - - // 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; diff --git a/src/core/bpf_perf_buffer.h b/src/core/bpf_perf_buffer.h index 3794a2d..3f21213 100644 --- a/src/core/bpf_perf_buffer.h +++ b/src/core/bpf_perf_buffer.h @@ -20,11 +20,12 @@ class BpfPerfBuffer { 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 From 1c956da07f74572b8b913147441d3216184df489 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 19 Oct 2025 04:06:54 +0530 Subject: [PATCH 14/49] Janitorial clang-format --- src/core/bpf_exception.h | 9 +++------ src/core/bpf_object.cpp | 3 ++- src/core/bpf_program.cpp | 2 +- src/core/bpf_program.h | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/core/bpf_exception.h b/src/core/bpf_exception.h index 4616c19..7266ce9 100644 --- a/src/core/bpf_exception.h +++ b/src/core/bpf_exception.h @@ -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 diff --git a/src/core/bpf_object.cpp b/src/core/bpf_object.cpp index 5b328bd..9ae949a 100644 --- a/src/core/bpf_object.cpp +++ b/src/core/bpf_object.cpp @@ -106,7 +106,8 @@ BpfObject::_get_or_create_program(struct bpf_program *prog) { } // Create and cache - auto bpf_prog = std::make_shared(shared_from_this(), prog, prog_name); + auto bpf_prog = + std::make_shared(shared_from_this(), prog, prog_name); prog_cache_[prog_name] = bpf_prog; return bpf_prog; diff --git a/src/core/bpf_program.cpp b/src/core/bpf_program.cpp index 3da1b78..e3319af 100644 --- a/src/core/bpf_program.cpp +++ b/src/core/bpf_program.cpp @@ -1,6 +1,6 @@ -#include "bpf_object.h" #include "bpf_program.h" #include "bpf_exception.h" +#include "bpf_object.h" #include #include diff --git a/src/core/bpf_program.h b/src/core/bpf_program.h index 4f7dfc6..2b27cc4 100644 --- a/src/core/bpf_program.h +++ b/src/core/bpf_program.h @@ -1,10 +1,10 @@ #ifndef PYLIBBPF_BPF_PROGRAM_H #define PYLIBBPF_BPF_PROGRAM_H +#include #include #include #include -#include class BpfObject; From cbe019c2bc56e0775dbd24c28623e2b98c3c7411 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 19 Oct 2025 04:08:13 +0530 Subject: [PATCH 15/49] Restore minimum cmake version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 07bf4dd..bd526d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.31) +cmake_minimum_required(VERSION 4.0) project(pylibbpf) set(CMAKE_CXX_STANDARD 20) From 771d8fef0ac7cc9a8b60891e4b64d172e6f03889 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 19 Oct 2025 04:21:13 +0530 Subject: [PATCH 16/49] Modify tests to use BpfObject instead --- tests/test_basic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_basic.py b/tests/test_basic.py index 98ac1cf..1dedd62 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -3,5 +3,5 @@ def test_main(): assert m.__version__ == "0.0.5" - prog = m.BpfProgram("tests/execve2.o") + prog = m.BpfObject("tests/execve2.o") print(prog) From 874d567825745c2060c78dbf4f666a1c8ab9ef9e Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 19 Oct 2025 18:47:35 +0530 Subject: [PATCH 17/49] Move BpfPerfBuffer under src/maps --- CMakeLists.txt | 4 ++-- src/bindings/main.cpp | 2 +- src/{core => maps}/bpf_perf_buffer.cpp | 2 +- src/{core => maps}/bpf_perf_buffer.h | 0 4 files changed, 4 insertions(+), 4 deletions(-) rename src/{core => maps}/bpf_perf_buffer.cpp (98%) rename src/{core => maps}/bpf_perf_buffer.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index bd526d0..268b08e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,12 +14,12 @@ pybind11_add_module( src/core/bpf_exception.h src/core/bpf_map.h src/core/bpf_object.h - src/core/bpf_perf_buffer.h + src/maps/bpf_perf_buffer.h src/bindings/main.cpp src/core/bpf_program.cpp src/core/bpf_map.cpp src/core/bpf_object.cpp - src/core/bpf_perf_buffer.cpp) + src/maps/bpf_perf_buffer.cpp) # --- libbpf build rules --- set(LIBBPF_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libbpf/src) diff --git a/src/bindings/main.cpp b/src/bindings/main.cpp index b6bb779..e2336f2 100644 --- a/src/bindings/main.cpp +++ b/src/bindings/main.cpp @@ -10,7 +10,7 @@ extern "C" { #include "core/bpf_program.h" #include "core/bpf_exception.h" #include "core/bpf_map.h" -#include "core/bpf_perf_buffer.h" +#include "maps/bpf_perf_buffer.h" namespace py = pybind11; diff --git a/src/core/bpf_perf_buffer.cpp b/src/maps/bpf_perf_buffer.cpp similarity index 98% rename from src/core/bpf_perf_buffer.cpp rename to src/maps/bpf_perf_buffer.cpp index dceb747..7b5c4e1 100644 --- a/src/core/bpf_perf_buffer.cpp +++ b/src/maps/bpf_perf_buffer.cpp @@ -1,5 +1,5 @@ #include "bpf_perf_buffer.h" -#include "bpf_exception.h" +#include "core/bpf_exception.h" BpfPerfBuffer::BpfPerfBuffer(int map_fd, int page_cnt, py::function callback, py::object lost_callback) diff --git a/src/core/bpf_perf_buffer.h b/src/maps/bpf_perf_buffer.h similarity index 100% rename from src/core/bpf_perf_buffer.h rename to src/maps/bpf_perf_buffer.h From b4d0a4988392941d1ff927a921b2f602ba7de7d4 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 19 Oct 2025 21:26:20 +0530 Subject: [PATCH 18/49] Add struct_defs_ to BpfObject --- src/core/bpf_object.cpp | 5 +++-- src/core/bpf_object.h | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/core/bpf_object.cpp b/src/core/bpf_object.cpp index 9ae949a..1cb5eba 100644 --- a/src/core/bpf_object.cpp +++ b/src/core/bpf_object.cpp @@ -4,8 +4,9 @@ #include "bpf_program.h" #include -BpfObject::BpfObject(std::string object_path) - : obj_(nullptr), object_path_(std::move(object_path)), loaded_(false) {} +BpfObject::BpfObject(std::string object_path, py::dict structs) + : obj_(nullptr), object_path_(std::move(object_path)), loaded_(false), + struct_defs_(structs) {} BpfObject::~BpfObject() { // Clear caches first (order matters!) diff --git a/src/core/bpf_object.h b/src/core/bpf_object.h index 49fdb09..a66e73b 100644 --- a/src/core/bpf_object.h +++ b/src/core/bpf_object.h @@ -28,12 +28,13 @@ class BpfObject : public std::enable_shared_from_this { mutable std::unordered_map> maps_cache_; mutable std::unordered_map> prog_cache_; + py::dict struct_defs_; std::shared_ptr _get_or_create_program(struct bpf_program *prog); std::shared_ptr _get_or_create_map(struct bpf_map *map); public: - explicit BpfObject(std::string object_path); + explicit BpfObject(std::string object_path, py::dict structs = py::dict()); ~BpfObject(); // Disable copy, allow move From f7874137ad6d24d80ef62b5fcbd5b80a658999c8 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 19 Oct 2025 21:33:24 +0530 Subject: [PATCH 19/49] Enable BpfMap to be shared, add get_parent --- src/core/bpf_map.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/bpf_map.h b/src/core/bpf_map.h index a7c2008..9407a40 100644 --- a/src/core/bpf_map.h +++ b/src/core/bpf_map.h @@ -15,7 +15,7 @@ class BpfObject; namespace py = pybind11; -class BpfMap { +class BpfMap : public std::enable_shared_from_this { private: std::weak_ptr parent_obj_; struct bpf_map *map_; @@ -62,6 +62,9 @@ class BpfMap { [[nodiscard]] int get_key_size() const { return key_size_; }; [[nodiscard]] int get_value_size() const { return value_size_; }; [[nodiscard]] int get_max_entries() const; + [[nodiscard]] std::shared_ptr get_parent() const { + return parent_obj_.lock(); + } private: static void python_to_bytes_inplace(const py::object &obj, From 05d5bba4f788898b73f68852f55276eea8e7afdc Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 19 Oct 2025 22:02:19 +0530 Subject: [PATCH 20/49] Add StructParser utility --- CMakeLists.txt | 17 ++++++++++++++--- src/utils/struct_parser.cpp | 25 +++++++++++++++++++++++++ src/utils/struct_parser.h | 20 ++++++++++++++++++++ 3 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 src/utils/struct_parser.cpp create mode 100644 src/utils/struct_parser.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 268b08e..5aee60d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,16 +10,27 @@ include_directories(${CMAKE_SOURCE_DIR}/src) add_subdirectory(pybind11) pybind11_add_module( pylibbpf + + # Core src/core/bpf_program.h src/core/bpf_exception.h src/core/bpf_map.h src/core/bpf_object.h - src/maps/bpf_perf_buffer.h - src/bindings/main.cpp src/core/bpf_program.cpp src/core/bpf_map.cpp src/core/bpf_object.cpp - src/maps/bpf_perf_buffer.cpp) + + # Maps + src/maps/bpf_perf_buffer.h + src/maps/bpf_perf_buffer.cpp + + # Utils + src/utils/struct_parser.h + src/utils/struct_parser.cpp + + # Bindings + src/bindings/main.cpp +) # --- libbpf build rules --- set(LIBBPF_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libbpf/src) diff --git a/src/utils/struct_parser.cpp b/src/utils/struct_parser.cpp new file mode 100644 index 0000000..e1d1700 --- /dev/null +++ b/src/utils/struct_parser.cpp @@ -0,0 +1,25 @@ +#include "struct_parser.h" +#include "core/bpf_exception.h" + +StructParser::StructParser(py::dict structs) { + for (auto item : structs) { + std::string name = py::str(item.first); + struct_types_[name] = py::reinterpret_borrow(item.second); + } +} + +py::object StructParser::parse(const std::string &struct_name, py::bytes data) { + auto it = struct_types_.find(struct_name); + if (it == struct_types_.end()) { + throw BpfException("Unknown struct: " + struct_name); + } + + py::object struct_type = it->second; + + // Use ctypes.from_buffer_copy() to create struct from bytes + return struct_type.attr("from_buffer_copy")(data); +} + +bool StructParser::has_struct(const std::string &struct_name) const { + return struct_types_.find(struct_name) != struct_types_.end(); +} diff --git a/src/utils/struct_parser.h b/src/utils/struct_parser.h new file mode 100644 index 0000000..aa9ed64 --- /dev/null +++ b/src/utils/struct_parser.h @@ -0,0 +1,20 @@ +#ifndef PYLIBBPF_STRUCT_PARSER_H +#define PYLIBBPF_STRUCT_PARSER_H + +#include +#include +#include + +namespace py = pybind11; + +class StructParser { +private: + std::unordered_map struct_types_; + +public: + explicit StructParser(py::dict structs); + py::object parse(const std::string &struct_name, py::bytes data); + bool has_struct(const std::string &struct_name) const; +}; + +#endif From cbfe6ae95eb54a770742000e46a07570c531ce6e Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Sun, 19 Oct 2025 22:34:10 +0530 Subject: [PATCH 21/49] Rename BpfPerfBuffer to PerfEventArray, add struct_parser to BpfObject as a shared_ptr --- CMakeLists.txt | 4 +- src/bindings/main.cpp | 8 ++-- src/core/bpf_object.cpp | 11 +++++- src/core/bpf_object.h | 6 +++ src/maps/bpf_perf_buffer.h | 18 ++++++--- ...f_perf_buffer.cpp => perf_event_array.cpp} | 18 ++++----- src/maps/perf_event_array.h | 37 +++++++++++++++++++ 7 files changed, 80 insertions(+), 22 deletions(-) rename src/maps/{bpf_perf_buffer.cpp => perf_event_array.cpp} (79%) create mode 100644 src/maps/perf_event_array.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5aee60d..cf2236e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,8 +21,8 @@ pybind11_add_module( src/core/bpf_object.cpp # Maps - src/maps/bpf_perf_buffer.h - src/maps/bpf_perf_buffer.cpp + src/maps/perf_event_array.h + src/maps/perf_event_array.cpp # Utils src/utils/struct_parser.h diff --git a/src/bindings/main.cpp b/src/bindings/main.cpp index e2336f2..d374ff7 100644 --- a/src/bindings/main.cpp +++ b/src/bindings/main.cpp @@ -10,7 +10,7 @@ extern "C" { #include "core/bpf_program.h" #include "core/bpf_exception.h" #include "core/bpf_map.h" -#include "maps/bpf_perf_buffer.h" +#include "maps/perf_event_array.h" namespace py = pybind11; @@ -66,14 +66,14 @@ PYBIND11_MODULE(pylibbpf, m) { .def("get_value_size", &BpfMap::get_value_size) .def("get_max_entries", &BpfMap::get_max_entries); - py::class_(m, "BpfPerfBuffer") + py::class_(m, "PerfEventArray") .def(py::init(), 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); + .def("poll", &PerfEventArray::poll, py::arg("timeout_ms") = -1) + .def("consume", &PerfEventArray::consume); #ifdef VERSION_INFO diff --git a/src/core/bpf_object.cpp b/src/core/bpf_object.cpp index 1cb5eba..dbc66d4 100644 --- a/src/core/bpf_object.cpp +++ b/src/core/bpf_object.cpp @@ -2,11 +2,12 @@ #include "bpf_exception.h" #include "bpf_map.h" #include "bpf_program.h" +#include "utils/struct_parser.h" #include BpfObject::BpfObject(std::string object_path, py::dict structs) : obj_(nullptr), object_path_(std::move(object_path)), loaded_(false), - struct_defs_(structs) {} + struct_defs_(structs), struct_parser_(nullptr) {} BpfObject::~BpfObject() { // Clear caches first (order matters!) @@ -256,3 +257,11 @@ py::dict BpfObject::get_cached_maps() const { } return maps; } + +std::shared_ptr BpfObject::get_struct_parser() const { + if (!struct_parser_ && !struct_defs_.empty()) { + // Create parser on first access + struct_parser_ = std::make_shared(struct_defs_); + } + return struct_parser_; +} diff --git a/src/core/bpf_object.h b/src/core/bpf_object.h index a66e73b..b5ec4e9 100644 --- a/src/core/bpf_object.h +++ b/src/core/bpf_object.h @@ -12,6 +12,7 @@ namespace py = pybind11; class BpfProgram; class BpfMap; +class StructParser; /** * BpfObject - Represents a loaded BPF object file. @@ -29,6 +30,7 @@ class BpfObject : public std::enable_shared_from_this { mutable std::unordered_map> prog_cache_; py::dict struct_defs_; + mutable std::shared_ptr struct_parser_; std::shared_ptr _get_or_create_program(struct bpf_program *prog); std::shared_ptr _get_or_create_map(struct bpf_map *map); @@ -78,6 +80,10 @@ class BpfObject : public std::enable_shared_from_this { [[nodiscard]] std::shared_ptr 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; + + // Struct parsing + [[nodiscard]] py::dict get_struct_defs() const { return struct_defs_; } + [[nodiscard]] std::shared_ptr get_struct_parser() const; }; #endif // PYLIBBPF_BPF_OBJECT_H diff --git a/src/maps/bpf_perf_buffer.h b/src/maps/bpf_perf_buffer.h index 3f21213..d0ee10d 100644 --- a/src/maps/bpf_perf_buffer.h +++ b/src/maps/bpf_perf_buffer.h @@ -1,31 +1,37 @@ -#ifndef PYLIBBPF_BPF_PERF_BUFFER_H -#define PYLIBBPF_BPF_PERF_BUFFER_H +#ifndef PYLIBBPF_PERF_EVENT_ARRAY_H +#define PYLIBBPF_PERF_EVENT_ARRAY_H #include #include #include +#include + +class StructParser; namespace py = pybind11; -class BpfPerfBuffer { +class PerfEventArray { private: struct perf_buffer *pb_; py::function callback_; py::function lost_callback_; + std::shared_ptr parser_; + std::string struct_name_; + // Static callback wrappers for C API static void sample_callback_wrapper(void *ctx, int cpu, void *data, unsigned int size); static void lost_callback_wrapper(void *ctx, int cpu, unsigned long long cnt); public: - BpfPerfBuffer(int map_fd, int page_cnt, py::function callback, + PerfEventArray(int map_fd, int page_cnt, py::function callback, py::object lost_callback = py::none()); - ~BpfPerfBuffer(); + ~PerfEventArray(); int poll(int timeout_ms); int consume(); [[nodiscard]] int fd() const; }; -#endif // PYLIBBPF_BPF_PERF_BUFFER_H +#endif // PYLIBBPF_PERF_EVENT_ARRAY_H diff --git a/src/maps/bpf_perf_buffer.cpp b/src/maps/perf_event_array.cpp similarity index 79% rename from src/maps/bpf_perf_buffer.cpp rename to src/maps/perf_event_array.cpp index 7b5c4e1..7d53e9d 100644 --- a/src/maps/bpf_perf_buffer.cpp +++ b/src/maps/perf_event_array.cpp @@ -1,7 +1,7 @@ -#include "bpf_perf_buffer.h" +#include "perf_event_array.h" #include "core/bpf_exception.h" -BpfPerfBuffer::BpfPerfBuffer(int map_fd, int page_cnt, py::function callback, +PerfEventArray::PerfEventArray(int map_fd, int page_cnt, py::function callback, py::object lost_callback) : pb_(nullptr), callback_(std::move(callback)), lost_callback_(lost_callback) { @@ -27,15 +27,15 @@ BpfPerfBuffer::BpfPerfBuffer(int map_fd, int page_cnt, py::function callback, } } -BpfPerfBuffer::~BpfPerfBuffer() { +PerfEventArray::~PerfEventArray() { if (pb_) { perf_buffer__free(pb_); } } -void BpfPerfBuffer::sample_callback_wrapper(void *ctx, int cpu, void *data, +void PerfEventArray::sample_callback_wrapper(void *ctx, int cpu, void *data, unsigned int size) { - auto *self = static_cast(ctx); + auto *self = static_cast(ctx); // Acquire GIL for Python calls py::gil_scoped_acquire acquire; @@ -51,9 +51,9 @@ void BpfPerfBuffer::sample_callback_wrapper(void *ctx, int cpu, void *data, } } -void BpfPerfBuffer::lost_callback_wrapper(void *ctx, int cpu, +void PerfEventArray::lost_callback_wrapper(void *ctx, int cpu, unsigned long long cnt) { - auto *self = static_cast(ctx); + auto *self = static_cast(ctx); if (self->lost_callback_.is_none()) { return; @@ -68,13 +68,13 @@ void BpfPerfBuffer::lost_callback_wrapper(void *ctx, int cpu, } } -int BpfPerfBuffer::poll(int timeout_ms) { +int PerfEventArray::poll(int timeout_ms) { // Release GIL during blocking poll py::gil_scoped_release release; return perf_buffer__poll(pb_, timeout_ms); } -int BpfPerfBuffer::consume() { +int PerfEventArray::consume() { py::gil_scoped_release release; return perf_buffer__consume(pb_); } diff --git a/src/maps/perf_event_array.h b/src/maps/perf_event_array.h new file mode 100644 index 0000000..86a2c83 --- /dev/null +++ b/src/maps/perf_event_array.h @@ -0,0 +1,37 @@ +#ifndef PYLIBBPF_BPF_PERF_BUFFER_H +#define PYLIBBPF_BPF_PERF_BUFFER_H + +#include +#include +#include +#include + +class StructParser; + +namespace py = pybind11; + +class PerfEventArray { +private: + struct perf_buffer *pb_; + py::function callback_; + py::function lost_callback_; + + std::shared_ptr parser_; + std::string struct_name_; + + // Static callback wrappers for C API + static void sample_callback_wrapper(void *ctx, int cpu, void *data, + unsigned int size); + static void lost_callback_wrapper(void *ctx, int cpu, unsigned long long cnt); + +public: + PerfEventArray(int map_fd, int page_cnt, py::function callback, + py::object lost_callback = py::none()); + ~PerfEventArray(); + + int poll(int timeout_ms); + int consume(); + [[nodiscard]] int fd() const; +}; + +#endif // PYLIBBPF_BPF_PERF_BUFFER_H From 8babf3087b992387861fb68e09746cac85ec4333 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 00:10:54 +0530 Subject: [PATCH 22/49] Add parser and parent shared_ptr to PerfEventArray --- src/maps/bpf_perf_buffer.h | 37 -------------------------- src/maps/perf_event_array.cpp | 49 +++++++++++++++++++++++++++++------ src/maps/perf_event_array.h | 21 ++++++++++----- 3 files changed, 56 insertions(+), 51 deletions(-) delete mode 100644 src/maps/bpf_perf_buffer.h diff --git a/src/maps/bpf_perf_buffer.h b/src/maps/bpf_perf_buffer.h deleted file mode 100644 index d0ee10d..0000000 --- a/src/maps/bpf_perf_buffer.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef PYLIBBPF_PERF_EVENT_ARRAY_H -#define PYLIBBPF_PERF_EVENT_ARRAY_H - -#include -#include -#include -#include - -class StructParser; - -namespace py = pybind11; - -class PerfEventArray { -private: - struct perf_buffer *pb_; - py::function callback_; - py::function lost_callback_; - - std::shared_ptr parser_; - std::string struct_name_; - - // Static callback wrappers for C API - static void sample_callback_wrapper(void *ctx, int cpu, void *data, - unsigned int size); - static void lost_callback_wrapper(void *ctx, int cpu, unsigned long long cnt); - -public: - PerfEventArray(int map_fd, int page_cnt, py::function callback, - py::object lost_callback = py::none()); - ~PerfEventArray(); - - int poll(int timeout_ms); - int consume(); - [[nodiscard]] int fd() const; -}; - -#endif // PYLIBBPF_PERF_EVENT_ARRAY_H diff --git a/src/maps/perf_event_array.cpp b/src/maps/perf_event_array.cpp index 7d53e9d..a8183b8 100644 --- a/src/maps/perf_event_array.cpp +++ b/src/maps/perf_event_array.cpp @@ -1,11 +1,17 @@ #include "perf_event_array.h" #include "core/bpf_exception.h" +#include "core/bpf_map.h" -PerfEventArray::PerfEventArray(int map_fd, int page_cnt, py::function callback, - py::object lost_callback) - : pb_(nullptr), callback_(std::move(callback)), +PerfEventArray::PerfEventArray(std::shared_ptr map, int page_cnt, + py::function callback, py::object lost_callback) + : map_(map), pb_(nullptr), callback_(std::move(callback)), lost_callback_(lost_callback) { + if (map->get_type() != BPF_MAP_TYPE_PERF_EVENT_ARRAY) { + throw BpfException("Map '" + map->get_name() + + "' is not a PERF_EVENT_ARRAY"); + } + if (page_cnt <= 0 || (page_cnt & (page_cnt - 1)) != 0) { throw BpfException("page_cnt must be a positive power of 2"); } @@ -14,7 +20,7 @@ PerfEventArray::PerfEventArray(int map_fd, int page_cnt, py::function callback, pb_opts.sz = sizeof(pb_opts); // Required for forward compatibility pb_ = perf_buffer__new( - map_fd, page_cnt, + map->get_fd(), page_cnt, sample_callback_wrapper, // sample_cb lost_callback.is_none() ? nullptr : lost_callback_wrapper, // lost_cb this, // ctx @@ -27,6 +33,25 @@ PerfEventArray::PerfEventArray(int map_fd, int page_cnt, py::function callback, } } +PerfEventArray::PerfEventArray(std::shared_ptr map, int page_cnt, + py::function callback, + const std::string &struct_name, + py::object lost_callback) + : PerfEventArray(map, page_cnt, callback, lost_callback) { + + auto parent = map->get_parent(); + if (!parent) { + throw BpfException("Parent BpfObject has been destroyed"); + } + + parser_ = parent->get_struct_parser(); + struct_name_ = struct_name; + + if (!parser_) { + throw BpfException("No struct definitions available"); + } +} + PerfEventArray::~PerfEventArray() { if (pb_) { perf_buffer__free(pb_); @@ -34,7 +59,7 @@ PerfEventArray::~PerfEventArray() { } void PerfEventArray::sample_callback_wrapper(void *ctx, int cpu, void *data, - unsigned int size) { + unsigned int size) { auto *self = static_cast(ctx); // Acquire GIL for Python calls @@ -44,15 +69,23 @@ void PerfEventArray::sample_callback_wrapper(void *ctx, int cpu, void *data, // Convert data to Python bytes py::bytes py_data(static_cast(data), size); - // Call Python callback: callback(cpu, data, size) - self->callback_(cpu, py_data, size); + if (self->parser_ && !self->struct_name_.empty()) { + py::object event = self->parser_->parse(self->struct_name_, py_data); + self->callback_(cpu, event); + } else { + self->callback_(cpu, py_data); + } + } catch (const py::error_already_set &e) { PyErr_Print(); + } catch (const std::exception &e) { + py::gil_scoped_acquire acquire; + py::print("C++ error in perf callback:", e.what()); } } void PerfEventArray::lost_callback_wrapper(void *ctx, int cpu, - unsigned long long cnt) { + unsigned long long cnt) { auto *self = static_cast(ctx); if (self->lost_callback_.is_none()) { diff --git a/src/maps/perf_event_array.h b/src/maps/perf_event_array.h index 86a2c83..0d09210 100644 --- a/src/maps/perf_event_array.h +++ b/src/maps/perf_event_array.h @@ -1,5 +1,5 @@ -#ifndef PYLIBBPF_BPF_PERF_BUFFER_H -#define PYLIBBPF_BPF_PERF_BUFFER_H +#ifndef PYLIBBPF_PERF_EVENT_ARRAY_H +#define PYLIBBPF_PERF_EVENT_ARRAY_H #include #include @@ -7,11 +7,13 @@ #include class StructParser; +class BpfMap; namespace py = pybind11; class PerfEventArray { private: + std::shared_ptr map_; struct perf_buffer *pb_; py::function callback_; py::function lost_callback_; @@ -25,13 +27,20 @@ class PerfEventArray { static void lost_callback_wrapper(void *ctx, int cpu, unsigned long long cnt); public: - PerfEventArray(int map_fd, int page_cnt, py::function callback, - py::object lost_callback = py::none()); + PerfEventArray(std::shared_ptr map, int page_cnt, + py::function callback, py::object lost_callback = py::none()); + PerfEventArray(std::shared_ptr map, int page_cnt, + py::function callback, const std::string &struct_name, + py::object lost_callback = py::none()); ~PerfEventArray(); + PerfEventArray(const PerfEventArray &) = delete; + PerfEventArray &operator=(const PerfEventArray &) = delete; + int poll(int timeout_ms); int consume(); - [[nodiscard]] int fd() const; + + [[nodiscard]] std::shared_ptr get_map() const { return map_; } }; -#endif // PYLIBBPF_BPF_PERF_BUFFER_H +#endif // PYLIBBPF_PERF_EVENT_ARRAY_H From 1eb7ed460e977aa1d036e697626eb4f0e08e8e04 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 00:28:27 +0530 Subject: [PATCH 23/49] Fix Bindings and PerfEventArray --- src/bindings/main.cpp | 109 ++++++++++++++++++++-------------- src/maps/perf_event_array.cpp | 2 + 2 files changed, 65 insertions(+), 46 deletions(-) diff --git a/src/bindings/main.cpp b/src/bindings/main.cpp index d374ff7..ef9d63b 100644 --- a/src/bindings/main.cpp +++ b/src/bindings/main.cpp @@ -6,16 +6,17 @@ extern "C" { #include } -#include "core/bpf_object.h" -#include "core/bpf_program.h" #include "core/bpf_exception.h" #include "core/bpf_map.h" +#include "core/bpf_object.h" +#include "core/bpf_program.h" #include "maps/perf_event_array.h" +#include "utils/struct_parser.h" namespace py = pybind11; PYBIND11_MODULE(pylibbpf, m) { - m.doc() = R"pbdoc( + m.doc() = R"pbdoc( Pylibbpf - libbpf bindings for Python ----------------------- @@ -29,56 +30,72 @@ PYBIND11_MODULE(pylibbpf, m) { BpfException )pbdoc"; - // Register the custom exception - py::register_exception(m, "BpfException"); + // Register the custom exception + py::register_exception(m, "BpfException"); - // BpfObject - py::class_>(m, "BpfObject") - .def(py::init(), 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")); + // BpfObject + py::class_>(m, "BpfObject") + .def(py::init(), py::arg("object_path"), + py::arg("structs") = py::dict()) + .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")) + .def("get_struct_defs", &BpfObject::get_struct_defs) + .def("__getitem__", &BpfObject::get_map, py::arg("name")); - // BpfProgram - py::class_>(m, "BpfProgram") - .def("attach", &BpfProgram::attach) - .def("detach", &BpfProgram::detach) - .def("is_attached", &BpfProgram::is_attached) - .def("get_name", &BpfProgram::get_name); + // BpfProgram + py::class_>(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_>(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); + // BpfMap + py::class_>(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) + .def("__getitem__", &BpfMap::lookup, py::arg("key")) + .def("__setitem__", &BpfMap::update, py::arg("key"), py::arg("value")); - py::class_(m, "PerfEventArray") - .def(py::init(), - py::arg("map_fd"), - py::arg("page_cnt") = 8, - py::arg("callback"), - py::arg("lost_callback") = py::none()) - .def("poll", &PerfEventArray::poll, py::arg("timeout_ms") = -1) - .def("consume", &PerfEventArray::consume); + // StructParser + py::class_(m, "StructParser") + .def(py::init(), py::arg("structs")) + .def("parse", &StructParser::parse, py::arg("struct_name"), + py::arg("data")) + .def("has_struct", &StructParser::has_struct, py::arg("struct_name")); + // PerfEventArray + py::class_>(m, + "PerfEventArray") + .def(py::init, int, py::function, py::object>(), + py::arg("map"), py::arg("page_cnt"), py::arg("callback"), + py::arg("lost_callback") = py::none()) + .def(py::init, int, py::function, std::string, + py::object>(), + py::arg("map"), py::arg("page_cnt"), py::arg("callback"), + py::arg("struct_name"), py::arg("lost_callback") = py::none()) + .def("poll", &PerfEventArray::poll, py::arg("timeout_ms")) + .def("consume", &PerfEventArray::consume) + .def("get_map", &PerfEventArray::get_map); #ifdef VERSION_INFO - m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO); + m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO); #else - m.attr("__version__") = "dev"; + m.attr("__version__") = "dev"; #endif } diff --git a/src/maps/perf_event_array.cpp b/src/maps/perf_event_array.cpp index a8183b8..56f4959 100644 --- a/src/maps/perf_event_array.cpp +++ b/src/maps/perf_event_array.cpp @@ -1,6 +1,8 @@ #include "perf_event_array.h" #include "core/bpf_exception.h" #include "core/bpf_map.h" +#include "core/bpf_object.h" +#include "utils/struct_parser.h" PerfEventArray::PerfEventArray(std::shared_ptr map, int page_cnt, py::function callback, py::object lost_callback) From eda08b05d73219e249cb0087df24ce78ec9514d2 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 02:04:38 +0530 Subject: [PATCH 24/49] lint fix to CMakeLists --- CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cf2236e..807ac3b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,8 +29,7 @@ pybind11_add_module( src/utils/struct_parser.cpp # Bindings - src/bindings/main.cpp -) + src/bindings/main.cpp) # --- libbpf build rules --- set(LIBBPF_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libbpf/src) From 0e454bd7d73e89e9189c0dd25a3e571baf52e66c Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 02:57:09 +0530 Subject: [PATCH 25/49] Add IR Types to CTypes struct convertor --- ir_to_ctypes.py | 84 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 ir_to_ctypes.py diff --git a/ir_to_ctypes.py b/ir_to_ctypes.py new file mode 100644 index 0000000..889e573 --- /dev/null +++ b/ir_to_ctypes.py @@ -0,0 +1,84 @@ +import ctypes +from typing import Dict, Type +from llvmlite import ir +import logging + +logger = logging.getLogger(__name__) + + +def ir_type_to_ctypes(ir_type): + """Convert LLVM IR type to ctypes type.""" + if isinstance(ir_type, ir.IntType): + width = ir_type.width + type_map = { + 8: ctypes.c_uint8, + 16: ctypes.c_uint16, + 32: ctypes.c_uint32, + 64: ctypes.c_uint64, + } + if width not in type_map: + raise ValueError(f"Unsupported integer width: {width}") + return type_map[width] + + elif isinstance(ir_type, ir.ArrayType): + count = ir_type.count + element_type = ir_type_to_ctypes(ir_type.element) + return element_type * count + + elif isinstance(ir_type, ir.PointerType): + return ctypes.c_void_p + + else: + raise TypeError(f"Unsupported IR type: {ir_type}") + + +def convert_structs_to_ctypes(structs_sym_tab) -> Dict[str, Type[ctypes.Structure]]: + """Convert PythonBPF's structs_sym_tab to ctypes.Structure classes.""" + if not structs_sym_tab: + return {} + + ctypes_structs = {} + + for struct_name, struct_type_obj in structs_sym_tab.items(): + try: + fields = [] + for field_name, field_ir_type in struct_type_obj.fields.items(): + field_ctypes = ir_type_to_ctypes(field_ir_type) + fields.append((field_name, field_ctypes)) + + struct_class = type( + struct_name, + (ctypes.Structure,), + {'_fields_': fields, '__module__': 'pylibbpf.ir_to_ctypes', '__doc__': f'Auto-generated ctypes structure for {struct_name}', + '__repr__': lambda self: ( + f"<{struct_name} " + + " ".join(f"{name}={getattr(self, name)}" for name, _ in fields) + + ">" + )} + ) + + ctypes_structs[struct_name] = struct_class + # Pretty print field info + field_info = ", ".join(f"{name}: {typ.__name__}" for name, typ in fields) + logger.debug(f" {struct_name}({field_info})") + except Exception as e: + logger.error(f"Failed to convert struct '{struct_name}': {e}") + raise + logger.info(f"Converted struct '{struct_name}' to ctypes") + return ctypes_structs + + +def is_pythonbpf_structs(structs) -> bool: + """Check if structs dict is from PythonBPF.""" + if not isinstance(structs, dict) or not structs: + return False + + first_value = next(iter(structs.values())) + return ( + hasattr(first_value, 'ir_type') and + hasattr(first_value, 'fields') and + hasattr(first_value, 'size') + ) + + +__all__ = ["convert_structs_to_ctypes", "is_pythonbpf_structs"] From 30021e8520a234bfa251c9af5812be3d40c081a6 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 02:57:57 +0530 Subject: [PATCH 26/49] Add PerfEventArray and BpfObject wrappers --- wrappers.py | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 wrappers.py diff --git a/wrappers.py b/wrappers.py new file mode 100644 index 0000000..4d231ae --- /dev/null +++ b/wrappers.py @@ -0,0 +1,80 @@ +from typing import Callable, Optional + + +class PerfEventArrayHelper: + """Fluent wrapper for PERF_EVENT_ARRAY maps.""" + + def __init__(self, bpf_map): + self._map = bpf_map + self._perf_buffer = None + + def open_perf_buffer( + self, + callback: Callable, + struct_name: str = "", + page_cnt: int = 8, + lost_callback: Optional[Callable] = None + ): + """Open perf buffer with auto-deserialization.""" + from .pylibbpf import PerfEventArray + + if struct_name: + self._perf_buffer = PerfEventArray( + self._map, + page_cnt, + callback, + struct_name, + lost_callback or (lambda cpu, cnt: None) + ) + else: + self._perf_buffer = PerfEventArray( + self._map, + page_cnt, + callback, + lost_callback or (lambda cpu, cnt: None) + ) + + return self + + def poll(self, timeout_ms: int = -1) -> int: + if not self._perf_buffer: + raise RuntimeError("Call open_perf_buffer() first") + return self._perf_buffer.poll(timeout_ms) + + def consume(self) -> int: + if not self._perf_buffer: + raise RuntimeError("Call open_perf_buffer() first") + return self._perf_buffer.consume() + + def __getattr__(self, name): + return getattr(self._map, name) + + +class BpfObjectWrapper: + """Smart wrapper that returns map-specific helpers.""" + + BPF_MAP_TYPE_PERF_EVENT_ARRAY = 4 + BPF_MAP_TYPE_RINGBUF = 27 + + def __init__(self, bpf_object): + self._obj = bpf_object + self._map_helpers = {} + + def __getitem__(self, name: str): + """Return appropriate helper based on map type.""" + if name in self._map_helpers: + return self._map_helpers[name] + + map_obj = self._obj[name] + map_type = map_obj.get_type() + + if map_type == self.BPF_MAP_TYPE_PERF_EVENT_ARRAY: + helper = PerfEventArrayHelper(map_obj) + else: + helper = map_obj + + self._map_helpers[name] = helper + return helper + + def __getattr__(self, name): + return getattr(self._obj, name) From 23cafa4d7bfad1c5b6c8e1f33cf9e9c4b16158fb Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 02:58:22 +0530 Subject: [PATCH 27/49] Expose classes and perform struct conversion in __init__ --- __init__.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 __init__.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..d679661 --- /dev/null +++ b/__init__.py @@ -0,0 +1,41 @@ +import logging +from .pylibbpf import ( + BpfObject as _BpfObject, # C++ object (internal) + BpfProgram, + BpfMap, + PerfEventArray, + StructParser, + BpfException, +) +from .wrappers import BpfObjectWrapper +from .ir_to_ctypes import convert_structs_to_ctypes, is_pythonbpf_structs + +logger = logging.getLogger(__name__) + + +class BpfObject(BpfObjectWrapper): + """BpfObject with automatic struct conversion""" + + def __init__(self, object_path: str, structs=None): + """Create a BPF object""" + if structs is None: + structs = {} + elif is_pythonbpf_structs(structs): + logger.info(f"Auto-converting {len(structs)} PythonBPF structs to ctypes") + structs = convert_structs_to_ctypes(structs) + + # Create C++ BpfObject with converted structs + cpp_obj = _BpfObject(object_path, structs) + + # Initialize wrapper + super().__init__(cpp_obj) + + +__all__ = [ + "BpfObject", + "BpfProgram", + "BpfMap", + "PerfEventArray", + "StructParser", + "BpfException", +] From bbb39898ab74aaba636662f47fb74f76257b026e Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 03:06:51 +0530 Subject: [PATCH 28/49] Fix setup.py --- setup.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 37e5302..6b04766 100644 --- a/setup.py +++ b/setup.py @@ -129,8 +129,11 @@ def build_extension(self, ext: CMakeExtension) -> None: description="Python Bindings for Libbpf", long_description=long_description, long_description_content_type="text/markdown", - url="https://github.com/varun-r-mallya/pylibbpf", - ext_modules=[CMakeExtension("pylibbpf")], + url="https://github.com/pythonbpf/pylibbpf", + packages=find_packages(where="."), + package_dir={"": "."}, + py_modules=[], # Empty since we use packages + ext_modules=[CMakeExtension("pylibbpf.pylibbpf")], cmdclass={"build_ext": CMakeBuild}, zip_safe=False, classifiers=[ @@ -147,6 +150,16 @@ def build_extension(self, ext: CMakeExtension) -> None: "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: System :: Operating System Kernels :: Linux", ], + install_requires=[ + "llvmlite>=0.40.0", # Required for struct conversion + ], extras_require={"test": ["pytest>=6.0"]}, python_requires=">=3.8", + package_data={ + "pylibbpf": [ + "*.py", + "py.typed", # For type hints + ], + }, + include_package_data=True, ) From 495318f622bf86205fb0b0e03dd5accf84bea7e6 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 03:09:51 +0530 Subject: [PATCH 29/49] Update pyproject.toml --- pyproject.toml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 45b0c4a..08c7b8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,12 +4,13 @@ requires = [ "wheel", "ninja", "cmake>=4.0", + "pybind11>=2.10", ] build-backend = "setuptools.build_meta" [project] name = "pylibbpf" -version = "0.0.5" +version = "0.0.6" description = "Python Bindings for Libbpf" authors = [ { name = "r41k0u", email = "pragyanshchaturvedi18@gmail.com" }, @@ -32,14 +33,17 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: System :: Operating System Kernels :: Linux", ] +dependencies = [ + "llvmlite>=0.40.0", +] [project.optional-dependencies] test = ["pytest>=6.0"] [project.urls] -Homepage = "https://github.com/varun-r-mallya/pylibbpf" -Repository = "https://github.com/varun-r-mallya/pylibbpf" -Issues = "https://github.com/varun-r-mallya/pylibbpf/issues" +Homepage = "https://github.com/pythonbpf/pylibbpf" +Repository = "https://github.com/pythonbpf/pylibbpf" +Issues = "https://github.com/pythonbpf/pylibbpf/issues" [tool.mypy] files = "setup.py" From 470afc517439398e8807ba4f18e99eb9addd84b8 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 03:45:59 +0530 Subject: [PATCH 30/49] Import find_packages in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6b04766..ee8398e 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import sys from pathlib import Path -from setuptools import Extension, setup +from setuptools import Extension, setup, find_packages from setuptools.command.build_ext import build_ext # Convert distutils Windows platform specifiers to CMake -A arguments From c580aab1c429e9d03cef5c580587da485c5acc45 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 03:46:30 +0530 Subject: [PATCH 31/49] Move Python files to pylibbpf/ --- __init__.py => pylibbpf/__init__.py | 0 ir_to_ctypes.py => pylibbpf/ir_to_ctypes.py | 0 pylibbpf/py.typed | 0 wrappers.py => pylibbpf/wrappers.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename __init__.py => pylibbpf/__init__.py (100%) rename ir_to_ctypes.py => pylibbpf/ir_to_ctypes.py (100%) create mode 100644 pylibbpf/py.typed rename wrappers.py => pylibbpf/wrappers.py (100%) diff --git a/__init__.py b/pylibbpf/__init__.py similarity index 100% rename from __init__.py rename to pylibbpf/__init__.py diff --git a/ir_to_ctypes.py b/pylibbpf/ir_to_ctypes.py similarity index 100% rename from ir_to_ctypes.py rename to pylibbpf/ir_to_ctypes.py diff --git a/pylibbpf/py.typed b/pylibbpf/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/wrappers.py b/pylibbpf/wrappers.py similarity index 100% rename from wrappers.py rename to pylibbpf/wrappers.py From ddbbce400e65c50cfd07e7645b7fb27ce796f26c Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 03:48:02 +0530 Subject: [PATCH 32/49] Use c_char type for Int8 arrays in ir_to_ctypes --- pylibbpf/ir_to_ctypes.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pylibbpf/ir_to_ctypes.py b/pylibbpf/ir_to_ctypes.py index 889e573..455dda5 100644 --- a/pylibbpf/ir_to_ctypes.py +++ b/pylibbpf/ir_to_ctypes.py @@ -22,9 +22,14 @@ def ir_type_to_ctypes(ir_type): elif isinstance(ir_type, ir.ArrayType): count = ir_type.count - element_type = ir_type_to_ctypes(ir_type.element) - return element_type * count - + element_type_ir = ir_type.element + + if isinstance(element_type_ir, ir.IntType) and element_type_ir.width == 8: + # Use c_char for string fields (will have .decode()) + return ctypes.c_char * count + else: + element_type = ir_type_to_ctypes(element_type_ir) + return element_type * count elif isinstance(ir_type, ir.PointerType): return ctypes.c_void_p From b7aa0807c5876d0557af816828c77bfd0c672322 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 04:05:14 +0530 Subject: [PATCH 33/49] Fix pre-commit conditions --- CMakeLists.txt | 4 ---- examples/execve.py | 3 ++- pylibbpf/__init__.py | 11 +++++++---- pylibbpf/ir_to_ctypes.py | 27 +++++++++++++++++---------- pylibbpf/wrappers.py | 9 +++------ setup.py | 2 +- 6 files changed, 30 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 807ac3b..c8b690d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,6 @@ include_directories(${CMAKE_SOURCE_DIR}/src) add_subdirectory(pybind11) pybind11_add_module( pylibbpf - # Core src/core/bpf_program.h src/core/bpf_exception.h @@ -19,15 +18,12 @@ pybind11_add_module( src/core/bpf_program.cpp src/core/bpf_map.cpp src/core/bpf_object.cpp - # Maps src/maps/perf_event_array.h src/maps/perf_event_array.cpp - # Utils src/utils/struct_parser.h src/utils/struct_parser.cpp - # Bindings src/bindings/main.cpp) diff --git a/examples/execve.py b/examples/execve.py index c6a3f9f..d47f327 100644 --- a/examples/execve.py +++ b/examples/execve.py @@ -1,10 +1,11 @@ import time from ctypes import c_int32, c_int64, c_uint64, c_void_p -from pylibbpf import BpfMap from pythonbpf import BPF, bpf, bpfglobal, map, section from pythonbpf.maps import HashMap +from pylibbpf import BpfMap + @bpf @map diff --git a/pylibbpf/__init__.py b/pylibbpf/__init__.py index d679661..cac92df 100644 --- a/pylibbpf/__init__.py +++ b/pylibbpf/__init__.py @@ -1,14 +1,17 @@ import logging + +from .ir_to_ctypes import convert_structs_to_ctypes, is_pythonbpf_structs from .pylibbpf import ( - BpfObject as _BpfObject, # C++ object (internal) - BpfProgram, + BpfException, BpfMap, + BpfProgram, PerfEventArray, StructParser, - BpfException, +) +from .pylibbpf import ( + BpfObject as _BpfObject, # C++ object (internal) ) from .wrappers import BpfObjectWrapper -from .ir_to_ctypes import convert_structs_to_ctypes, is_pythonbpf_structs logger = logging.getLogger(__name__) diff --git a/pylibbpf/ir_to_ctypes.py b/pylibbpf/ir_to_ctypes.py index 455dda5..1ad5a71 100644 --- a/pylibbpf/ir_to_ctypes.py +++ b/pylibbpf/ir_to_ctypes.py @@ -1,7 +1,8 @@ import ctypes +import logging from typing import Dict, Type + from llvmlite import ir -import logging logger = logging.getLogger(__name__) @@ -54,12 +55,18 @@ def convert_structs_to_ctypes(structs_sym_tab) -> Dict[str, Type[ctypes.Structur struct_class = type( struct_name, (ctypes.Structure,), - {'_fields_': fields, '__module__': 'pylibbpf.ir_to_ctypes', '__doc__': f'Auto-generated ctypes structure for {struct_name}', - '__repr__': lambda self: ( - f"<{struct_name} " + - " ".join(f"{name}={getattr(self, name)}" for name, _ in fields) + - ">" - )} + { + "_fields_": fields, + "__module__": "pylibbpf.ir_to_ctypes", + "__doc__": f"Auto-generated ctypes structure for {struct_name}", + "__repr__": lambda self: ( + f"<{struct_name} " + + " ".join( + f"{name}={getattr(self, name)}" for name, _ in fields + ) + + ">" + ), + }, ) ctypes_structs[struct_name] = struct_class @@ -80,9 +87,9 @@ def is_pythonbpf_structs(structs) -> bool: first_value = next(iter(structs.values())) return ( - hasattr(first_value, 'ir_type') and - hasattr(first_value, 'fields') and - hasattr(first_value, 'size') + hasattr(first_value, "ir_type") + and hasattr(first_value, "fields") + and hasattr(first_value, "size") ) diff --git a/pylibbpf/wrappers.py b/pylibbpf/wrappers.py index 4d231ae..aa7679c 100644 --- a/pylibbpf/wrappers.py +++ b/pylibbpf/wrappers.py @@ -13,7 +13,7 @@ def open_perf_buffer( callback: Callable, struct_name: str = "", page_cnt: int = 8, - lost_callback: Optional[Callable] = None + lost_callback: Optional[Callable] = None, ): """Open perf buffer with auto-deserialization.""" from .pylibbpf import PerfEventArray @@ -24,14 +24,11 @@ def open_perf_buffer( page_cnt, callback, struct_name, - lost_callback or (lambda cpu, cnt: None) + lost_callback or (lambda cpu, cnt: None), ) else: self._perf_buffer = PerfEventArray( - self._map, - page_cnt, - callback, - lost_callback or (lambda cpu, cnt: None) + self._map, page_cnt, callback, lost_callback or (lambda cpu, cnt: None) ) return self diff --git a/setup.py b/setup.py index ee8398e..41abc05 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import sys from pathlib import Path -from setuptools import Extension, setup, find_packages +from setuptools import Extension, find_packages, setup from setuptools.command.build_ext import build_ext # Convert distutils Windows platform specifiers to CMake -A arguments From 92e92f134abf194a28cf6654e7b9d2ca79bff522 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 04:18:38 +0530 Subject: [PATCH 34/49] Add _make_repr to ir_to_ctypes --- pylibbpf/ir_to_ctypes.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/pylibbpf/ir_to_ctypes.py b/pylibbpf/ir_to_ctypes.py index 1ad5a71..059e664 100644 --- a/pylibbpf/ir_to_ctypes.py +++ b/pylibbpf/ir_to_ctypes.py @@ -38,6 +38,19 @@ def ir_type_to_ctypes(ir_type): raise TypeError(f"Unsupported IR type: {ir_type}") +def _make_repr(struct_name: str, fields: list): + """Create a __repr__ function for a struct""" + + def __repr__(self): + field_strs = [] + for field_name, _ in fields: + value = getattr(self, field_name) + field_strs.append(f"{field_name}={value}") + return f"<{struct_name} {' '.join(field_strs)}>" + + return __repr__ + + def convert_structs_to_ctypes(structs_sym_tab) -> Dict[str, Type[ctypes.Structure]]: """Convert PythonBPF's structs_sym_tab to ctypes.Structure classes.""" if not structs_sym_tab: @@ -52,6 +65,8 @@ def convert_structs_to_ctypes(structs_sym_tab) -> Dict[str, Type[ctypes.Structur field_ctypes = ir_type_to_ctypes(field_ir_type) fields.append((field_name, field_ctypes)) + repr_func = _make_repr(struct_name, fields) + struct_class = type( struct_name, (ctypes.Structure,), @@ -59,13 +74,7 @@ def convert_structs_to_ctypes(structs_sym_tab) -> Dict[str, Type[ctypes.Structur "_fields_": fields, "__module__": "pylibbpf.ir_to_ctypes", "__doc__": f"Auto-generated ctypes structure for {struct_name}", - "__repr__": lambda self: ( - f"<{struct_name} " - + " ".join( - f"{name}={getattr(self, name)}" for name, _ in fields - ) - + ">" - ), + "__repr__": repr_func, }, ) From c9a152adc3ae10e8ec5282345f0cb886e94f809c Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 04:22:14 +0530 Subject: [PATCH 35/49] Add __version__ to __init__ --- pylibbpf/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pylibbpf/__init__.py b/pylibbpf/__init__.py index cac92df..e746b99 100644 --- a/pylibbpf/__init__.py +++ b/pylibbpf/__init__.py @@ -42,3 +42,5 @@ def __init__(self, object_path: str, structs=None): "StructParser", "BpfException", ] + +__version__ = "0.0.6" From 638533c19a2cf5155f15d0b60c2e41340cbcc8da Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 04:27:30 +0530 Subject: [PATCH 36/49] Fix test for pip GH workflow --- .github/workflows/pip.yml | 4 ++-- tests/test_basic.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml index 467cc4c..f4f5ada 100644 --- a/.github/workflows/pip.yml +++ b/.github/workflows/pip.yml @@ -45,7 +45,7 @@ jobs: run: pip install --verbose .[test] - name: Test import - run: python -c "import pylibbpf; print('Import successful')" + run: python -I -c "import pylibbpf; print('Import successful')" - name: Test - run: python -m pytest -v + run: python -I -m pytest -v diff --git a/tests/test_basic.py b/tests/test_basic.py index 1dedd62..31ac054 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -2,6 +2,6 @@ def test_main(): - assert m.__version__ == "0.0.5" - prog = m.BpfObject("tests/execve2.o") + assert m.__version__ == "0.0.6" + prog = m.BpfObject("tests/execve2.o", structs={}) print(prog) From dd552de02c271e6dff0dba9860d3a076e2880a08 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 05:04:19 +0530 Subject: [PATCH 37/49] Add memory header to maps/perf_event_array.h --- src/maps/perf_event_array.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/maps/perf_event_array.h b/src/maps/perf_event_array.h index 0d09210..1b75440 100644 --- a/src/maps/perf_event_array.h +++ b/src/maps/perf_event_array.h @@ -5,6 +5,7 @@ #include #include #include +#include class StructParser; class BpfMap; From 3085e8155dc5d00af90f27c935adcee346dba9d6 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 05:08:15 +0530 Subject: [PATCH 38/49] Add __delitem__ for BpfMap in bindings --- src/bindings/main.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/bindings/main.cpp b/src/bindings/main.cpp index ef9d63b..88b718c 100644 --- a/src/bindings/main.cpp +++ b/src/bindings/main.cpp @@ -70,7 +70,8 @@ PYBIND11_MODULE(pylibbpf, m) { .def("get_value_size", &BpfMap::get_value_size) .def("get_max_entries", &BpfMap::get_max_entries) .def("__getitem__", &BpfMap::lookup, py::arg("key")) - .def("__setitem__", &BpfMap::update, py::arg("key"), py::arg("value")); + .def("__setitem__", &BpfMap::update, py::arg("key"), py::arg("value")) + .def("__delitem__", &BpfMap::delete_elem, py::arg("key")); // StructParser py::class_(m, "StructParser") From a3c3dbe141519e695b7b6469bd7a427a91dcccf2 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 05:09:34 +0530 Subject: [PATCH 39/49] Fix BpfMap header guard --- src/core/bpf_map.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/bpf_map.h b/src/core/bpf_map.h index 9407a40..f9fd493 100644 --- a/src/core/bpf_map.h +++ b/src/core/bpf_map.h @@ -72,4 +72,4 @@ class BpfMap : public std::enable_shared_from_this { static py::object bytes_to_python(std::span data); }; -#endif // PYLIBBPF_MAPS_H +#endif // PYLIBBPF_BPF_MAP_H From ec5377ba14a797ff341bce3838b28204d0b679ce Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 05:14:44 +0530 Subject: [PATCH 40/49] Remove unnecessary GIL acquisition in PerfEventArray --- src/maps/perf_event_array.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/maps/perf_event_array.cpp b/src/maps/perf_event_array.cpp index 56f4959..a14d11b 100644 --- a/src/maps/perf_event_array.cpp +++ b/src/maps/perf_event_array.cpp @@ -81,7 +81,6 @@ void PerfEventArray::sample_callback_wrapper(void *ctx, int cpu, void *data, } catch (const py::error_already_set &e) { PyErr_Print(); } catch (const std::exception &e) { - py::gil_scoped_acquire acquire; py::print("C++ error in perf callback:", e.what()); } } From eebfe61ccca006b0dc84d89f5c59a5f14783d992 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 05:17:40 +0530 Subject: [PATCH 41/49] fix lost_callback_wrapper, clang-format --- src/maps/perf_event_array.cpp | 7 +++---- src/maps/perf_event_array.h | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/maps/perf_event_array.cpp b/src/maps/perf_event_array.cpp index a14d11b..cfebb3d 100644 --- a/src/maps/perf_event_array.cpp +++ b/src/maps/perf_event_array.cpp @@ -89,13 +89,12 @@ void PerfEventArray::lost_callback_wrapper(void *ctx, int cpu, unsigned long long cnt) { auto *self = static_cast(ctx); - if (self->lost_callback_.is_none()) { - return; - } - py::gil_scoped_acquire acquire; try { + if (self->lost_callback_.is_none()) { + return; + } self->lost_callback_(cpu, cnt); } catch (const py::error_already_set &e) { PyErr_Print(); diff --git a/src/maps/perf_event_array.h b/src/maps/perf_event_array.h index 1b75440..9f77454 100644 --- a/src/maps/perf_event_array.h +++ b/src/maps/perf_event_array.h @@ -2,10 +2,10 @@ #define PYLIBBPF_PERF_EVENT_ARRAY_H #include +#include #include #include #include -#include class StructParser; class BpfMap; From 003495e833f0c129115e0af46d4bc4ad6e30ccdc Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 05:22:31 +0530 Subject: [PATCH 42/49] Make lost_callback type asfe in PerfEventArray --- src/maps/perf_event_array.cpp | 10 ++++++---- src/maps/perf_event_array.h | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/maps/perf_event_array.cpp b/src/maps/perf_event_array.cpp index cfebb3d..66214e8 100644 --- a/src/maps/perf_event_array.cpp +++ b/src/maps/perf_event_array.cpp @@ -7,7 +7,7 @@ PerfEventArray::PerfEventArray(std::shared_ptr map, int page_cnt, py::function callback, py::object lost_callback) : map_(map), pb_(nullptr), callback_(std::move(callback)), - lost_callback_(lost_callback) { + lost_callback_(std::move(lost_callback)) { if (map->get_type() != BPF_MAP_TYPE_PERF_EVENT_ARRAY) { throw BpfException("Map '" + map->get_name() + @@ -92,10 +92,12 @@ void PerfEventArray::lost_callback_wrapper(void *ctx, int cpu, py::gil_scoped_acquire acquire; try { - if (self->lost_callback_.is_none()) { - return; + if (!self->lost_callback_.is_none()) { + py::function lost_fn = py::cast(self->lost_callback_); + lost_fn(cpu, cnt); + } else { + py::print("Lost", cnt, "events on CPU", cpu); } - self->lost_callback_(cpu, cnt); } catch (const py::error_already_set &e) { PyErr_Print(); } diff --git a/src/maps/perf_event_array.h b/src/maps/perf_event_array.h index 9f77454..d35aceb 100644 --- a/src/maps/perf_event_array.h +++ b/src/maps/perf_event_array.h @@ -17,7 +17,7 @@ class PerfEventArray { std::shared_ptr map_; struct perf_buffer *pb_; py::function callback_; - py::function lost_callback_; + py::object lost_callback_; std::shared_ptr parser_; std::string struct_name_; From 88716ce19a3d562af7cbb5339013a14158307071 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 05:47:40 +0530 Subject: [PATCH 43/49] Fill missing fields in BpfObject's move constructor --- src/core/bpf_object.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/core/bpf_object.cpp b/src/core/bpf_object.cpp index dbc66d4..b3d66a5 100644 --- a/src/core/bpf_object.cpp +++ b/src/core/bpf_object.cpp @@ -4,6 +4,7 @@ #include "bpf_program.h" #include "utils/struct_parser.h" #include +#include BpfObject::BpfObject(std::string object_path, py::dict structs) : obj_(nullptr), object_path_(std::move(object_path)), loaded_(false), @@ -22,9 +23,13 @@ BpfObject::~BpfObject() { } BpfObject::BpfObject(BpfObject &&other) noexcept - : obj_(other.obj_), object_path_(std::move(other.object_path_)), - loaded_(other.loaded_), prog_cache_(std::move(other.prog_cache_)), - maps_cache_(std::move(other.maps_cache_)) { + : obj_(std::exchange(other.obj_, nullptr)), + object_path_(std::move(other.object_path_)), + loaded_(std::exchange(other.loaded_, false)), + maps_cache_(std::move(other.maps_cache_)), + prog_cache_(std::move(other.prog_cache_)), + struct_defs_(std::move(other.struct_defs_)), + struct_parser_(std::move(other.struct_parser_)) { other.obj_ = nullptr; other.loaded_ = false; @@ -38,14 +43,13 @@ BpfObject &BpfObject::operator=(BpfObject &&other) noexcept { bpf_object__close(obj_); } - obj_ = other.obj_; + obj_ = std::exchange(other.obj_, nullptr); object_path_ = std::move(other.object_path_); - loaded_ = other.loaded_; - prog_cache_ = std::move(other.prog_cache_); + loaded_ = std::exchange(other.loaded_, false); maps_cache_ = std::move(other.maps_cache_); - - other.obj_ = nullptr; - other.loaded_ = false; + prog_cache_ = std::move(other.prog_cache_); + struct_defs_ = std::move(other.struct_defs_); + struct_parser_ = std::move(other.struct_parser_); } return *this; } From fb82b609f9bcdc094cd5c9c9d61145b20a559097 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 05:58:55 +0530 Subject: [PATCH 44/49] Fix BpfMap includes --- src/core/bpf_map.cpp | 3 +++ src/core/bpf_map.h | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/bpf_map.cpp b/src/core/bpf_map.cpp index 2eafdec..6381d07 100644 --- a/src/core/bpf_map.cpp +++ b/src/core/bpf_map.cpp @@ -1,6 +1,9 @@ #include "bpf_map.h" #include "bpf_exception.h" #include "bpf_object.h" +#include +#include +#include BpfMap::BpfMap(std::shared_ptr parent, struct bpf_map *raw_map, const std::string &map_name) diff --git a/src/core/bpf_map.h b/src/core/bpf_map.h index f9fd493..e3d7af3 100644 --- a/src/core/bpf_map.h +++ b/src/core/bpf_map.h @@ -1,10 +1,7 @@ #ifndef PYLIBBPF_BPF_MAP_H #define PYLIBBPF_BPF_MAP_H -#include #include -#include -#include #include #include #include From ff427c2e61c48bcdb94311a3cb8d84a18142905f Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 06:07:57 +0530 Subject: [PATCH 45/49] Fix includes for bindings --- src/bindings/main.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/bindings/main.cpp b/src/bindings/main.cpp index 88b718c..023215c 100644 --- a/src/bindings/main.cpp +++ b/src/bindings/main.cpp @@ -2,10 +2,6 @@ #define STRINGIFY(x) #x #define MACRO_STRINGIFY(x) STRINGIFY(x) -extern "C" { -#include -} - #include "core/bpf_exception.h" #include "core/bpf_map.h" #include "core/bpf_object.h" From 8cc8f4267ae1a57778d5b7c1c2d9b25ca9034987 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 06:08:16 +0530 Subject: [PATCH 46/49] Fix includes for BpfMap --- src/core/bpf_map.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/bpf_map.cpp b/src/core/bpf_map.cpp index 6381d07..213233f 100644 --- a/src/core/bpf_map.cpp +++ b/src/core/bpf_map.cpp @@ -1,6 +1,6 @@ -#include "bpf_map.h" -#include "bpf_exception.h" -#include "bpf_object.h" +#include "core/bpf_map.h" +#include "core/bpf_exception.h" +#include "core/bpf_object.h" #include #include #include From 867f142a7ff13580a62bd48e6f702f2903be277d Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 06:08:36 +0530 Subject: [PATCH 47/49] Fix includes for BpfObject --- src/core/bpf_object.cpp | 19 ++++++++++--------- src/core/bpf_object.h | 1 - 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/core/bpf_object.cpp b/src/core/bpf_object.cpp index b3d66a5..fd80c8f 100644 --- a/src/core/bpf_object.cpp +++ b/src/core/bpf_object.cpp @@ -1,9 +1,10 @@ -#include "bpf_object.h" -#include "bpf_exception.h" -#include "bpf_map.h" -#include "bpf_program.h" +#include "core/bpf_object.h" +#include "core/bpf_exception.h" +#include "core/bpf_map.h" +#include "core/bpf_program.h" #include "utils/struct_parser.h" #include +#include #include BpfObject::BpfObject(std::string object_path, py::dict structs) @@ -263,9 +264,9 @@ py::dict BpfObject::get_cached_maps() const { } std::shared_ptr BpfObject::get_struct_parser() const { - if (!struct_parser_ && !struct_defs_.empty()) { - // Create parser on first access - struct_parser_ = std::make_shared(struct_defs_); - } - return struct_parser_; + if (!struct_parser_ && !struct_defs_.empty()) { + // Create parser on first access + struct_parser_ = std::make_shared(struct_defs_); + } + return struct_parser_; } diff --git a/src/core/bpf_object.h b/src/core/bpf_object.h index b5ec4e9..bbc0576 100644 --- a/src/core/bpf_object.h +++ b/src/core/bpf_object.h @@ -6,7 +6,6 @@ #include #include #include -#include namespace py = pybind11; From fa5d181e1a527e66f903e3e787124605a71d34e1 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 06:08:50 +0530 Subject: [PATCH 48/49] Fix includes for BpfProgram --- src/core/bpf_program.cpp | 7 ++++--- src/core/bpf_program.h | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/bpf_program.cpp b/src/core/bpf_program.cpp index e3319af..cf2a603 100644 --- a/src/core/bpf_program.cpp +++ b/src/core/bpf_program.cpp @@ -1,7 +1,8 @@ -#include "bpf_program.h" -#include "bpf_exception.h" -#include "bpf_object.h" +#include "core/bpf_program.h" +#include "core/bpf_exception.h" +#include "core/bpf_object.h" #include +#include #include BpfProgram::BpfProgram(std::shared_ptr parent, diff --git a/src/core/bpf_program.h b/src/core/bpf_program.h index 2b27cc4..2cc16a9 100644 --- a/src/core/bpf_program.h +++ b/src/core/bpf_program.h @@ -1,7 +1,6 @@ #ifndef PYLIBBPF_BPF_PROGRAM_H #define PYLIBBPF_BPF_PROGRAM_H -#include #include #include #include From f99de9981cbb1749f66102b259bde71c134b2c03 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Mon, 20 Oct 2025 06:09:07 +0530 Subject: [PATCH 49/49] Fix includes for PerfEventArray --- src/maps/perf_event_array.cpp | 4 +++- src/maps/perf_event_array.h | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/maps/perf_event_array.cpp b/src/maps/perf_event_array.cpp index 66214e8..8ffc20d 100644 --- a/src/maps/perf_event_array.cpp +++ b/src/maps/perf_event_array.cpp @@ -1,8 +1,10 @@ -#include "perf_event_array.h" +#include "maps/perf_event_array.h" #include "core/bpf_exception.h" #include "core/bpf_map.h" #include "core/bpf_object.h" #include "utils/struct_parser.h" +#include +#include PerfEventArray::PerfEventArray(std::shared_ptr map, int page_cnt, py::function callback, py::object lost_callback) diff --git a/src/maps/perf_event_array.h b/src/maps/perf_event_array.h index d35aceb..1a1cba9 100644 --- a/src/maps/perf_event_array.h +++ b/src/maps/perf_event_array.h @@ -3,7 +3,6 @@ #include #include -#include #include #include