Home / Alt manpages / bpftool-gen(8)

  • bpftool-gen(8)
  • Admin command
  • linux

Build a BPF Skeleton with bpftool gen

You will turn one or more compiled BPF ELF objects into a combined object and a generated C header for libbpf. The workflow also shows when a subskeleton or a reduced CO-RE BTF file is the better fit. Allow 20 to 30 minutes for a first run. You need the bpftool, LLVM or Clang with BPF support, libbpf development headers, and a working C build environment.

The examples follow the installed linux-tools-common package version 6.8.0-139.139 and its bpftool-gen(8) manual page. The package wrapper on this host reports that the kernel-specific bpftool binary is missing, so use the commands on a machine where bpftool is available. This guide only creates build artefacts. Loading or attaching a BPF program is a separate, potentially privileged operation.

1. Check the local command and toolchain

Start by checking the executable and the command syntax. These are ordinary read-only commands:

$ command -v bpftool
$ bpftool version
$ bpftool gen help

Record the version before copying examples into a build system. The local 6.8 manual documents object, skeleton, subskeleton and min_core_btf, with -L or --use-loader for a light skeleton. Upstream documentation can move faster than a distribution package, so do not add an option merely because it appears in a newer online manual.

Checkpoint

Continue only when bpftool gen help lists the subcommand you intend to use. A missing executable or an unsupported subcommand is a package or version issue, not a BPF source error.

2. Compile each BPF source file

Keep source files separate when that makes the program easier to maintain. Compile each one to a BPF ELF object with debug information:

$ clang --target=bpf -g example1.bpf.c -o example1.bpf.o
$ clang --target=bpf -g example2.bpf.c -o example2.bpf.o
$ file example1.bpf.o example2.bpf.o

The input to bpftool gen object must be BPF ELF, not C source. The -g option supplies information useful to BTF and later diagnostics; it does not load anything into the kernel. If Clang cannot find BPF headers or rejects the target, fix the compiler and development package setup before invoking bpftool.

Keep the original object files. They are useful for identifying which compilation step failed, and they let you repeat the link without rebuilding source.

3. Combine objects into one BPF ELF file

Use gen object when a program is split across compilation units:

$ bpftool gen object example.bpf.o example1.bpf.o example2.bpf.o
$ file example.bpf.o
$ test -s example.bpf.o && echo "combined object exists"

This statically links the BPF instruction and data sections. It also combines available .BTF and .BTF.ext data, deduplicating common BTF types. The output file is newly generated, so choose a path that is not a valuable existing object. If you must replace one, copy it first:

$ cp --preserve=all example.bpf.o example.bpf.o.backup

If the command fails, the inputs remain available. Remove only a partial output that you have confirmed is disposable, or use a new output name on the next attempt. Do not run the build as root just to write into a directory with unsuitable ownership.

Checkpoint

file example.bpf.o should identify an ELF object for BPF. A file that exists but is empty or is identified as plain data is not a successful link.

4. Generate and inspect a normal skeleton

A skeleton embeds the object in a generated C header and gives the user-space program named access to maps, programs, links and supported global data. Generate it with an explicit object name so the C API is predictable:

$ bpftool gen skeleton example.bpf.o name example | tee example.skel.h
$ grep -E 'example__(open|load|attach|detach|destroy)' example.skel.h

The generated header is derived from the input object, so the header and object are a matching pair. Re-run this command whenever the BPF object changes. The explicit name example makes functions such as example__open(), example__load() and example__destroy() easy to recognise. Without it, the object filename supplies the name.

Generated code contains LGPL-2.1 and BSD-2-Clause licensing notices. Review the generated file before distributing it, and retain that notice. The header does not itself load or attach the program: your C application must call the generated functions and handle errors.

For a loader-style light skeleton, use the documented local option:

$ bpftool gen skeleton --use-loader example.bpf.o name example | tee example-loader.skel.h

Use this only when its reduced libbpf and libelf requirements fit your application. Verify the option with the local help output first.

5. Use a subskeleton for a library object

A subskeleton is for code that joins an already opened BPF object. It does not own the maps, programs or global variables, and destroying it does not unload those resources:

$ bpftool gen subskeleton library.bpf.o name library | tee library.subskel.h
$ grep -E 'library__(open|destroy)' library.subskel.h

Use this when a library is embedded in a larger BPF application and the larger application's bpf_object owns the load lifecycle. Do not substitute a subskeleton for a normal skeleton unless that ownership is deliberate. In C, the generated open function takes an already opened bpf_object; the generated destroy function frees subskeleton storage but does not unload the parent object.

6. Create a smaller CO-RE BTF file when needed

When a target kernel lacks CONFIG_DEBUG_INFO_BTF, libbpf may need an external BTF file to resolve CO-RE relocations. min_core_btf derives a smaller file from an input BTF file and one or more BPF objects:

$ bpftool gen min_core_btf 5.4.0-example.btf 5.4.0-smaller.btf one.bpf.o
$ test -s 5.4.0-smaller.btf && echo "minimal BTF exists"
$ bpftool btf dump file 5.4.0-smaller.btf format raw | sed -n '1,12p'

The input BTF must be suitable for the kernel and the object list must include every CO-RE object you plan to load. Add more objects as needed:

$ bpftool gen min_core_btf kernel.btf application.btf collector.bpf.o policy.bpf.o

The result is deliberately specific. A minimal BTF file made for one object is not a general replacement for the full kernel BTF and may not satisfy another object's relocations. Pass it to libbpf as the custom BTF path while opening the object, then test loading on the target kernel. Loading can require elevated privileges and can affect kernel state, so perform that step only in an approved test environment with a rollback plan.

7. Diagnose the common mistakes

  • Unknown option: compare bpftool version and bpftool gen help with the documentation you are reading. Distribution versions differ.
  • Invalid input: inspect every input with file. Source files, stripped unrelated ELF files and a BTF file are not interchangeable with BPF ELF objects.
  • Missing generated names: pass name OBJECT_NAME and use a C identifier-friendly name. Then regenerate the header and rebuild the user-space program.
  • CO-RE still fails: check that the reduced BTF came from the right kernel's full BTF and was generated with every object that will be loaded. A successful generation command does not prove that a later verifier load will succeed.

Do not use sudo for compilation or generation unless filesystem permissions genuinely require it. If a previous command created root-owned build files, restore ownership with your normal administrator procedure rather than making the whole build run as root.

Done means

  • bpftool gen help matches the subcommands and options you used.
  • file identifies the combined output as a BPF ELF object.
  • The generated header is rebuilt from the current object and exposes the expected object-name prefix.
  • You chose a normal skeleton, subskeleton or light skeleton according to ownership and runtime requirements.
  • If you generated minimal BTF, you recorded exactly which objects it covers and kept the full input BTF available.