Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
8d27a35
Add src/core/bpf_perf_buffer
r41k0u Oct 16, 2025
528a542
Create bpf_object.h as a container for the object file having bpf pro…
r41k0u Oct 17, 2025
763c188
Implement BpfObject class
r41k0u Oct 17, 2025
2b99f01
Rework BpfProgram.h, pass BpfObject as shared_ptr to BpfPrograms
r41k0u Oct 18, 2025
5c74be0
Reimplement BpfProgram
r41k0u Oct 18, 2025
54acc2c
Redesign BpfMap
r41k0u Oct 18, 2025
c5a485b
Reimplement BpfMap
r41k0u Oct 18, 2025
4a5ff0c
Janitorial: clang-format
r41k0u Oct 18, 2025
744a509
Modify bindings for newly designed classes
r41k0u Oct 18, 2025
f233cf2
Allow C++20 in CMakeLists.txt
r41k0u Oct 18, 2025
1c2e170
Add BpfObject and BpfPerfBuffer to pybind11
r41k0u Oct 18, 2025
c0b982a
Use shared_from_this while creating BpfProgram or BpfMap, make get_ma…
r41k0u Oct 18, 2025
fc4d9a5
Fix perf_buffer__new call
r41k0u Oct 18, 2025
1c956da
Janitorial clang-format
r41k0u Oct 18, 2025
cbe019c
Restore minimum cmake version
r41k0u Oct 18, 2025
771d8fe
Modify tests to use BpfObject instead
r41k0u Oct 18, 2025
874d567
Move BpfPerfBuffer under src/maps
r41k0u Oct 19, 2025
b4d0a49
Add struct_defs_ to BpfObject
r41k0u Oct 19, 2025
f787413
Enable BpfMap to be shared, add get_parent
r41k0u Oct 19, 2025
05d5bba
Add StructParser utility
r41k0u Oct 19, 2025
cbfe6ae
Rename BpfPerfBuffer to PerfEventArray, add struct_parser to BpfObjec…
r41k0u Oct 19, 2025
8babf30
Add parser and parent shared_ptr to PerfEventArray
r41k0u Oct 19, 2025
1eb7ed4
Fix Bindings and PerfEventArray
r41k0u Oct 19, 2025
eda08b0
lint fix to CMakeLists
r41k0u Oct 19, 2025
0e454bd
Add IR Types to CTypes struct convertor
r41k0u Oct 19, 2025
30021e8
Add PerfEventArray and BpfObject wrappers
r41k0u Oct 19, 2025
23cafa4
Expose classes and perform struct conversion in __init__
r41k0u Oct 19, 2025
bbb3989
Fix setup.py
r41k0u Oct 19, 2025
495318f
Update pyproject.toml
r41k0u Oct 19, 2025
470afc5
Import find_packages in setup.py
r41k0u Oct 19, 2025
c580aab
Move Python files to pylibbpf/
r41k0u Oct 19, 2025
ddbbce4
Use c_char type for Int8 arrays in ir_to_ctypes
r41k0u Oct 19, 2025
b7aa080
Fix pre-commit conditions
r41k0u Oct 19, 2025
92e92f1
Add _make_repr to ir_to_ctypes
r41k0u Oct 19, 2025
c9a152a
Add __version__ to __init__
r41k0u Oct 19, 2025
638533c
Fix test for pip GH workflow
r41k0u Oct 19, 2025
dd552de
Add memory header to maps/perf_event_array.h
r41k0u Oct 19, 2025
3085e81
Add __delitem__ for BpfMap in bindings
r41k0u Oct 19, 2025
a3c3dbe
Fix BpfMap header guard
r41k0u Oct 19, 2025
ec5377b
Remove unnecessary GIL acquisition in PerfEventArray
r41k0u Oct 19, 2025
eebfe61
fix lost_callback_wrapper, clang-format
r41k0u Oct 19, 2025
003495e
Make lost_callback type asfe in PerfEventArray
r41k0u Oct 19, 2025
88716ce
Fill missing fields in BpfObject's move constructor
r41k0u Oct 20, 2025
fb82b60
Fix BpfMap includes
r41k0u Oct 20, 2025
ff427c2
Fix includes for bindings
r41k0u Oct 20, 2025
8cc8f42
Fix includes for BpfMap
r41k0u Oct 20, 2025
867f142
Fix includes for BpfObject
r41k0u Oct 20, 2025
fa5d181
Fix includes for BpfProgram
r41k0u Oct 20, 2025
f99de99
Fix includes for PerfEventArray
r41k0u Oct 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/pip.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
run: pip install --verbose .[test]

- name: Test import
run: python -c "import pylibbpf; print('Import successful')"
run: python -I -c "import pylibbpf; print('Import successful')"

- name: Test
run: python -m pytest -v
run: python -I -m pytest -v
18 changes: 16 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
cmake_minimum_required(VERSION 4.0)
project(pylibbpf)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# pybind11
include_directories(${CMAKE_SOURCE_DIR}/src)
add_subdirectory(pybind11)
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)
Expand Down
3 changes: 2 additions & 1 deletion examples/execve.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
46 changes: 46 additions & 0 deletions pylibbpf/__init__.py
Original file line number Diff line number Diff line change
@@ -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"
105 changes: 105 additions & 0 deletions pylibbpf/ir_to_ctypes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import ctypes
import logging
from typing import Dict, Type

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"]
Empty file added pylibbpf/py.typed
Empty file.
77 changes: 77 additions & 0 deletions pylibbpf/wrappers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from typing import Callable, Optional


class PerfEventArrayHelper:
"""Fluent wrapper for PERF_EVENT_ARRAY maps."""

def __init__(self, bpf_map):
self._map = bpf_map
self._perf_buffer = None

def open_perf_buffer(
self,
callback: Callable,
struct_name: str = "",
page_cnt: int = 8,
lost_callback: Optional[Callable] = None,
):
"""Open perf buffer with auto-deserialization."""
from .pylibbpf import PerfEventArray

if struct_name:
self._perf_buffer = PerfEventArray(
self._map,
page_cnt,
callback,
struct_name,
lost_callback or (lambda cpu, cnt: None),
)
else:
self._perf_buffer = PerfEventArray(
self._map, page_cnt, callback, lost_callback or (lambda cpu, cnt: None)
)

return self

def poll(self, timeout_ms: int = -1) -> int:
if not self._perf_buffer:
raise RuntimeError("Call open_perf_buffer() first")
return self._perf_buffer.poll(timeout_ms)

def consume(self) -> int:
if not self._perf_buffer:
raise RuntimeError("Call open_perf_buffer() first")
return self._perf_buffer.consume()

def __getattr__(self, name):
return getattr(self._map, name)


class BpfObjectWrapper:
"""Smart wrapper that returns map-specific helpers."""

BPF_MAP_TYPE_PERF_EVENT_ARRAY = 4
BPF_MAP_TYPE_RINGBUF = 27

def __init__(self, bpf_object):
self._obj = bpf_object
self._map_helpers = {}

def __getitem__(self, name: str):
"""Return appropriate helper based on map type."""
if name in self._map_helpers:
return self._map_helpers[name]

map_obj = self._obj[name]
map_type = map_obj.get_type()

if map_type == self.BPF_MAP_TYPE_PERF_EVENT_ARRAY:
helper = PerfEventArrayHelper(map_obj)
else:
helper = map_obj

self._map_helpers[name] = helper
return helper

def __getattr__(self, name):
return getattr(self._obj, name)
12 changes: 8 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ requires = [
"wheel",
"ninja",
"cmake>=4.0",
"pybind11>=2.10",
]
build-backend = "setuptools.build_meta"

[project]
name = "pylibbpf"
version = "0.0.5"
version = "0.0.6"
description = "Python Bindings for Libbpf"
authors = [
{ name = "r41k0u", email = "pragyanshchaturvedi18@gmail.com" },
Expand All @@ -32,14 +33,17 @@ classifiers = [
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: System :: Operating System Kernels :: Linux",
]
dependencies = [
"llvmlite>=0.40.0",
]

[project.optional-dependencies]
test = ["pytest>=6.0"]

[project.urls]
Homepage = "https://github.com/varun-r-mallya/pylibbpf"
Repository = "https://github.com/varun-r-mallya/pylibbpf"
Issues = "https://github.com/varun-r-mallya/pylibbpf/issues"
Homepage = "https://github.com/pythonbpf/pylibbpf"
Repository = "https://github.com/pythonbpf/pylibbpf"
Issues = "https://github.com/pythonbpf/pylibbpf/issues"

[tool.mypy]
files = "setup.py"
Expand Down
19 changes: 16 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import sys
from pathlib import Path

from setuptools import Extension, setup
from setuptools import Extension, find_packages, setup
from setuptools.command.build_ext import build_ext

# Convert distutils Windows platform specifiers to CMake -A arguments
Expand Down Expand Up @@ -129,8 +129,11 @@ def build_extension(self, ext: CMakeExtension) -> None:
description="Python Bindings for Libbpf",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/varun-r-mallya/pylibbpf",
ext_modules=[CMakeExtension("pylibbpf")],
url="https://github.com/pythonbpf/pylibbpf",
packages=find_packages(where="."),
package_dir={"": "."},
py_modules=[], # Empty since we use packages
ext_modules=[CMakeExtension("pylibbpf.pylibbpf")],
cmdclass={"build_ext": CMakeBuild},
zip_safe=False,
classifiers=[
Expand All @@ -147,6 +150,16 @@ def build_extension(self, ext: CMakeExtension) -> None:
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: System :: Operating System Kernels :: Linux",
],
install_requires=[
"llvmlite>=0.40.0", # Required for struct conversion
],
extras_require={"test": ["pytest>=6.0"]},
python_requires=">=3.8",
package_data={
"pylibbpf": [
"*.py",
"py.typed", # For type hints
],
},
include_package_data=True,
)
Loading
Loading