Skip to content

Loading Objects

ebpf-go ships an eBPF object (ELF) loader that aims to be compatible with the upstream libbpf and iproute2 (tc/ip) projects. An ELF is typically obtained by compiling a eBPF C program using the LLVM toolchain (clang).

This page describes the journey from compiled eBPF ELF to resources in the kernel. This involves parsing the ELF into intermediate Go (Spec) types that can be modified and copied before loading them into the kernel.

graph LR
    ELF --> ProgramSpec --> Program
    ELF --> Types
    ELF --> MapSpec --> Map
    Map & Program --> Links
    subgraph Collection
        Program & Map
    subgraph CollectionSpec
        ProgramSpec & MapSpec & Types


A CollectionSpec represents eBPF objects extracted from an ELF, and can be obtained by calling LoadCollectionSpec. In the examples below, we declare a Map and Program in eBPF C, then load and inspect them using Go. Use the tabs to explore the Go and C counterparts below.

Parse ELF and inspect its CollectionSpec
// Parse an ELF into a CollectionSpec.
// bpf_prog.o is the result of compiling BPF C code.
spec, err := ebpf.LoadCollectionSpec("bpf_prog.o")
if err != nil {

// Look up the MapSpec and ProgramSpec in the CollectionSpec.
m := spec.Maps["my_map"]
p := spec.Programs["my_prog"]
// Note: We've omitted nil checks for brevity, take a look at
// LoadAndAssign for an automated way of checking for maps/programs.

// Inspect the map and program type.
fmt.Println(m.Type, p.Type)

// Print the map's key and value BTF types.
fmt.Println(m.Key, m.Value)

// Print the program's instructions in a human-readable form,
// similar to llvm-objdump -S.

All of a Spec's attributes can be modified, and those modifications influence the resources created in the kernel. Be aware that doing so may invalidate any assumptions made by the compiler, resulting in maps or programs being rejected by the kernel. Proceed with caution.

Declare a minimal map and a program
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

// Declare a hash map called 'my_map' with a u32 key and a u64 value.
// The __uint, __type and SEC macros are from libbpf's bpf_helpers.h.
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __type(key, __u32);
    __type(value, __u64);
    __uint(max_entries, 1);
} my_map SEC(".maps");

// Declare a dummy socket program called 'my_prog'.
SEC("socket") int my_prog() {
    return 0;

See Section Naming to learn about the use of the SEC() macro in the example above.


After parsing the ELF into a CollectionSpec, it can be loaded into the kernel using NewCollection, resulting in a Collection.

spec, err := ebpf.LoadCollectionSpec("bpf_prog.o")
if err != nil {

// Instantiate a Collection from a CollectionSpec.
coll, err := ebpf.NewCollection(spec)
if err != nil {
// Close the Collection before the enclosing function returns.
defer coll.Close()

// Obtain a reference to 'my_map'.
m := coll.Maps["my_map"]

// Set map key '1' to value '2'.
if err := m.Put(uint32(1), uint64(2)); err != nil {

Collection.Close closes all Maps and Programs in the Collection. Interacting with any resources after Close() will return an error, since their underlying file descriptors will be closed. See Object Lifecycle to gain a better understanding of how ebpf-go manages its resources and for best practices handling Maps and Programs.


LoadAndAssign is a convenience API that can be used instead of NewCollection. It has two major benefits:

  • It automates pulling Maps and Programs out of a Collection. No more if m := coll.Maps["my_map"]; m == nil { return ... }.
  • Selective loading of Maps and Programs! Only resources of interest and their dependencies are loaded into the kernel. Great for working with large CollectionSpecs that only need to be partially loaded.

First, declare a struct that will receive pointers to a Map and a Program after loading them into the kernel. Give it a Close() method to make cleanup easier.

Declare a custom struct myObjs
type myObjs struct {
    MyMap  *ebpf.Map     `ebpf:"my_map"`
    MyProg *ebpf.Program `ebpf:"my_prog"`

func (objs *myObjs) Close() error {
    if err := objs.MyMap.Close(); err != nil {
        return err
    if err := objs.MyProg.Close(); err != nil {
        return err
    return nil

Use bpf2go if the preceding code snippet looks tedious. bpf2go can generate this kind of boilerplate code automatically and will make sure it stays in sync with your C code.

Next, instantiate a variable of our newly-declared type and pass its pointer to LoadAndAssign.

Pass a custom struct to LoadAndAssign
spec, err := ebpf.LoadCollectionSpec("bpf_prog.o")
if err != nil {

// Insert only the resources specified in 'obj' into the kernel and assign
// them to their respective fields. If any requested resources are not found
// in the ELF, this will fail. Any errors encountered while loading Maps or
// Programs will also be returned here.
var objs myObjs
if err := spec.LoadAndAssign(&objs, nil); err != nil {
defer objs.Close()

// Interact with MyMap through the custom struct.
if err := objs.MyMap.Put(uint32(1), uint64(2)); err != nil {

If your use case requires dynamically renaming keys in CollectionSpec.Maps, you may need to use NewCollection instead. Map and Program names in struct tags are baked into the Go binary at compile time.

Type Information (BTF)

If an eBPF ELF was built with clang -g, it will automatically contain BTF type information. This information can be accessed programmatically through CollectionSpec.Types. Note that this field will be nil if the ELF was built without BTF.

spec, err := ebpf.LoadCollectionSpec("bpf_prog.o")
if err != nil {

// Look up the __64 type declared in linux/bpf.h.
t, err := spec.Types.AnyTypeByName("__u64")
if err != nil {

Many eBPF features rely on ELFs to be built with BTF, and there is little to be gained by opting out of it. clang -g also includes DWARF information in the ELF which can be safely removed with llvm-strip. eBPF does not rely on DWARF information.

Last updated 2023-11-24
Authored by Timo Beckers