32 Commits

Author SHA1 Message Date
771d8fef0a Modify tests to use BpfObject instead 2025-10-19 04:25:44 +05:30
cbe019c2bc Restore minimum cmake version 2025-10-19 04:08:13 +05:30
1c956da07f Janitorial clang-format 2025-10-19 04:06:54 +05:30
fc4d9a51e7 Fix perf_buffer__new call 2025-10-19 04:06:35 +05:30
c0b982a514 Use shared_from_this while creating BpfProgram or BpfMap, make get_map_names and get_program_names non-const 2025-10-19 03:04:11 +05:30
1c2e170bab Add BpfObject and BpfPerfBuffer to pybind11 2025-10-19 03:02:53 +05:30
f233cf2134 Allow C++20 in CMakeLists.txt 2025-10-19 02:41:55 +05:30
744a50925e Modify bindings for newly designed classes 2025-10-19 02:40:06 +05:30
4a5ff0c1c2 Janitorial: clang-format 2025-10-18 21:00:16 +05:30
c5a485b526 Reimplement BpfMap 2025-10-18 20:59:31 +05:30
54acc2c15d Redesign BpfMap 2025-10-18 14:53:35 +05:30
5c74be041e Reimplement BpfProgram 2025-10-18 13:24:03 +05:30
2b99f01b02 Rework BpfProgram.h, pass BpfObject as shared_ptr to BpfPrograms 2025-10-18 12:36:15 +05:30
763c188fa9 Implement BpfObject class 2025-10-18 04:59:14 +05:30
528a54247d Create bpf_object.h as a container for the object file having bpf progs and maps 2025-10-17 18:12:26 +05:30
8d27a351e0 Add src/core/bpf_perf_buffer 2025-10-17 03:47:31 +05:30
5a3937b7f6 Update README to include responsive image support 2025-10-01 17:47:54 +05:30
6e6e48acdd fix readme formatting 2025-10-01 01:51:31 +05:30
15c171b546 Enhance README with badges and clearer descriptions
Updated README with improved descriptions and badges.
2025-10-01 01:49:42 +05:30
aa8183b28c Merge pull request #3 from pythonbpf/dependabot/github_actions/actions-e3feb9629e
Bump the actions group with 4 updates
2025-10-01 01:31:24 +05:30
80a0afe74f Bump the actions group with 4 updates
Bumps the actions group with 4 updates: [actions/checkout](https://github.com/actions/checkout), [actions/setup-python](https://github.com/actions/setup-python), [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) and [actions/download-artifact](https://github.com/actions/download-artifact).


Updates `actions/checkout` from 4 to 5
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

Updates `actions/setup-python` from 5 to 6
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5...v6)

Updates `pypa/cibuildwheel` from 2.17 to 3.1
- [Release notes](https://github.com/pypa/cibuildwheel/releases)
- [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md)
- [Commits](https://github.com/pypa/cibuildwheel/compare/v2.17...v3.1)

Updates `actions/download-artifact` from 4 to 5
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: pypa/cibuildwheel
  dependency-version: '3.1'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/download-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-29 01:37:02 +00:00
6bea1bc8e5 bump version
Signed-off-by: varun-r-mallya <varunrmallya@gmail.com>
2025-09-26 22:54:13 +05:30
64109fac3b bump version
Signed-off-by: varun-r-mallya <varunrmallya@gmail.com>
2025-09-26 22:50:36 +05:30
e5a946a767 add map support 2025-09-21 23:55:10 +05:30
0a27d5a520 add destroy function 2025-09-21 19:24:01 +05:30
aa3bd82194 fix format errors 2025-09-21 18:12:19 +05:30
ec003a2c0a addd example and support for load and attach 2025-09-21 18:02:49 +05:30
9fb3ab3238 manifest fixing for release working
Signed-off-by: varun-r-mallya <varunrmallya@gmail.com>
2025-09-21 15:35:40 +05:30
596943d246 update version in test 2025-09-21 15:26:05 +05:30
22ad94a64f Merge remote-tracking branch 'origin/master' 2025-09-21 15:22:20 +05:30
20b505c314 Update MANIFEST.in 2025-09-21 15:21:57 +05:30
b827b3132e Update MANIFEST.in 2025-09-21 15:21:12 +05:30
20 changed files with 1004 additions and 102 deletions

View File

@ -15,8 +15,8 @@ jobs:
name: Format name: Format
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- uses: actions/setup-python@v5 - uses: actions/setup-python@v6
with: with:
python-version: "3.x" python-version: "3.x"
- uses: pre-commit/action@v3.0.1 - uses: pre-commit/action@v3.0.1

View File

@ -19,11 +19,11 @@ jobs:
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
with: with:
submodules: true submodules: true
- uses: actions/setup-python@v5 - uses: actions/setup-python@v6
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}

View File

@ -15,7 +15,7 @@ jobs:
name: Build SDist name: Build SDist
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
with: with:
submodules: true submodules: true
@ -39,12 +39,12 @@ jobs:
arch: [x86_64] arch: [x86_64]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
with: with:
submodules: true submodules: true
- name: Build wheels - name: Build wheels
uses: pypa/cibuildwheel@v2.17 uses: pypa/cibuildwheel@v3.2
env: env:
CIBW_PLATFORM: linux CIBW_PLATFORM: linux
CIBW_ARCHS_LINUX: ${{ matrix.arch }} CIBW_ARCHS_LINUX: ${{ matrix.arch }}
@ -80,7 +80,7 @@ jobs:
steps: steps:
- name: Download all artifacts - name: Download all artifacts
uses: actions/download-artifact@v4 uses: actions/download-artifact@v5
with: with:
pattern: cibw-* pattern: cibw-*
path: dist path: dist

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ _generate/
build/ build/
*venv/ *venv/
.idea/ .idea/
sudo-python.sh

View File

@ -1,11 +1,25 @@
cmake_minimum_required(VERSION 4.0) cmake_minimum_required(VERSION 4.0)
project(pylibbpf) project(pylibbpf)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# pybind11 # pybind11
include_directories(${CMAKE_SOURCE_DIR}/src) include_directories(${CMAKE_SOURCE_DIR}/src)
add_subdirectory(pybind11) add_subdirectory(pybind11)
pybind11_add_module(pylibbpf src/core/bpf_program.h src/core/bpf_exception.h pybind11_add_module(
src/bindings/main.cpp src/core/bpf_program.cpp) pylibbpf
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_object.cpp
src/core/bpf_perf_buffer.cpp)
# --- libbpf build rules --- # --- libbpf build rules ---
set(LIBBPF_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libbpf/src) set(LIBBPF_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libbpf/src)

View File

@ -1,8 +1,7 @@
include README.md LICENSE pybind11/LICENSE include README.md LICENSE pybind11/LICENSE
graft pybind11/include graft pybind11/include
graft pybind11/tools graft pybind11/tools
recursive-include src *.cpp *.h include LICENSE
recursive-include libbpf/src *.c *.h Makefile
recursive-include libbpf/include *.h
graft src graft src
graft libbpf
global-include CMakeLists.txt *.cmake global-include CMakeLists.txt *.cmake

View File

@ -1,24 +1,47 @@
# Py-libbpf <picture>
This library provides Python bindings for libbpf on Linux to make loading of eBPF object files easier. This is meant to <source
be used along with `pythonbpf`, the eBPF Python DSL compiler. This library makes it possible to attach these programs to media="(prefers-color-scheme: light)"
events in the kernel right from inside Python. srcset="https://github.com/user-attachments/assets/dbd56f5b-4512-4c82-a404-30bce0ee5207"
width="450"
alt="pylibbpf light mode">
<img
src="https://github.com/user-attachments/assets/9e873d60-a834-4411-bc0c-361b14502f8b"
width="450"
alt="pylibbpf dark mode">
</picture>
<p align="center">
<!-- PyPI -->
<a href="https://www.python.org/downloads/release/python-3080/"><img src="https://img.shields.io/badge/python-3.8-blue.svg"></a>
<a href="https://pypi.org/project/pylibbpf"><img src="https://badge.fury.io/py/pylibbpf.svg"></a>
<!-- <a href="https://pypi.org/project/pythonbpf/"><img src="https://img.shields.io/pypi/status/pythonbpf" alt="PyPI Status"></a> -->
<a href="https://pepy.tech/project/pylibbpf"><img src="https://pepy.tech/badge/pylibbpf" alt="Downloads"></a>
<!-- Build & CI -->
<a href="https://github.com/pythonbpf/pylibbpf/actions"><img src="https://github.com/pythonbpf/pylibbpf/actions/workflows/wheels.yml/badge.svg" alt="Build Status"></a>
<!-- Meta -->
<a href="https://github.com/pythonbpf/pylibbpf/blob/master/LICENSE"><img src="https://img.shields.io/github/license/pythonbpf/pylibbpf" alt="License"></a>
</p>
# Warning This library provides Python bindings for **libbpf** on Linux, making it easier to load eBPF object files. It is designed to be used together with [PythonBPF](https://github.com/pythonbpf/python-bpf), the eBPF compiler for Python. With these bindings, you can attach eBPF programs to kernel events directly from Python.
IN DEVELOPMENT. DO NOT USE. All programs written with this are to be run with a `sudo` Python interpreter.
## Prerequisites > **Note**: This project is under active development and not ready for production use.
## Dependencies
* A compiler with C++11 support * A compiler with C++11 support
* Pip 10+ or CMake >= 4.1 * Pip 10+ or CMake >= 4.1
* Ninja or Pip 10+ * Ninja or Pip 10+
## Installation ## Installation
`pip install pylibbpf`
Just clone this repository and pip install. Note the `--recursive` option which is ## Development
Clone this repository and pip install. Note the `--recursive` option which is
needed for the pybind11 submodule: needed for the pybind11 submodule:
```bash ```bash
sudo apt install libelf-dev
git clone --recursive https://github.com/varun-r-mallya/pylibbpf.git git clone --recursive https://github.com/varun-r-mallya/pylibbpf.git
pip install . pip install .
``` ```

54
examples/execve.py Normal file
View File

@ -0,0 +1,54 @@
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
@bpf
@map
def last() -> HashMap:
return HashMap(key=c_uint64, value=c_uint64, max_entries=3)
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello(ctx: c_void_p) -> c_int32:
print("entered")
print("multi constant support")
return c_int32(0)
@bpf
@section("tracepoint/syscalls/sys_exit_execve")
def hello_again(ctx: c_void_p) -> c_int64:
print("exited")
key = 0
tsp = last().lookup(key)
print(tsp)
return c_int64(0)
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
b = BPF()
b.load_and_attach()
mp = BpfMap(b, last)
mp[42] = 100 # Update entry
value = mp[42] # Lookup entry
del mp[42] # Delete entry
mp[69] = 420
mp[31] = 42
mp[21] = 34
print(mp.items())
# Iterate through map
for key in mp.keys():
print(mp[key])
while True:
print("running")
time.sleep(1)

View File

@ -9,7 +9,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "pylibbpf" name = "pylibbpf"
version = "0.0.2" version = "0.0.5"
description = "Python Bindings for Libbpf" description = "Python Bindings for Libbpf"
authors = [ authors = [
{ name = "r41k0u", email = "pragyanshchaturvedi18@gmail.com" }, { name = "r41k0u", email = "pragyanshchaturvedi18@gmail.com" },
@ -69,9 +69,9 @@ line-length = 88
[tool.ruff.lint] [tool.ruff.lint]
extend-select = [ extend-select = [
"B", # flake8-bugbear "B", # flake8-bugbear
"I", # isort "I", # isort
"PGH", # pygrep-hooks "PGH", # pygrep-hooks
"RUF", # Ruff-specific "RUF", # Ruff-specific
"UP", # pyupgrade "UP", # pyupgrade
] ]

View File

@ -3,10 +3,14 @@
#define MACRO_STRINGIFY(x) STRINGIFY(x) #define MACRO_STRINGIFY(x) STRINGIFY(x)
extern "C" { extern "C" {
#include "libbpf.h" #include <libbpf.h>
} }
#include "core/bpf_object.h"
#include "core/bpf_program.h" #include "core/bpf_program.h"
#include "core/bpf_exception.h" #include "core/bpf_exception.h"
#include "core/bpf_map.h"
#include "core/bpf_perf_buffer.h"
namespace py = pybind11; namespace py = pybind11;
@ -21,20 +25,56 @@ PYBIND11_MODULE(pylibbpf, m) {
:toctree: _generate :toctree: _generate
BpfProgram BpfProgram
BpfMap
BpfException BpfException
)pbdoc"; )pbdoc";
// Register the custom exception // Register the custom exception
py::register_exception<BpfException>(m, "BpfException"); py::register_exception<BpfException>(m, "BpfException");
py::class_<BpfProgram>(m, "BpfProgram") // BpfObject
.def(py::init<const std::string&>()) py::class_<BpfObject, std::shared_ptr<BpfObject>>(m, "BpfObject")
.def(py::init<const std::string&, const std::string&>()) .def(py::init<std::string>(), py::arg("object_path"))
.def("load", &BpfProgram::load) .def("load", &BpfObject::load)
.def("attach", &BpfProgram::attach) .def("is_loaded", &BpfObject::is_loaded)
// .def("detach", &BpfProgram::detach) .def("get_program_names", &BpfObject::get_program_names)
.def("is_loaded", &BpfProgram::is_loaded) .def("get_program", &BpfObject::get_program, py::arg("name"))
.def("is_attached", &BpfProgram::is_attached); .def("attach_all", &BpfObject::attach_all)
.def("get_map_names", &BpfObject::get_map_names)
.def("get_map", &BpfObject::get_map, py::arg("name"));
// BpfProgram
py::class_<BpfProgram, std::shared_ptr<BpfProgram>>(m, "BpfProgram")
.def("attach", &BpfProgram::attach)
.def("detach", &BpfProgram::detach)
.def("is_attached", &BpfProgram::is_attached)
.def("get_name", &BpfProgram::get_name);
// BpfMap
py::class_<BpfMap, std::shared_ptr<BpfMap>>(m, "BpfMap")
.def("lookup", &BpfMap::lookup, py::arg("key"))
.def("update", &BpfMap::update, py::arg("key"), py::arg("value"))
.def("delete_elem", &BpfMap::delete_elem, py::arg("key"))
.def("get_next_key", &BpfMap::get_next_key, py::arg("key") = py::none())
.def("items", &BpfMap::items)
.def("keys", &BpfMap::keys)
.def("values", &BpfMap::values)
.def("get_name", &BpfMap::get_name)
.def("get_fd", &BpfMap::get_fd)
.def("get_type", &BpfMap::get_type)
.def("get_key_size", &BpfMap::get_key_size)
.def("get_value_size", &BpfMap::get_value_size)
.def("get_max_entries", &BpfMap::get_max_entries);
py::class_<BpfPerfBuffer>(m, "BpfPerfBuffer")
.def(py::init<int, int, py::function, py::object>(),
py::arg("map_fd"),
py::arg("page_cnt") = 8,
py::arg("callback"),
py::arg("lost_callback") = py::none())
.def("poll", &BpfPerfBuffer::poll, py::arg("timeout_ms") = -1)
.def("consume", &BpfPerfBuffer::consume);
#ifdef VERSION_INFO #ifdef VERSION_INFO
m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO); m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO);

View File

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

227
src/core/bpf_map.cpp Normal file
View File

@ -0,0 +1,227 @@
#include "bpf_map.h"
#include "bpf_exception.h"
#include "bpf_object.h"
BpfMap::BpfMap(std::shared_ptr<BpfObject> 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);
}
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));
}
}
void BpfMap::delete_elem(const py::object &key) const {
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_);
// 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::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_);
}
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;
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;
}
}
return result;
}
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<uint8_t> buffer) {
std::fill(buffer.begin(), buffer.end(), 0);
if (py::isinstance<py::int_>(obj)) {
if (buffer.size() <= sizeof(uint64_t)) {
uint64_t value = obj.cast<uint64_t>();
std::memcpy(buffer.data(), &value, buffer.size());
} else {
throw BpfException("Integer key/value size exceeds maximum (8 bytes)");
}
} else if (py::isinstance<py::bytes>(obj)) {
std::string bytes_str = obj.cast<std::string>();
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<py::str>(obj)) {
std::string str_val = obj.cast<std::string>();
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<const uint8_t> 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<const char *>(data.data()), data.size());
}
}

72
src/core/bpf_map.h Normal file
View File

@ -0,0 +1,72 @@
#ifndef PYLIBBPF_BPF_MAP_H
#define PYLIBBPF_BPF_MAP_H
#include <algorithm>
#include <array>
#include <cerrno>
#include <cstring>
#include <libbpf.h>
#include <pybind11/pybind11.h>
#include <span>
#include <string>
#include <vector>
class BpfObject;
namespace py = pybind11;
class BpfMap {
private:
std::weak_ptr<BpfObject> parent_obj_;
struct bpf_map *map_;
int map_fd_;
std::string map_name_;
__u32 key_size_, value_size_;
template <size_t StackSize = 64> struct BufferManager {
std::array<uint8_t, StackSize> stack_buf;
std::vector<uint8_t> heap_buf;
std::span<uint8_t> get_span(size_t size) {
if (size <= StackSize) {
return std::span<uint8_t>(stack_buf.data(), size);
} else {
heap_buf.resize(size);
return std::span<uint8_t>(heap_buf);
}
}
};
public:
BpfMap(std::shared_ptr<BpfObject> 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::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;
private:
static void python_to_bytes_inplace(const py::object &obj,
std::span<uint8_t> buffer);
static py::object bytes_to_python(std::span<const uint8_t> data);
};
#endif // PYLIBBPF_MAPS_H

257
src/core/bpf_object.cpp Normal file
View File

@ -0,0 +1,257 @@
#include "bpf_object.h"
#include "bpf_exception.h"
#include "bpf_map.h"
#include "bpf_program.h"
#include <cerrno>
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_)) {
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() {
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<BpfProgram>
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<BpfProgram>(shared_from_this(), prog, prog_name);
prog_cache_[prog_name] = bpf_prog;
return bpf_prog;
}
std::shared_ptr<BpfProgram> 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<BpfProgram>(shared_from_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 &entry : prog_cache_) {
programs[entry.first.c_str()] = entry.second;
}
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() {
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<BpfMap> 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<BpfMap>(shared_from_this(), raw_map, name);
maps_cache_[name] = map;
return map;
}
std::shared_ptr<BpfMap> 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<BpfMap>(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;
}
py::dict BpfObject::get_cached_maps() const {
py::dict maps;
for (const auto &entry : maps_cache_) {
maps[entry.first.c_str()] = entry.second;
}
return maps;
}

82
src/core/bpf_object.h Normal file
View File

@ -0,0 +1,82 @@
#ifndef PYLIBBPF_BPF_OBJECT_H
#define PYLIBBPF_BPF_OBJECT_H
#include <libbpf.h>
#include <memory>
#include <pybind11/pybind11.h>
#include <string>
#include <unordered_map>
#include <vector>
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 : public std::enable_shared_from_this<BpfObject> {
private:
struct bpf_object *obj_;
std::string object_path_;
bool loaded_;
mutable std::unordered_map<std::string, std::shared_ptr<BpfMap>> maps_cache_;
mutable std::unordered_map<std::string, std::shared_ptr<BpfProgram>>
prog_cache_;
std::shared_ptr<BpfProgram> _get_or_create_program(struct bpf_program *prog);
std::shared_ptr<BpfMap> _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_; }
/**
* Attach all programs in the object.
*/
py::dict attach_all();
// Program access
[[nodiscard]] py::list get_program_names();
[[nodiscard]] std::shared_ptr<BpfProgram>
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();
[[nodiscard]] std::shared_ptr<BpfMap> get_map(const std::string &name);
[[nodiscard]] struct bpf_map *find_map_by_name(const std::string &name) const;
[[nodiscard]] py::dict get_cached_maps() const;
};
#endif // PYLIBBPF_BPF_OBJECT_H

View File

@ -0,0 +1,80 @@
#include "bpf_perf_buffer.h"
#include "bpf_exception.h"
BpfPerfBuffer::BpfPerfBuffer(int map_fd, int page_cnt, py::function callback,
py::object lost_callback)
: pb_(nullptr), callback_(std::move(callback)),
lost_callback_(lost_callback) {
if (page_cnt <= 0 || (page_cnt & (page_cnt - 1)) != 0) {
throw BpfException("page_cnt must be a positive power of 2");
}
struct perf_buffer_opts pb_opts = {};
pb_opts.sz = sizeof(pb_opts); // Required for forward compatibility
pb_ = perf_buffer__new(
map_fd, page_cnt,
sample_callback_wrapper, // sample_cb
lost_callback.is_none() ? nullptr : lost_callback_wrapper, // lost_cb
this, // ctx
&pb_opts // opts
);
if (!pb_) {
throw BpfException("Failed to create perf buffer: " +
std::string(std::strerror(errno)));
}
}
BpfPerfBuffer::~BpfPerfBuffer() {
if (pb_) {
perf_buffer__free(pb_);
}
}
void BpfPerfBuffer::sample_callback_wrapper(void *ctx, int cpu, void *data,
unsigned int size) {
auto *self = static_cast<BpfPerfBuffer *>(ctx);
// Acquire GIL for Python calls
py::gil_scoped_acquire acquire;
try {
// Convert data to Python bytes
py::bytes py_data(static_cast<const char *>(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<BpfPerfBuffer *>(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();
}
}
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_);
}

View File

@ -0,0 +1,31 @@
#ifndef PYLIBBPF_BPF_PERF_BUFFER_H
#define PYLIBBPF_BPF_PERF_BUFFER_H
#include <libbpf.h>
#include <pybind11/functional.h>
#include <pybind11/pybind11.h>
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 = py::none());
~BpfPerfBuffer();
int poll(int timeout_ms);
int consume();
[[nodiscard]] int fd() const;
};
#endif // PYLIBBPF_BPF_PERF_BUFFER_H

View File

@ -1,58 +1,73 @@
#include "bpf_program.h" #include "bpf_program.h"
#include "bpf_exception.h" #include "bpf_exception.h"
#include <filesystem> #include "bpf_object.h"
#include <cerrno>
#include <utility>
BpfProgram::BpfProgram(const std::string& object_path, const std::string& program_name) BpfProgram::BpfProgram(std::shared_ptr<BpfObject> parent,
: obj_(nullptr), prog_(nullptr), link_(nullptr), struct bpf_program *raw_prog,
object_path_(object_path), program_name_(program_name) { 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");
} }
BpfProgram::~BpfProgram() { BpfProgram::~BpfProgram() { detach(); }
//TODO: detach here as well
if (obj_) { BpfProgram::BpfProgram(BpfProgram &&other) noexcept
bpf_object__close(obj_); : 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() { BpfProgram &BpfProgram::operator=(BpfProgram &&other) noexcept {
// Open the eBPF object file if (this != &other) {
obj_ = bpf_object__open_file(object_path_.c_str(), nullptr); detach();
if (libbpf_get_error(obj_)) {
throw BpfException("Failed to open BPF object file: " + object_path_);
}
// Find the program by name (if specified) parent_obj_ = std::move(other.parent_obj_);
if (!program_name_.empty()) { prog_ = other.prog_;
prog_ = bpf_object__find_program_by_name(obj_, program_name_.c_str()); link_ = other.link_;
if (!prog_) { program_name_ = std::move(other.program_name_);
throw BpfException("Program '" + program_name_ + "' not found in object");
}
} else {
// Use the first program if no name specified
prog_ = bpf_object__next_program(obj_, nullptr);
if (!prog_) {
throw BpfException("No programs found in object file");
}
}
// Load the eBPF object into the kernel other.prog_ = nullptr;
if (bpf_object__load(obj_)) { other.link_ = nullptr;
throw BpfException("Failed to load BPF object into kernel"); }
} return *this;
return true;
} }
bool BpfProgram::attach() { void BpfProgram::attach() {
if (!prog_) { // Check if parent is still alive
throw BpfException("Program not loaded"); auto parent = parent_obj_.lock();
} if (!parent) {
throw BpfException("Parent BpfObject has been destroyed");
}
link_ = bpf_program__attach(prog_); if (link_) {
if (libbpf_get_error(link_)) { throw BpfException("Program '" + program_name_ + "' already attached");
link_ = nullptr; }
throw BpfException("Failed to attach BPF program");
}
return true; 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;
}
} }

View File

@ -1,29 +1,37 @@
#ifndef PYLIBBPF_BPF_PROGRAM_H #ifndef PYLIBBPF_BPF_PROGRAM_H
#define PYLIBBPF_BPF_PROGRAM_H #define PYLIBBPF_BPF_PROGRAM_H
#include "libbpf.h" #include <cstring>
#include <pybind11/stl.h> #include <libbpf.h>
#include <memory>
#include <string> #include <string>
namespace py = pybind11; class BpfObject;
class BpfProgram { class BpfProgram {
private: private:
struct bpf_object* obj_; std::weak_ptr<BpfObject> parent_obj_;
struct bpf_program* prog_; struct bpf_program *prog_;
struct bpf_link* link_; struct bpf_link *link_;
std::string object_path_; std::string program_name_;
std::string program_name_;
public: public:
explicit BpfProgram(const std::string& object_path, const std::string& program_name = ""); explicit BpfProgram(std::shared_ptr<BpfObject> parent,
~BpfProgram(); struct bpf_program *raw_prog,
const std::string &program_name);
bool load(); ~BpfProgram();
bool attach();
bool is_loaded() const { return obj_ != nullptr; } BpfProgram(const BpfProgram &) = delete;
bool is_attached() const { return link_ != nullptr; } BpfProgram &operator=(const BpfProgram &) = delete;
BpfProgram(BpfProgram &&) noexcept;
BpfProgram &operator=(BpfProgram &&) noexcept;
void attach();
void detach();
[[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

View File

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