75 Commits

Author SHA1 Message Date
8ac2d67a76 Make bytes_to_python const non-static 2025-11-27 03:55:58 +05:30
d4202cdc88 Add basic userspace support for struct value type for maps 2025-11-27 03:43:24 +05:30
3335e0471b Return None instead of KeyError for failure of BpfMap::lookup for missing key 2025-11-24 23:51:08 +05:30
ffc5a9f15f Add .so and .pyd wildcards to setuptools package-data in pyproject.toml 2025-11-11 17:31:09 +05:30
503e6da987 Janitorial formatting 2025-11-11 17:28:30 +05:30
8585f170cb Remove debug prints from test, fix name of Pip workflow 2025-11-11 17:21:30 +05:30
0780615fc5 Config setuptools to get library location 2025-11-11 17:12:31 +05:30
c26617d64f Overhaul pip.yml 2025-11-11 16:57:30 +05:30
303fe4e6c8 Make wheels workflow test oneline 2025-11-11 16:35:26 +05:30
9c18f5967d Fix more 2025-11-11 16:28:15 +05:30
434f145f9b Add better test to wheels workflow 2025-11-11 16:20:51 +05:30
b3e9410e0d Fix wheels workflow 2025-11-11 10:51:13 +05:30
fd739e87c1 Don't run pytest in isolated mode in pip workflow 2025-11-11 10:37:28 +05:30
87c698e940 Print pylibbpf dir in test 2025-11-11 10:29:48 +05:30
298df7ede6 Update workflows to treat Python 3.12 as minimum version 2025-11-11 10:23:58 +05:30
b48f6a8a97 Bump minimum python version to 3.12 2025-11-11 10:22:06 +05:30
6e4dc0f5aa Bump setuptools version in setup.py to use new licensing format 2025-11-11 10:13:41 +05:30
a51bed14ca Fix LICENSE and metadata in pyproject.toml 2025-11-11 10:04:13 +05:30
3a85a6446e Add Python discovery to CMakeLists.txt 2025-11-11 10:03:48 +05:30
6bc378defd Remove unnecessary metadata, fix CMake flag in setup.py 2025-11-11 10:02:13 +05:30
5c1071fac0 Merge pull request #4 from pythonbpf/perfbuf
Add userspace utils for Structs and maps, starting with PERF_EVENT_ARRAY
2025-10-20 06:14:45 +05:30
f99de9981c Fix includes for PerfEventArray 2025-10-20 06:09:07 +05:30
fa5d181e1a Fix includes for BpfProgram 2025-10-20 06:08:50 +05:30
867f142a7f Fix includes for BpfObject 2025-10-20 06:08:36 +05:30
8cc8f4267a Fix includes for BpfMap 2025-10-20 06:08:16 +05:30
ff427c2e61 Fix includes for bindings 2025-10-20 06:07:57 +05:30
fb82b609f9 Fix BpfMap includes 2025-10-20 05:58:55 +05:30
88716ce19a Fill missing fields in BpfObject's move constructor 2025-10-20 05:47:40 +05:30
003495e833 Make lost_callback type asfe in PerfEventArray 2025-10-20 05:22:31 +05:30
eebfe61ccc fix lost_callback_wrapper, clang-format 2025-10-20 05:17:40 +05:30
ec5377ba14 Remove unnecessary GIL acquisition in PerfEventArray 2025-10-20 05:14:44 +05:30
a3c3dbe141 Fix BpfMap header guard 2025-10-20 05:09:34 +05:30
3085e8155d Add __delitem__ for BpfMap in bindings 2025-10-20 05:08:15 +05:30
dd552de02c Add memory header to maps/perf_event_array.h 2025-10-20 05:04:19 +05:30
638533c19a Fix test for pip GH workflow 2025-10-20 04:48:46 +05:30
c9a152adc3 Add __version__ to __init__ 2025-10-20 04:22:14 +05:30
92e92f134a Add _make_repr to ir_to_ctypes 2025-10-20 04:18:38 +05:30
b7aa0807c5 Fix pre-commit conditions 2025-10-20 04:05:14 +05:30
ddbbce400e Use c_char type for Int8 arrays in ir_to_ctypes 2025-10-20 03:48:02 +05:30
c580aab1c4 Move Python files to pylibbpf/ 2025-10-20 03:46:30 +05:30
470afc5174 Import find_packages in setup.py 2025-10-20 03:45:59 +05:30
495318f622 Update pyproject.toml 2025-10-20 03:09:51 +05:30
bbb39898ab Fix setup.py 2025-10-20 03:06:51 +05:30
23cafa4d7b Expose classes and perform struct conversion in __init__ 2025-10-20 02:58:22 +05:30
30021e8520 Add PerfEventArray and BpfObject wrappers 2025-10-20 02:57:57 +05:30
0e454bd7d7 Add IR Types to CTypes struct convertor 2025-10-20 02:57:25 +05:30
eda08b05d7 lint fix to CMakeLists 2025-10-20 02:04:38 +05:30
1eb7ed460e Fix Bindings and PerfEventArray 2025-10-20 00:28:27 +05:30
8babf3087b Add parser and parent shared_ptr to PerfEventArray 2025-10-20 00:10:54 +05:30
cbfe6ae95e Rename BpfPerfBuffer to PerfEventArray, add struct_parser to BpfObject as a shared_ptr 2025-10-19 22:34:10 +05:30
05d5bba4f7 Add StructParser utility 2025-10-19 22:02:19 +05:30
f7874137ad Enable BpfMap to be shared, add get_parent 2025-10-19 21:33:24 +05:30
b4d0a49883 Add struct_defs_ to BpfObject 2025-10-19 21:26:20 +05:30
874d567825 Move BpfPerfBuffer under src/maps 2025-10-19 18:47:35 +05:30
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
25 changed files with 1426 additions and 464 deletions

View File

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

View File

@ -1,29 +1,38 @@
name: Pip
on:
workflow_dispatch:
pull_request:
push:
branches:
- master
- main
branches: [main, master]
pull_request:
branches: [main, master]
workflow_dispatch:
jobs:
build:
test:
name: Test on Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest]
python-version: ["3.8", "3.12"]
runs-on: ${{ matrix.platform }}
python-version: ['3.12', '3.13']
steps:
- uses: actions/checkout@v4
- name: Checkout code
uses: actions/checkout@v5
with:
submodules: true
submodules: recursive
- uses: actions/setup-python@v5
- name: Verify submodules
run: |
echo "Checking submodule status:"
git submodule status
echo "Checking libbpf directory:"
ls -la libbpf/
echo "Checking libbpf/src:"
ls -la libbpf/src/ || echo "libbpf/src not found!"
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
@ -33,19 +42,72 @@ jobs:
sudo apt-get install -y \
libbpf-dev \
libelf-dev \
linux-headers-generic \
zlib1g-dev \
build-essential \
clang \
cmake \
ninja-build
ninja-build \
pkg-config \
git \
make
- name: Add requirements
run: python -m pip install --upgrade pip wheel setuptools
- name: Install Python build dependencies
run: |
python -m pip install --upgrade pip
pip install --upgrade "setuptools>=77.0.0" wheel
pip install cmake ninja pybind11
- name: Build and install
run: pip install --verbose .[test]
- name: Check build requirements
run: |
echo "Python version:"
python --version
echo "CMake version:"
cmake --version
echo "Ninja version:"
ninja --version
echo "Setuptools version:"
python -c "import setuptools; print(setuptools.__version__)"
- name: Test import
run: python -c "import pylibbpf; print('Import successful')"
- name: Build and install pylibbpf (verbose)
run: |
pip install -v -e . 2>&1 | tee build.log
continue-on-error: false
- name: Test
run: python -m pytest -v
- name: Check build output
run: |
echo "Build directory contents:"
find build -type f -name "*.so" 2>/dev/null || echo "No .so files found in build/"
echo ""
echo "Looking for pylibbpf extension:"
find . -name "pylibbpf*.so" -o -name "pylibbpf*.pyd"
echo ""
echo "Site-packages contents:"
python -c "import site; print(site.getsitepackages())"
ls -la $(python -c "import site; print(site.getsitepackages()[0])")/pylibbpf/ || echo "pylibbpf not in site-packages"
- name: Try importing extension directly
run: |
python -c "
import sys
print('Python path:', sys.path)
try:
from pylibbpf import pylibbpf
print('Successfully imported pylibbpf.pylibbpf')
print('pylibbpf.pylibbpf members:', dir(pylibbpf))
except ImportError as e:
print(f'Failed to import pylibbpf.pylibbpf: {e}')
"
- name: Verify extension loaded
run: |
python -c "import pylibbpf; print('Members:', dir(pylibbpf)); assert hasattr(pylibbpf, 'BpfObject'), 'BpfObject not found!'; print('✓ OK')"
- name: Install test dependencies
if: success()
run: |
pip install pytest pytest-cov
- name: Run tests
if: success()
run: |
python -I -m pytest -v -s --cov=pylibbpf --cov-report=term-missing

View File

@ -15,9 +15,9 @@ jobs:
name: Build SDist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: true
submodules: recursive
- name: Build SDist
run: pipx run build --sdist
@ -39,24 +39,26 @@ jobs:
arch: [x86_64]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: true
submodules: recursive
- name: Build wheels
uses: pypa/cibuildwheel@v2.17
uses: pypa/cibuildwheel@v3.2
env:
CIBW_PLATFORM: linux
CIBW_ARCHS_LINUX: ${{ matrix.arch }}
CIBW_BUILD: "cp38-* cp311-*"
CIBW_BUILD: "cp312-* cp313-*"
CIBW_SKIP: "*-musllinux*"
CIBW_TEST_COMMAND: "python -c 'import pylibbpf; print(f\"pylibbpf {pylibbpf.__version__} imported successfully\")'"
CIBW_TEST_COMMAND: "python -c 'import pylibbpf; assert hasattr(pylibbpf, \"BpfObject\"), \"BpfObject not found\"; print(f\"pylibbpf {pylibbpf.__version__} OK\")'"
CIBW_TEST_SKIP: "*-linux_aarch64"
CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28
CIBW_BEFORE_ALL_LINUX: |
dnf install -y elfutils-libelf-devel zlib-devel
dnf install -y elfutils-libelf-devel zlib-devel make gcc gcc-c++ git
CIBW_BEFORE_BUILD: >
pip install --upgrade "setuptools>=77.0.0" wheel cmake ninja pybind11
- name: Verify clean directory
run: git diff --exit-code
@ -80,7 +82,7 @@ jobs:
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
pattern: cibw-*
path: dist

View File

@ -1,17 +1,36 @@
cmake_minimum_required(VERSION 4.0)
project(pylibbpf)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(
Python
COMPONENTS Interpreter Development.Module
REQUIRED)
# pybind11
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/bindings/main.cpp
src/core/bpf_object.h
src/core/bpf_program.cpp
src/core/bpf_map.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)
# --- libbpf build rules ---
set(LIBBPF_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libbpf/src)

View File

@ -1,24 +1,43 @@
# Py-libbpf
<picture>
<source
media="(prefers-color-scheme: light)"
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">
<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>
<!-- 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>
This library provides Python bindings for libbpf on Linux to make loading of eBPF object files easier. This is meant to
be used along with `pythonbpf`, the eBPF Python DSL compiler. This library makes it possible to attach these programs to
events in the kernel right from inside Python.
# IN DEVELOPMENT. DO NOT USE.
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.
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
* Pip 10+ or CMake >= 4.1
* Ninja or Pip 10+
## 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:
```bash
@ -30,11 +49,5 @@ pip install .
With the `setup.py` file included in this example, the `pip install` command will
invoke CMake and build the pybind11 module as specified in `CMakeLists.txt`.
## Development
Do this before running to make sure Python can manipulate bpf programs without sudo
```bash
sudo setcap cap_bpf,cap_sys_admin+ep /usr/bin/python3.12
```
## Building the documentation
The documentation here is still boilerplate.

View File

@ -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

46
pylibbpf/__init__.py Normal file
View File

@ -0,0 +1,46 @@
import logging
from .ir_to_ctypes import convert_structs_to_ctypes, is_pythonbpf_structs
from .pylibbpf import (
BpfException,
BpfMap,
BpfProgram,
PerfEventArray,
StructParser,
)
from .pylibbpf import (
BpfObject as _BpfObject, # C++ object (internal)
)
from .wrappers import BpfObjectWrapper
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",
]
__version__ = "0.0.6"

104
pylibbpf/ir_to_ctypes.py Normal file
View File

@ -0,0 +1,104 @@
import ctypes
import logging
from llvmlite import ir
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 = 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
else:
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:
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))
repr_func = _make_repr(struct_name, fields)
struct_class = type(
struct_name,
(ctypes.Structure,),
{
"_fields_": fields,
"__module__": "pylibbpf.ir_to_ctypes",
"__doc__": f"Auto-generated ctypes structure for {struct_name}",
"__repr__": repr_func,
},
)
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"]

0
pylibbpf/py.typed Normal file
View File

77
pylibbpf/wrappers.py Normal file
View File

@ -0,0 +1,77 @@
from collections.abc import Callable
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: Callable | None = 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)

View File

@ -1,23 +1,24 @@
[build-system]
requires = [
"setuptools>=42",
"setuptools>=77",
"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" },
{ name = "varun-r-mallya", email = "varunrmallya@gmail.com" }
]
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.8"
license = "Apache-2.0"
requires-python = ">=3.12"
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
@ -28,22 +29,26 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: C++",
"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"
python_version = "3.8"
python_version = "3.12"
strict = true
show_error_codes = true
enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
@ -64,7 +69,7 @@ filterwarnings = [
testpaths = ["tests"]
[tool.ruff]
target-version = "py38"
target-version = "py312"
line-length = 88
[tool.ruff.lint]
@ -75,3 +80,7 @@ extend-select = [
"RUF", # Ruff-specific
"UP", # pyupgrade
]
[tool.setuptools]
packages = ["pylibbpf"]
package-data = {"pylibbpf" = ["*.py", "*.so", "*.pyd", "py.typed"]}

View File

@ -6,14 +6,6 @@ from pathlib import Path
from setuptools import Extension, setup
from setuptools.command.build_ext import build_ext
# Convert distutils Windows platform specifiers to CMake -A arguments
PLAT_TO_CMAKE = {
"win32": "Win32",
"win-amd64": "x64",
"win-arm32": "ARM",
"win-arm64": "ARM64",
}
# A CMakeExtension needs a sourcedir instead of a file list.
# The name must be the _single_ output extension from the CMake build.
@ -45,8 +37,8 @@ class CMakeBuild(build_ext):
# from Python.
cmake_args = [
f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}{os.sep}",
f"-DPYTHON_EXECUTABLE={sys.executable}",
f"-DCMAKE_BUILD_TYPE={cfg}", # not used on MSVC, but no harm
f"-DPython_EXECUTABLE={sys.executable}",
]
build_args = []
@ -122,31 +114,6 @@ if readme_path.exists():
long_description = readme_path.read_text(encoding="utf-8")
setup(
name="pylibbpf",
version="0.0.1",
author="varun-r-mallya, r41k0u",
author_email="varunrmallyagmail.com",
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")],
ext_modules=[CMakeExtension("pylibbpf.pylibbpf")],
cmdclass={"build_ext": CMakeBuild},
zip_safe=False,
classifiers=[
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: C++",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: System :: Operating System Kernels :: Linux",
],
extras_require={"test": ["pytest>=6.0"]},
python_requires=">=3.8",
)

View File

@ -2,18 +2,17 @@
#define STRINGIFY(x) #x
#define MACRO_STRINGIFY(x) STRINGIFY(x)
extern "C" {
#include <libbpf.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
-----------------------
@ -27,41 +26,77 @@ PYBIND11_MODULE(pylibbpf, m) {
BpfException
)pbdoc";
// Register the custom exception
py::register_exception<BpfException>(m, "BpfException");
// Register the custom exception
py::register_exception<BpfException>(m, "BpfException");
py::class_<BpfProgram>(m, "BpfProgram")
.def(py::init<const std::string &>())
.def(py::init<const std::string &, const std::string &>())
.def("load", &BpfProgram::load)
.def("attach", &BpfProgram::attach)
.def("destroy", &BpfProgram::destroy)
.def("load_and_attach", &BpfProgram::load_and_attach)
.def("is_loaded", &BpfProgram::is_loaded)
.def("is_attached", &BpfProgram::is_attached);
// BpfObject
py::class_<BpfObject, std::shared_ptr<BpfObject>>(m, "BpfObject")
.def(py::init<std::string, py::dict>(), 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"));
py::class_<BpfMap>(m, "BpfMap")
.def(py::init<BpfProgram *, py::object &>())
.def("lookup", &BpfMap::lookup)
.def("update", &BpfMap::update)
.def("delete", &BpfMap::delete_elem)
.def("get_next_key", &BpfMap::get_next_key, py::arg("key") = py::none())
.def("items", &BpfMap::items)
.def("keys", &BpfMap::keys)
.def("values", &BpfMap::values)
.def("get_name", &BpfMap::get_name)
.def("get_type", &BpfMap::get_type)
.def("get_key_size", &BpfMap::get_key_size)
.def("get_value_size", &BpfMap::get_value_size)
.def("get_max_entries", &BpfMap::get_max_entries)
.def("__getitem__", &BpfMap::lookup)
.def("__setitem__", &BpfMap::update)
.def("__delitem__", &BpfMap::delete_elem);
// BpfProgram
py::class_<BpfProgram, std::shared_ptr<BpfProgram>>(m, "BpfProgram")
.def("attach", &BpfProgram::attach)
.def("detach", &BpfProgram::detach)
.def("is_attached", &BpfProgram::is_attached)
.def("get_name", &BpfProgram::get_name);
// BpfMap
py::class_<BpfMap, std::shared_ptr<BpfMap>>(m, "BpfMap")
.def("lookup", &BpfMap::lookup, py::arg("key"))
.def("update", &BpfMap::update, py::arg("key"), py::arg("value"))
.def("delete_elem", &BpfMap::delete_elem, py::arg("key"))
.def("get_next_key", &BpfMap::get_next_key, py::arg("key") = py::none())
.def("items", &BpfMap::items)
.def("keys", &BpfMap::keys)
.def("values", &BpfMap::values)
.def("get_name", &BpfMap::get_name)
.def("get_fd", &BpfMap::get_fd)
.def("get_type", &BpfMap::get_type)
.def("get_key_size", &BpfMap::get_key_size)
.def("get_value_size", &BpfMap::get_value_size)
.def("get_max_entries", &BpfMap::get_max_entries)
.def("set_value_struct", &BpfMap::set_value_struct,
py::arg("struct_name"))
.def("get_value_struct_name", &BpfMap::get_value_struct_name)
.def("has_struct_value", &BpfMap::has_struct_value)
.def("__getitem__", &BpfMap::lookup, py::arg("key"))
.def("__setitem__", &BpfMap::update, py::arg("key"), py::arg("value"))
.def("__delitem__", &BpfMap::delete_elem, py::arg("key"));
// StructParser
py::class_<StructParser>(m, "StructParser")
.def(py::init<py::dict>(), 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_<PerfEventArray, std::shared_ptr<PerfEventArray>>(m,
"PerfEventArray")
.def(py::init<std::shared_ptr<BpfMap>, int, py::function, py::object>(),
py::arg("map"), py::arg("page_cnt"), py::arg("callback"),
py::arg("lost_callback") = py::none())
.def(py::init<std::shared_ptr<BpfMap>, 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
}

View File

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

View File

@ -1,211 +1,255 @@
#include "bpf_map.h"
#include "core/bpf_map.h"
#include "core/bpf_exception.h"
#include "core/bpf_object.h"
#include "utils/struct_parser.h"
#include <algorithm>
#include <cerrno>
#include <cstring>
#include "bpf_exception.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");
BpfMap::BpfMap(BpfProgram *program_, const py::object &map_from_python) {
if (py::isinstance<py::function>(map_from_python)) {
const auto name = map_from_python.attr("__name__").cast<std::string>();
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.");
}
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<uint8_t> BpfMap::python_to_bytes(const py::object &obj, size_t size) {
std::vector<uint8_t> result(size, 0);
void BpfMap::set_value_struct(const std::string &struct_name) {
auto parent = parent_obj_.lock();
if (!parent) {
throw BpfException("Parent BpfObject no longer exists");
}
if (py::isinstance<py::int_>(obj)) {
const auto value = obj.cast<uint64_t>();
std::memcpy(result.data(), &value, std::min(size, sizeof(uint64_t)));
} else if (py::isinstance<py::bytes>(obj)) {
const auto bytes_str = obj.cast<std::string>();
std::memcpy(result.data(), bytes_str.data(), std::min(size, bytes_str.size()));
} else if (py::isinstance<py::str>(obj)) {
const auto str_val = obj.cast<std::string>();
std::memcpy(result.data(), str_val.data(), std::min(size, str_val.size()));
}
struct_parser_ = parent->get_struct_parser();
if (!struct_parser_) {
throw BpfException("No struct definitions available in BpfObject");
}
return result;
}
if (!struct_parser_->has_struct(struct_name)) {
throw BpfException("Unknown struct: " + struct_name);
}
py::object BpfMap::bytes_to_python(const std::vector<uint8_t> &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<const char *>(data.data()), data.size());
}
}
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_);
const auto key_bytes = python_to_bytes(key, key_size);
const auto value_bytes = python_to_bytes(value, value_size);
const int ret = bpf_map__update_elem(
map_,
key_bytes.data(),
key_size,
value_bytes.data(),
value_size,
BPF_ANY);
if (ret != 0) {
throw BpfException("Failed to update map element");
}
}
void BpfMap::delete_elem(const py::object &key) const {
const size_t key_size = bpf_map__key_size(map_);
std::vector<uint8_t> key_bytes;
key_bytes = python_to_bytes(key, key_size);
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");
}
}
py::list BpfMap::get_next_key(const py::object &key) const {
const size_t key_size = bpf_map__key_size(map_);
std::vector<uint8_t> next_key(key_size);
int ret;
if (key.is_none()) {
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);
}
py::list result;
if (ret == 0) {
result.append(bytes_to_python(next_key));
}
return result;
}
py::list BpfMap::keys() const {
py::list result;
const size_t key_size = bpf_map__key_size(map_);
std::vector<uint8_t> key(key_size);
std::vector<uint8_t> 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;
}
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<uint8_t> key(key_size);
std::vector<uint8_t> next_key(key_size);
std::vector<uint8_t> 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));
}
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<uint8_t> key(key_size);
std::vector<uint8_t> next_key(key_size);
std::vector<uint8_t> 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);
}
// Get next key
ret = bpf_map__get_next_key(map_, key.data(), next_key.data(), key_size);
key = next_key;
}
return result;
value_struct_name_ = struct_name;
}
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_);
if (map_fd_ < 0)
throw BpfException("Map '" + map_name_ + "' is not initialized properly");
const auto key_bytes = python_to_bytes(key, key_size);
std::vector<uint8_t> value_bytes(value_size);
BufferManager<> key_buf, value_buf;
auto key_span = key_buf.get_span(key_size_);
auto value_span = value_buf.get_span(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();
// 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)
return py::none();
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()));
}
return bytes_to_python(value_bytes);
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) const {
// NOTE: Struct parsing for value type
if (struct_parser_ && !value_struct_name_.empty()) {
py::bytes py_data(reinterpret_cast<const char *>(data.data()), data.size());
return struct_parser_->parse(value_struct_name_, py_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());
}
}

View File

@ -1,55 +1,85 @@
#ifndef PYLIBBPF_MAPS_H
#define PYLIBBPF_MAPS_H
#ifndef PYLIBBPF_BPF_MAP_H
#define PYLIBBPF_BPF_MAP_H
#include <array>
#include <libbpf.h>
#include <pybind11/pybind11.h>
#include <vector>
#include <span>
#include <string>
#include <vector>
#include "bpf_program.h"
class BpfObject;
class StructParser;
namespace py = pybind11;
class BpfMap {
class BpfMap : public std::enable_shared_from_this<BpfMap> {
private:
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;
std::weak_ptr<BpfObject> parent_obj_;
struct bpf_map *map_;
int map_fd_;
std::string map_name_;
__u32 key_size_, value_size_;
// TODO: For now, we'll only support struct parsing for value types
// later we can extend this to keys
std::shared_ptr<StructParser> struct_parser_;
std::string value_struct_name_;
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(BpfProgram *program_, const py::object &map_from_python);
BpfMap(std::shared_ptr<BpfObject> parent, struct bpf_map *raw_map,
const std::string &map_name);
~BpfMap() = default;
~BpfMap() = default;
[[nodiscard]] py::object lookup(const py::object &key) const;
BpfMap(const BpfMap &) = delete;
BpfMap &operator=(const BpfMap &) = delete;
BpfMap(BpfMap &&) noexcept = default;
BpfMap &operator=(BpfMap &&) noexcept = default;
void update(const py::object &key, const py::object &value) 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;
void set_value_struct(const std::string &struct_name);
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 { return key_size_; };
[[nodiscard]] int get_value_size() const { return value_size_; };
[[nodiscard]] int get_max_entries() const;
[[nodiscard]] std::shared_ptr<BpfObject> get_parent() const {
return parent_obj_.lock();
}
[[nodiscard]] std::string get_value_struct_name() const {
return value_struct_name_;
}
[[nodiscard]] bool has_struct_value() const {
return !value_struct_name_.empty();
}
private:
static std::vector<uint8_t> python_to_bytes(const py::object &obj, size_t size);
static py::object bytes_to_python(const std::vector<uint8_t> &data);
static void python_to_bytes_inplace(const py::object &obj,
std::span<uint8_t> buffer);
py::object bytes_to_python(std::span<const uint8_t> data) const;
};
#endif //PYLIBBPF_MAPS_H
#endif // PYLIBBPF_BPF_MAP_H

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

@ -0,0 +1,272 @@
#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 <cerrno>
#include <cstring>
#include <utility>
BpfObject::BpfObject(std::string object_path, py::dict structs)
: obj_(nullptr), object_path_(std::move(object_path)), loaded_(false),
struct_defs_(structs), struct_parser_(nullptr) {}
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_(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;
}
BpfObject &BpfObject::operator=(BpfObject &&other) noexcept {
if (this != &other) {
prog_cache_.clear();
maps_cache_.clear();
if (obj_) {
bpf_object__close(obj_);
}
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_);
}
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;
}
std::shared_ptr<StructParser> BpfObject::get_struct_parser() const {
if (!struct_parser_ && !struct_defs_.empty()) {
// Create parser on first access
struct_parser_ = std::make_shared<StructParser>(struct_defs_);
}
return struct_parser_;
}

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

@ -0,0 +1,88 @@
#ifndef PYLIBBPF_BPF_OBJECT_H
#define PYLIBBPF_BPF_OBJECT_H
#include <libbpf.h>
#include <memory>
#include <pybind11/pybind11.h>
#include <string>
#include <unordered_map>
namespace py = pybind11;
class BpfProgram;
class BpfMap;
class StructParser;
/**
* 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_;
py::dict struct_defs_;
mutable std::shared_ptr<StructParser> struct_parser_;
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, py::dict structs = py::dict());
~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;
// Struct parsing
[[nodiscard]] py::dict get_struct_defs() const { return struct_defs_; }
[[nodiscard]] std::shared_ptr<StructParser> get_struct_parser() const;
};
#endif // PYLIBBPF_BPF_OBJECT_H

View File

@ -1,84 +1,74 @@
#include "bpf_program.h"
#include "bpf_exception.h"
#include <filesystem>
#include "core/bpf_program.h"
#include "core/bpf_exception.h"
#include "core/bpf_object.h"
#include <cerrno>
#include <cstring>
#include <utility>
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<BpfObject> 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");
}
BpfProgram::~BpfProgram() {
destroy();
if (obj_) {
bpf_object__close(obj_);
}
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;
}
struct bpf_object * BpfProgram::get_obj() const {
return obj_;
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;
}
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_);
}
void BpfProgram::attach() {
// Check if parent is still alive
auto parent = parent_obj_.lock();
if (!parent) {
throw BpfException("Parent BpfObject has been destroyed");
}
// 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);
}
if (link_) {
throw BpfException("Program '" + program_name_ + "' already attached");
}
// throw if no programs found
if (programs.empty()) {
throw BpfException("No programs found in object file");
}
}
if (!prog_) {
throw BpfException("Program '" + program_name_ + "' not initialized");
}
// Load the eBPF object into the kernel
if (bpf_object__load(obj_)) {
throw BpfException("Failed to load BPF object into kernel");
}
return true;
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);
}
}
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");
}
}
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);
}
return success;
}
void BpfProgram::load_and_attach() {
load();
attach();
void BpfProgram::detach() {
if (link_) {
bpf_link__destroy(link_);
link_ = nullptr;
}
}

View File

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

@ -0,0 +1,117 @@
#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 <cerrno>
#include <cstring>
PerfEventArray::PerfEventArray(std::shared_ptr<BpfMap> map, int page_cnt,
py::function callback, py::object lost_callback)
: map_(map), pb_(nullptr), callback_(std::move(callback)),
lost_callback_(std::move(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");
}
struct perf_buffer_opts pb_opts = {};
pb_opts.sz = sizeof(pb_opts); // Required for forward compatibility
pb_ = perf_buffer__new(
map->get_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)));
}
}
PerfEventArray::PerfEventArray(std::shared_ptr<BpfMap> 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_);
}
}
void PerfEventArray::sample_callback_wrapper(void *ctx, int cpu, void *data,
unsigned int size) {
auto *self = static_cast<PerfEventArray *>(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);
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::print("C++ error in perf callback:", e.what());
}
}
void PerfEventArray::lost_callback_wrapper(void *ctx, int cpu,
unsigned long long cnt) {
auto *self = static_cast<PerfEventArray *>(ctx);
py::gil_scoped_acquire acquire;
try {
if (!self->lost_callback_.is_none()) {
py::function lost_fn = py::cast<py::function>(self->lost_callback_);
lost_fn(cpu, cnt);
} else {
py::print("Lost", cnt, "events on CPU", cpu);
}
} catch (const py::error_already_set &e) {
PyErr_Print();
}
}
int PerfEventArray::poll(int timeout_ms) {
// Release GIL during blocking poll
py::gil_scoped_release release;
return perf_buffer__poll(pb_, timeout_ms);
}
int PerfEventArray::consume() {
py::gil_scoped_release release;
return perf_buffer__consume(pb_);
}

View File

@ -0,0 +1,46 @@
#ifndef PYLIBBPF_PERF_EVENT_ARRAY_H
#define PYLIBBPF_PERF_EVENT_ARRAY_H
#include <libbpf.h>
#include <memory>
#include <pybind11/pybind11.h>
#include <string>
class StructParser;
class BpfMap;
namespace py = pybind11;
class PerfEventArray {
private:
std::shared_ptr<BpfMap> map_;
struct perf_buffer *pb_;
py::function callback_;
py::object lost_callback_;
std::shared_ptr<StructParser> 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(std::shared_ptr<BpfMap> map, int page_cnt,
py::function callback, py::object lost_callback = py::none());
PerfEventArray(std::shared_ptr<BpfMap> 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]] std::shared_ptr<BpfMap> get_map() const { return map_; }
};
#endif // PYLIBBPF_PERF_EVENT_ARRAY_H

View File

@ -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<py::object>(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();
}

20
src/utils/struct_parser.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef PYLIBBPF_STRUCT_PARSER_H
#define PYLIBBPF_STRUCT_PARSER_H
#include <pybind11/pybind11.h>
#include <string>
#include <unordered_map>
namespace py = pybind11;
class StructParser {
private:
std::unordered_map<std::string, py::object> 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

View File

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