27 Commits

Author SHA1 Message Date
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
19 changed files with 307 additions and 63 deletions

View File

@ -45,7 +45,7 @@ jobs:
run: pip install --verbose .[test] run: pip install --verbose .[test]
- name: Test import - name: Test import
run: python -c "import pylibbpf; print('Import successful')" run: python -I -c "import pylibbpf; print('Import successful')"
- name: Test - name: Test
run: python -m pytest -v run: python -I -m pytest -v

View File

@ -10,7 +10,6 @@ include_directories(${CMAKE_SOURCE_DIR}/src)
add_subdirectory(pybind11) add_subdirectory(pybind11)
pybind11_add_module( pybind11_add_module(
pylibbpf pylibbpf
# Core # Core
src/core/bpf_program.h src/core/bpf_program.h
src/core/bpf_exception.h src/core/bpf_exception.h
@ -19,18 +18,14 @@ pybind11_add_module(
src/core/bpf_program.cpp src/core/bpf_program.cpp
src/core/bpf_map.cpp src/core/bpf_map.cpp
src/core/bpf_object.cpp src/core/bpf_object.cpp
# Maps # Maps
src/maps/perf_event_array.h src/maps/perf_event_array.h
src/maps/perf_event_array.cpp src/maps/perf_event_array.cpp
# Utils # Utils
src/utils/struct_parser.h src/utils/struct_parser.h
src/utils/struct_parser.cpp src/utils/struct_parser.cpp
# Bindings # Bindings
src/bindings/main.cpp src/bindings/main.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,10 +1,11 @@
import time import time
from ctypes import c_int32, c_int64, c_uint64, c_void_p 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 import BPF, bpf, bpfglobal, map, section
from pythonbpf.maps import HashMap from pythonbpf.maps import HashMap
from pylibbpf import BpfMap
@bpf @bpf
@map @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"

105
pylibbpf/ir_to_ctypes.py Normal file
View File

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

0
pylibbpf/py.typed Normal file
View File

77
pylibbpf/wrappers.py Normal file
View File

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

View File

@ -4,12 +4,13 @@ requires = [
"wheel", "wheel",
"ninja", "ninja",
"cmake>=4.0", "cmake>=4.0",
"pybind11>=2.10",
] ]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[project] [project]
name = "pylibbpf" name = "pylibbpf"
version = "0.0.5" version = "0.0.6"
description = "Python Bindings for Libbpf" description = "Python Bindings for Libbpf"
authors = [ authors = [
{ name = "r41k0u", email = "pragyanshchaturvedi18@gmail.com" }, { name = "r41k0u", email = "pragyanshchaturvedi18@gmail.com" },
@ -32,14 +33,17 @@ classifiers = [
"Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: System :: Operating System Kernels :: Linux", "Topic :: System :: Operating System Kernels :: Linux",
] ]
dependencies = [
"llvmlite>=0.40.0",
]
[project.optional-dependencies] [project.optional-dependencies]
test = ["pytest>=6.0"] test = ["pytest>=6.0"]
[project.urls] [project.urls]
Homepage = "https://github.com/varun-r-mallya/pylibbpf" Homepage = "https://github.com/pythonbpf/pylibbpf"
Repository = "https://github.com/varun-r-mallya/pylibbpf" Repository = "https://github.com/pythonbpf/pylibbpf"
Issues = "https://github.com/varun-r-mallya/pylibbpf/issues" Issues = "https://github.com/pythonbpf/pylibbpf/issues"
[tool.mypy] [tool.mypy]
files = "setup.py" files = "setup.py"

View File

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

View File

@ -2,10 +2,6 @@
#define STRINGIFY(x) #x #define STRINGIFY(x) #x
#define MACRO_STRINGIFY(x) STRINGIFY(x) #define MACRO_STRINGIFY(x) STRINGIFY(x)
extern "C" {
#include <libbpf.h>
}
#include "core/bpf_exception.h" #include "core/bpf_exception.h"
#include "core/bpf_map.h" #include "core/bpf_map.h"
#include "core/bpf_object.h" #include "core/bpf_object.h"
@ -70,7 +66,8 @@ PYBIND11_MODULE(pylibbpf, m) {
.def("get_value_size", &BpfMap::get_value_size) .def("get_value_size", &BpfMap::get_value_size)
.def("get_max_entries", &BpfMap::get_max_entries) .def("get_max_entries", &BpfMap::get_max_entries)
.def("__getitem__", &BpfMap::lookup, py::arg("key")) .def("__getitem__", &BpfMap::lookup, py::arg("key"))
.def("__setitem__", &BpfMap::update, py::arg("key"), py::arg("value")); .def("__setitem__", &BpfMap::update, py::arg("key"), py::arg("value"))
.def("__delitem__", &BpfMap::delete_elem, py::arg("key"));
// StructParser // StructParser
py::class_<StructParser>(m, "StructParser") py::class_<StructParser>(m, "StructParser")

View File

@ -1,6 +1,9 @@
#include "bpf_map.h" #include "core/bpf_map.h"
#include "bpf_exception.h" #include "core/bpf_exception.h"
#include "bpf_object.h" #include "core/bpf_object.h"
#include <algorithm>
#include <cerrno>
#include <cstring>
BpfMap::BpfMap(std::shared_ptr<BpfObject> parent, struct bpf_map *raw_map, BpfMap::BpfMap(std::shared_ptr<BpfObject> parent, struct bpf_map *raw_map,
const std::string &map_name) const std::string &map_name)

View File

@ -1,10 +1,7 @@
#ifndef PYLIBBPF_BPF_MAP_H #ifndef PYLIBBPF_BPF_MAP_H
#define PYLIBBPF_BPF_MAP_H #define PYLIBBPF_BPF_MAP_H
#include <algorithm>
#include <array> #include <array>
#include <cerrno>
#include <cstring>
#include <libbpf.h> #include <libbpf.h>
#include <pybind11/pybind11.h> #include <pybind11/pybind11.h>
#include <span> #include <span>
@ -72,4 +69,4 @@ private:
static py::object bytes_to_python(std::span<const uint8_t> data); static py::object bytes_to_python(std::span<const uint8_t> data);
}; };
#endif // PYLIBBPF_MAPS_H #endif // PYLIBBPF_BPF_MAP_H

View File

@ -1,9 +1,11 @@
#include "bpf_object.h" #include "core/bpf_object.h"
#include "bpf_exception.h" #include "core/bpf_exception.h"
#include "bpf_map.h" #include "core/bpf_map.h"
#include "bpf_program.h" #include "core/bpf_program.h"
#include "utils/struct_parser.h" #include "utils/struct_parser.h"
#include <cerrno> #include <cerrno>
#include <cstring>
#include <utility>
BpfObject::BpfObject(std::string object_path, py::dict structs) BpfObject::BpfObject(std::string object_path, py::dict structs)
: obj_(nullptr), object_path_(std::move(object_path)), loaded_(false), : obj_(nullptr), object_path_(std::move(object_path)), loaded_(false),
@ -22,9 +24,13 @@ BpfObject::~BpfObject() {
} }
BpfObject::BpfObject(BpfObject &&other) noexcept BpfObject::BpfObject(BpfObject &&other) noexcept
: obj_(other.obj_), object_path_(std::move(other.object_path_)), : obj_(std::exchange(other.obj_, nullptr)),
loaded_(other.loaded_), prog_cache_(std::move(other.prog_cache_)), object_path_(std::move(other.object_path_)),
maps_cache_(std::move(other.maps_cache_)) { 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.obj_ = nullptr;
other.loaded_ = false; other.loaded_ = false;
@ -38,14 +44,13 @@ BpfObject &BpfObject::operator=(BpfObject &&other) noexcept {
bpf_object__close(obj_); bpf_object__close(obj_);
} }
obj_ = other.obj_; obj_ = std::exchange(other.obj_, nullptr);
object_path_ = std::move(other.object_path_); object_path_ = std::move(other.object_path_);
loaded_ = other.loaded_; loaded_ = std::exchange(other.loaded_, false);
prog_cache_ = std::move(other.prog_cache_);
maps_cache_ = std::move(other.maps_cache_); maps_cache_ = std::move(other.maps_cache_);
prog_cache_ = std::move(other.prog_cache_);
other.obj_ = nullptr; struct_defs_ = std::move(other.struct_defs_);
other.loaded_ = false; struct_parser_ = std::move(other.struct_parser_);
} }
return *this; return *this;
} }
@ -259,9 +264,9 @@ py::dict BpfObject::get_cached_maps() const {
} }
std::shared_ptr<StructParser> BpfObject::get_struct_parser() const { std::shared_ptr<StructParser> BpfObject::get_struct_parser() const {
if (!struct_parser_ && !struct_defs_.empty()) { if (!struct_parser_ && !struct_defs_.empty()) {
// Create parser on first access // Create parser on first access
struct_parser_ = std::make_shared<StructParser>(struct_defs_); struct_parser_ = std::make_shared<StructParser>(struct_defs_);
} }
return struct_parser_; return struct_parser_;
} }

View File

@ -6,7 +6,6 @@
#include <pybind11/pybind11.h> #include <pybind11/pybind11.h>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <vector>
namespace py = pybind11; namespace py = pybind11;

View File

@ -1,7 +1,8 @@
#include "bpf_program.h" #include "core/bpf_program.h"
#include "bpf_exception.h" #include "core/bpf_exception.h"
#include "bpf_object.h" #include "core/bpf_object.h"
#include <cerrno> #include <cerrno>
#include <cstring>
#include <utility> #include <utility>
BpfProgram::BpfProgram(std::shared_ptr<BpfObject> parent, BpfProgram::BpfProgram(std::shared_ptr<BpfObject> parent,

View File

@ -1,7 +1,6 @@
#ifndef PYLIBBPF_BPF_PROGRAM_H #ifndef PYLIBBPF_BPF_PROGRAM_H
#define PYLIBBPF_BPF_PROGRAM_H #define PYLIBBPF_BPF_PROGRAM_H
#include <cstring>
#include <libbpf.h> #include <libbpf.h>
#include <memory> #include <memory>
#include <string> #include <string>

View File

@ -1,13 +1,15 @@
#include "perf_event_array.h" #include "maps/perf_event_array.h"
#include "core/bpf_exception.h" #include "core/bpf_exception.h"
#include "core/bpf_map.h" #include "core/bpf_map.h"
#include "core/bpf_object.h" #include "core/bpf_object.h"
#include "utils/struct_parser.h" #include "utils/struct_parser.h"
#include <cerrno>
#include <cstring>
PerfEventArray::PerfEventArray(std::shared_ptr<BpfMap> map, int page_cnt, PerfEventArray::PerfEventArray(std::shared_ptr<BpfMap> map, int page_cnt,
py::function callback, py::object lost_callback) py::function callback, py::object lost_callback)
: map_(map), pb_(nullptr), callback_(std::move(callback)), : map_(map), pb_(nullptr), callback_(std::move(callback)),
lost_callback_(lost_callback) { lost_callback_(std::move(lost_callback)) {
if (map->get_type() != BPF_MAP_TYPE_PERF_EVENT_ARRAY) { if (map->get_type() != BPF_MAP_TYPE_PERF_EVENT_ARRAY) {
throw BpfException("Map '" + map->get_name() + throw BpfException("Map '" + map->get_name() +
@ -81,7 +83,6 @@ void PerfEventArray::sample_callback_wrapper(void *ctx, int cpu, void *data,
} catch (const py::error_already_set &e) { } catch (const py::error_already_set &e) {
PyErr_Print(); PyErr_Print();
} catch (const std::exception &e) { } catch (const std::exception &e) {
py::gil_scoped_acquire acquire;
py::print("C++ error in perf callback:", e.what()); py::print("C++ error in perf callback:", e.what());
} }
} }
@ -90,14 +91,15 @@ void PerfEventArray::lost_callback_wrapper(void *ctx, int cpu,
unsigned long long cnt) { unsigned long long cnt) {
auto *self = static_cast<PerfEventArray *>(ctx); auto *self = static_cast<PerfEventArray *>(ctx);
if (self->lost_callback_.is_none()) {
return;
}
py::gil_scoped_acquire acquire; py::gil_scoped_acquire acquire;
try { try {
self->lost_callback_(cpu, cnt); 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) { } catch (const py::error_already_set &e) {
PyErr_Print(); PyErr_Print();
} }

View File

@ -2,7 +2,7 @@
#define PYLIBBPF_PERF_EVENT_ARRAY_H #define PYLIBBPF_PERF_EVENT_ARRAY_H
#include <libbpf.h> #include <libbpf.h>
#include <pybind11/functional.h> #include <memory>
#include <pybind11/pybind11.h> #include <pybind11/pybind11.h>
#include <string> #include <string>
@ -16,7 +16,7 @@ private:
std::shared_ptr<BpfMap> map_; std::shared_ptr<BpfMap> map_;
struct perf_buffer *pb_; struct perf_buffer *pb_;
py::function callback_; py::function callback_;
py::function lost_callback_; py::object lost_callback_;
std::shared_ptr<StructParser> parser_; std::shared_ptr<StructParser> parser_;
std::string struct_name_; std::string struct_name_;

View File

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