From 2c9870bf837aa19d0df1c0ec5e42a873d9d0335e Mon Sep 17 00:00:00 2001 From: Littlefisher Date: Sat, 8 Nov 2025 15:51:34 -0800 Subject: [PATCH] feat: Add initial implementation of struct_ops BPF program and kernel module with kfunc support --- src/features/struct_ops/.config | 2 + src/features/struct_ops/.gitignore | 10 ++ src/features/struct_ops/Makefile | 141 +++++++++++++++++++ src/features/struct_ops/module/.gitignore | 23 +++ src/features/struct_ops/module/Makefile | 11 ++ src/features/struct_ops/module/README.txt | 134 ++++++++++++++++++ src/features/struct_ops/module/README.zh.txt | 134 ++++++++++++++++++ src/features/struct_ops/module/compact.h | 11 ++ src/features/struct_ops/module/hello.c | 89 ++++++++++++ src/features/struct_ops/struct_ops.bpf.c | 28 ++++ src/features/struct_ops/struct_ops.c | 76 ++++++++++ 11 files changed, 659 insertions(+) create mode 100644 src/features/struct_ops/.config create mode 100644 src/features/struct_ops/.gitignore create mode 100644 src/features/struct_ops/Makefile create mode 100644 src/features/struct_ops/module/.gitignore create mode 100644 src/features/struct_ops/module/Makefile create mode 100644 src/features/struct_ops/module/README.txt create mode 100644 src/features/struct_ops/module/README.zh.txt create mode 100644 src/features/struct_ops/module/compact.h create mode 100644 src/features/struct_ops/module/hello.c create mode 100644 src/features/struct_ops/struct_ops.bpf.c create mode 100644 src/features/struct_ops/struct_ops.c diff --git a/src/features/struct_ops/.config b/src/features/struct_ops/.config new file mode 100644 index 0000000..73992da --- /dev/null +++ b/src/features/struct_ops/.config @@ -0,0 +1,2 @@ +level=Depth +type=Features diff --git a/src/features/struct_ops/.gitignore b/src/features/struct_ops/.gitignore new file mode 100644 index 0000000..d95bc29 --- /dev/null +++ b/src/features/struct_ops/.gitignore @@ -0,0 +1,10 @@ +.vscode +package.json +*.o +*.skel.json +*.skel.yaml +package.yaml +ecli +ecc +.output +struct_ops diff --git a/src/features/struct_ops/Makefile b/src/features/struct_ops/Makefile new file mode 100644 index 0000000..aa532cc --- /dev/null +++ b/src/features/struct_ops/Makefile @@ -0,0 +1,141 @@ +# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +OUTPUT := .output +CLANG ?= clang +LIBBPF_SRC := $(abspath ../../third_party/libbpf/src) +BPFTOOL_SRC := $(abspath ../../third_party/bpftool/src) +LIBBPF_OBJ := $(abspath $(OUTPUT)/libbpf.a) +BPFTOOL_OUTPUT ?= $(abspath $(OUTPUT)/bpftool) +BPFTOOL ?= $(BPFTOOL_OUTPUT)/bootstrap/bpftool +LIBBLAZESYM_SRC := $(abspath ../../third_party/blazesym/) +LIBBLAZESYM_OBJ := $(abspath $(OUTPUT)/libblazesym.a) +LIBBLAZESYM_HEADER := $(abspath $(OUTPUT)/blazesym.h) +ARCH ?= $(shell uname -m | sed 's/x86_64/x86/' \ + | sed 's/arm.*/arm/' \ + | sed 's/aarch64/arm64/' \ + | sed 's/ppc64le/powerpc/' \ + | sed 's/mips.*/mips/' \ + | sed 's/riscv64/riscv/' \ + | sed 's/loongarch64/loongarch/') +VMLINUX := ../../third_party/vmlinux/$(ARCH)/vmlinux.h +# Use our own libbpf API headers and Linux UAPI headers distributed with +# libbpf to avoid dependency on system-wide headers, which could be missing or +# outdated +INCLUDES := -I$(OUTPUT) -I../../third_party/libbpf/include/uapi -I$(dir $(VMLINUX)) +CFLAGS := -g -Wall +ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS) + +APPS = struct_ops # minimal minimal_legacy uprobe kprobe fentry usdt sockfilter tc ksyscall + +CARGO ?= $(shell which cargo) +ifeq ($(strip $(CARGO)),) +BZS_APPS := +else +BZS_APPS := # profile +APPS += $(BZS_APPS) +# Required by libblazesym +ALL_LDFLAGS += -lrt -ldl -lpthread -lm +endif + +# Get Clang's default includes on this system. We'll explicitly add these dirs +# to the includes list when compiling with `-target bpf` because otherwise some +# architecture-specific dirs will be "missing" on some architectures/distros - +# headers such as asm/types.h, asm/byteorder.h, asm/socket.h, asm/sockios.h, +# sys/cdefs.h etc. might be missing. +# +# Use '-idirafter': Don't interfere with include mechanics except where the +# build would have failed anyways. +CLANG_BPF_SYS_INCLUDES ?= $(shell $(CLANG) -v -E - &1 \ + | sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') + +ifeq ($(V),1) + Q = + msg = +else + Q = @ + msg = @printf ' %-8s %s%s\n' \ + "$(1)" \ + "$(patsubst $(abspath $(OUTPUT))/%,%,$(2))" \ + "$(if $(3), $(3))"; + MAKEFLAGS += --no-print-directory +endif + +define allow-override + $(if $(or $(findstring environment,$(origin $(1))),\ + $(findstring command line,$(origin $(1)))),,\ + $(eval $(1) = $(2))) +endef + +$(call allow-override,CC,$(CROSS_COMPILE)cc) +$(call allow-override,LD,$(CROSS_COMPILE)ld) + +.PHONY: all +all: $(APPS) + +.PHONY: clean +clean: + $(call msg,CLEAN) + $(Q)rm -rf $(OUTPUT) $(APPS) + +$(OUTPUT) $(OUTPUT)/libbpf $(BPFTOOL_OUTPUT): + $(call msg,MKDIR,$@) + $(Q)mkdir -p $@ + +# Build libbpf +$(LIBBPF_OBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(OUTPUT)/libbpf + $(call msg,LIB,$@) + $(Q)$(MAKE) -C $(LIBBPF_SRC) BUILD_STATIC_ONLY=1 \ + OBJDIR=$(dir $@)/libbpf DESTDIR=$(dir $@) \ + INCLUDEDIR= LIBDIR= UAPIDIR= \ + install + +# Build bpftool +$(BPFTOOL): | $(BPFTOOL_OUTPUT) + $(call msg,BPFTOOL,$@) + $(Q)$(MAKE) ARCH= CROSS_COMPILE= OUTPUT=$(BPFTOOL_OUTPUT)/ -C $(BPFTOOL_SRC) bootstrap + + +$(LIBBLAZESYM_SRC)/target/release/libblazesym.a:: + $(Q)cd $(LIBBLAZESYM_SRC) && $(CARGO) build --features=cheader,dont-generate-test-files --release + +$(LIBBLAZESYM_OBJ): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT) + $(call msg,LIB, $@) + $(Q)cp $(LIBBLAZESYM_SRC)/target/release/libblazesym.a $@ + +$(LIBBLAZESYM_HEADER): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT) + $(call msg,LIB,$@) + $(Q)cp $(LIBBLAZESYM_SRC)/target/release/blazesym.h $@ + +# Build BPF code +$(OUTPUT)/%.bpf.o: %.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) $(VMLINUX) | $(OUTPUT) $(BPFTOOL) + $(call msg,BPF,$@) + $(Q)$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) \ + $(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) \ + -c $(filter %.c,$^) -o $(patsubst %.bpf.o,%.tmp.bpf.o,$@) + $(Q)$(BPFTOOL) gen object $@ $(patsubst %.bpf.o,%.tmp.bpf.o,$@) + +# Generate BPF skeletons +$(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o | $(OUTPUT) $(BPFTOOL) + $(call msg,GEN-SKEL,$@) + $(Q)$(BPFTOOL) gen skeleton $< > $@ + +# Build user-space code +$(patsubst %,$(OUTPUT)/%.o,$(APPS)): %.o: %.skel.h + +$(OUTPUT)/%.o: %.c $(wildcard %.h) | $(OUTPUT) + $(call msg,CC,$@) + $(Q)$(CC) $(CFLAGS) $(INCLUDES) -c $(filter %.c,$^) -o $@ + +$(patsubst %,$(OUTPUT)/%.o,$(BZS_APPS)): $(LIBBLAZESYM_HEADER) + +$(BZS_APPS): $(LIBBLAZESYM_OBJ) + +# Build application binary +$(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) | $(OUTPUT) + $(call msg,BINARY,$@) + $(Q)$(CC) $(CFLAGS) $^ $(ALL_LDFLAGS) -lelf -lz -o $@ + +# delete failed targets +.DELETE_ON_ERROR: + +# keep intermediate (.skel.h, .bpf.o, etc) targets +.SECONDARY: diff --git a/src/features/struct_ops/module/.gitignore b/src/features/struct_ops/module/.gitignore new file mode 100644 index 0000000..9523173 --- /dev/null +++ b/src/features/struct_ops/module/.gitignore @@ -0,0 +1,23 @@ +# Ignore object files and kernel modules +*.o +*.ko +*.mod +*.mod.c +*.symvers +*.order + +# Ignore temporary and backup files +*~ +*.bak +*.tmp +*.swp + +# Ignore build directory if generated +/Module.symvers +/Modules.markers +/Module.markers +/modules.order + +# Ignore other automatically generated files +*.cmd +.tmp_versions/ diff --git a/src/features/struct_ops/module/Makefile b/src/features/struct_ops/module/Makefile new file mode 100644 index 0000000..44afa31 --- /dev/null +++ b/src/features/struct_ops/module/Makefile @@ -0,0 +1,11 @@ +obj-m += hello.o # hello.o is the target + +# Enable BTF generation +KBUILD_CFLAGS += -g -O2 + +all: + # Compile the module with BTF information + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules + +clean: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean diff --git a/src/features/struct_ops/module/README.txt b/src/features/struct_ops/module/README.txt new file mode 100644 index 0000000..d2dfada --- /dev/null +++ b/src/features/struct_ops/module/README.txt @@ -0,0 +1,134 @@ +# write a basic kernel module + +## hello world + +Writing a Linux kernel module involves creating code that can be loaded into and unloaded from the kernel dynamically, without rebooting the system. Here’s a simple step-by-step guide to help you write a basic kernel module: + +### 1. Set Up Your Environment + +Make sure you have the Linux kernel headers installed and a suitable development environment ready. For Ubuntu or Debian, install them with: + +```bash +sudo apt-get install linux-headers-$(uname -r) build-essential +``` + +### 2. Write the Kernel Module Code + +Here’s an example of a very basic Linux kernel module: + +```c +// hello.c: A simple Linux kernel module +#include // Macros for module initialization +#include // Core header for loading modules +#include // Kernel logging macros + +// Function executed when the module is loaded +static int __init hello_init(void) +{ + printk(KERN_INFO "Hello, world!\n"); + return 0; // Return 0 if successful +} + +// Function executed when the module is removed +static void __exit hello_exit(void) +{ + printk(KERN_INFO "Goodbye, world!\n"); +} + +// Macros to define the module’s init and exit points +module_init(hello_init); +module_exit(hello_exit); + +MODULE_LICENSE("GPL"); // License type (GPL) +MODULE_AUTHOR("Your Name"); // Module author +MODULE_DESCRIPTION("A simple module"); // Module description +MODULE_VERSION("1.0"); // Module version +``` + +### 3. Create a Makefile + +To compile the kernel module, you’ll need a `Makefile`. Here's a simple one: + +```makefile +obj-m += hello.o # hello.o is the target + +all: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules + +clean: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean +``` + +### 4. Compile the Module + +Run the following command in the directory where your `hello.c` and `Makefile` are located: + +```bash +make +``` + +This will generate a file called `hello.ko`, which is the compiled kernel module. + +### 5. Load the Module + +To insert the module into the kernel, use `insmod`: + +```bash +sudo insmod hello.ko +``` + +### 6. Check the Logs + +To see the output from the `printk` statements, use the `dmesg` command: + +```bash +dmesg | tail +``` + +You should see something like: + +```txt +[ 1234.5678] Hello, world! +``` + +### 7. Remove the Module + +To unload the module, use `rmmod`: + +```bash +sudo rmmod hello +``` + +Again, check the logs using `dmesg`: + +```bash +sudo dmesg | tail +``` + +You should see: + +```txt +[ 1234.9876] Goodbye, world! +``` + +### 8. Clean Up + +To clean up the build files, run: + +```bash +make clean +``` + +### Notes + +- **License**: The `MODULE_LICENSE("GPL")` ensures the module is GPL-compliant, which allows it to use symbols (functions) exported by the kernel. +- **Debugging**: Use `printk` for logging within the module. It behaves similarly to `printf` but is designed for kernel space. +- **Module Parameters**: You can add parameters to modules using `module_param()` to pass arguments when the module is loaded. + +### Next Steps + +Once you are familiar with this basic example, you can explore: + +- Writing more advanced modules that interact with hardware or the filesystem. +- Using kernel-specific APIs like work queues, kthreads, or handling interrupts. +- Diving into eBPF or loadable kernel module techniques for debugging and tracing kernel events. diff --git a/src/features/struct_ops/module/README.zh.txt b/src/features/struct_ops/module/README.zh.txt new file mode 100644 index 0000000..d2dfada --- /dev/null +++ b/src/features/struct_ops/module/README.zh.txt @@ -0,0 +1,134 @@ +# write a basic kernel module + +## hello world + +Writing a Linux kernel module involves creating code that can be loaded into and unloaded from the kernel dynamically, without rebooting the system. Here’s a simple step-by-step guide to help you write a basic kernel module: + +### 1. Set Up Your Environment + +Make sure you have the Linux kernel headers installed and a suitable development environment ready. For Ubuntu or Debian, install them with: + +```bash +sudo apt-get install linux-headers-$(uname -r) build-essential +``` + +### 2. Write the Kernel Module Code + +Here’s an example of a very basic Linux kernel module: + +```c +// hello.c: A simple Linux kernel module +#include // Macros for module initialization +#include // Core header for loading modules +#include // Kernel logging macros + +// Function executed when the module is loaded +static int __init hello_init(void) +{ + printk(KERN_INFO "Hello, world!\n"); + return 0; // Return 0 if successful +} + +// Function executed when the module is removed +static void __exit hello_exit(void) +{ + printk(KERN_INFO "Goodbye, world!\n"); +} + +// Macros to define the module’s init and exit points +module_init(hello_init); +module_exit(hello_exit); + +MODULE_LICENSE("GPL"); // License type (GPL) +MODULE_AUTHOR("Your Name"); // Module author +MODULE_DESCRIPTION("A simple module"); // Module description +MODULE_VERSION("1.0"); // Module version +``` + +### 3. Create a Makefile + +To compile the kernel module, you’ll need a `Makefile`. Here's a simple one: + +```makefile +obj-m += hello.o # hello.o is the target + +all: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules + +clean: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean +``` + +### 4. Compile the Module + +Run the following command in the directory where your `hello.c` and `Makefile` are located: + +```bash +make +``` + +This will generate a file called `hello.ko`, which is the compiled kernel module. + +### 5. Load the Module + +To insert the module into the kernel, use `insmod`: + +```bash +sudo insmod hello.ko +``` + +### 6. Check the Logs + +To see the output from the `printk` statements, use the `dmesg` command: + +```bash +dmesg | tail +``` + +You should see something like: + +```txt +[ 1234.5678] Hello, world! +``` + +### 7. Remove the Module + +To unload the module, use `rmmod`: + +```bash +sudo rmmod hello +``` + +Again, check the logs using `dmesg`: + +```bash +sudo dmesg | tail +``` + +You should see: + +```txt +[ 1234.9876] Goodbye, world! +``` + +### 8. Clean Up + +To clean up the build files, run: + +```bash +make clean +``` + +### Notes + +- **License**: The `MODULE_LICENSE("GPL")` ensures the module is GPL-compliant, which allows it to use symbols (functions) exported by the kernel. +- **Debugging**: Use `printk` for logging within the module. It behaves similarly to `printf` but is designed for kernel space. +- **Module Parameters**: You can add parameters to modules using `module_param()` to pass arguments when the module is loaded. + +### Next Steps + +Once you are familiar with this basic example, you can explore: + +- Writing more advanced modules that interact with hardware or the filesystem. +- Using kernel-specific APIs like work queues, kthreads, or handling interrupts. +- Diving into eBPF or loadable kernel module techniques for debugging and tracing kernel events. diff --git a/src/features/struct_ops/module/compact.h b/src/features/struct_ops/module/compact.h new file mode 100644 index 0000000..5ff2115 --- /dev/null +++ b/src/features/struct_ops/module/compact.h @@ -0,0 +1,11 @@ +// Compatible for lower kernel versions. No need in 6.11. +#ifndef BTF_SET8_KFUNCS +/* This flag implies BTF_SET8 holds kfunc(s) */ +#define BTF_SET8_KFUNCS (1 << 0) +#endif +#ifndef BTF_KFUNCS_START +#define BTF_KFUNCS_START(name) static struct btf_id_set8 __maybe_unused name = { .flags = BTF_SET8_KFUNCS }; +#endif +#ifndef BTF_KFUNCS_END +#define BTF_KFUNCS_END(name) +#endif diff --git a/src/features/struct_ops/module/hello.c b/src/features/struct_ops/module/hello.c new file mode 100644 index 0000000..6691b01 --- /dev/null +++ b/src/features/struct_ops/module/hello.c @@ -0,0 +1,89 @@ +#include // Macros for module initialization +#include // Core header for loading modules +#include // Kernel logging macros +#include +#include +#include + +__bpf_kfunc int bpf_strstr(const char *str, u32 str__sz, const char *substr, u32 substr__sz); + +/* Define a kfunc function */ +__bpf_kfunc_start_defs(); + +__bpf_kfunc int bpf_strstr(const char *str, u32 str__sz, const char *substr, u32 substr__sz) +{ + // Edge case: if substr is empty, return 0 (assuming empty string is found at the start) + if (substr__sz == 0) + { + return 0; + } + // Edge case: if the substring is longer than the main string, it's impossible to find + if (substr__sz > str__sz) + { + return -1; // Return -1 to indicate not found + } + + // Iterate through the main string, considering the size limit + for (size_t i = 0; i <= str__sz - substr__sz; i++) + { + size_t j = 0; + // Compare the substring with the current position in the string + while (j < substr__sz && str[i + j] == substr[j]) + { + j++; + } + // If the entire substring was found + if (j == substr__sz) + { + return i; // Return the index of the first match + } + } + // Return -1 if the substring is not found + return -1; +} + +__bpf_kfunc_end_defs(); + +BTF_KFUNCS_START(bpf_kfunc_example_ids_set) +BTF_ID_FLAGS(func, bpf_strstr) +BTF_KFUNCS_END(bpf_kfunc_example_ids_set) + +// Register the kfunc ID set +static const struct btf_kfunc_id_set bpf_kfunc_example_set = { + .owner = THIS_MODULE, + .set = &bpf_kfunc_example_ids_set, +}; + +// Function executed when the module is loaded +static int __init hello_init(void) +{ + int ret; + + printk(KERN_INFO "Hello, world!\n"); + // Register the BTF kfunc ID set + ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_KPROBE, &bpf_kfunc_example_set); + if (ret) + { + pr_err("bpf_kfunc_example: Failed to register BTF kfunc ID set\n"); + return ret; + } + printk(KERN_INFO "bpf_kfunc_example: Module loaded successfully\n"); + return 0; // Return 0 if successful +} + +// Function executed when the module is removed +static void __exit hello_exit(void) +{ + // Unregister the BTF kfunc ID set + // unregister_btf_kfunc_id_set(BPF_PROG_TYPE_UNSPEC, &bpf_kfunc_example_set); + printk(KERN_INFO "Goodbye, world!\n"); +} + +// Macros to define the module’s init and exit points +module_init(hello_init); +module_exit(hello_exit); + +MODULE_LICENSE("GPL"); // License type (GPL) +MODULE_AUTHOR("Your Name"); // Module author +MODULE_DESCRIPTION("A simple module"); // Module description +MODULE_VERSION("1.0"); // Module version diff --git a/src/features/struct_ops/struct_ops.bpf.c b/src/features/struct_ops/struct_ops.bpf.c new file mode 100644 index 0000000..afbb7a9 --- /dev/null +++ b/src/features/struct_ops/struct_ops.bpf.c @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +#define BPF_NO_GLOBAL_DATA +#include +#include +#include + +typedef unsigned int u32; +typedef unsigned long long u64; +typedef int pid_t; + +extern int bpf_strstr(const char *str, u32 str__sz, const char *substr, u32 substr__sz) __ksym; + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; + +SEC("kprobe/do_unlinkat") +int handle_kprobe(void *ctx) +{ + pid_t pid = bpf_get_current_pid_tgid() >> 32; + char str[] = "Hello, world!"; + char substr[] = "wor"; + u32 result = bpf_strstr(str, sizeof(str) - 1, substr, sizeof(substr) - 1); + if (result != -1) + { + bpf_printk("'%s' found in '%s' at index %d\n", substr, str, result); + } + bpf_printk("Hello, world! (pid: %d) bpf_strstr %d\n", pid, result); + return 0; +} diff --git a/src/features/struct_ops/struct_ops.c b/src/features/struct_ops/struct_ops.c new file mode 100644 index 0000000..7d1c60c --- /dev/null +++ b/src/features/struct_ops/struct_ops.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include + +#include "struct_ops.skel.h" // Include the generated skeleton header + +static volatile bool exiting = false; + +// Signal handler for graceful termination +void handle_signal(int sig) { + exiting = true; +} + +int main(int argc, char **argv) { + struct struct_ops_bpf *skel; + int err; + + // Handle SIGINT and SIGTERM for graceful shutdown + signal(SIGINT, handle_signal); + + // Open the BPF application + skel = struct_ops_bpf__open(); + if (!skel) { + fprintf(stderr, "Failed to open BPF skeleton\n"); + return 1; + } + + // Load & verify the BPF program + err = struct_ops_bpf__load(skel); + if (err) { + fprintf(stderr, "Failed to load and verify BPF skeleton: %d\n", err); + goto cleanup; + } + + // Attach the BPF program (e.g., attach kprobe) + err = struct_ops_bpf__attach(skel); + if (err) { + fprintf(stderr, "Failed to attach BPF skeleton: %d\n", err); + goto cleanup; + } + + printf("BPF program loaded and attached successfully. Press Ctrl-C to exit.\n"); + + // Optionally, read the trace_pipe to see bpf_printk outputs + FILE *trace_pipe = fopen("/sys/kernel/debug/tracing/trace_pipe", "r"); + if (!trace_pipe) { + perror("fopen trace_pipe"); + // Continue without reading trace_pipe + } + + // Main loop + while (!exiting) { + if (trace_pipe) { + char buffer[256]; + if (fgets(buffer, sizeof(buffer), trace_pipe)) { + printf("%s", buffer); + } else { + if (errno == EINTR) + break; + } + } else { + // If trace_pipe is not available, just sleep + sleep(1); + } + } + + if (trace_pipe) + fclose(trace_pipe); + +cleanup: + // Clean up and destroy the BPF program + struct_ops_bpf__destroy(skel); + return err < 0 ? -err : 0; +}