Loading Objects

ebpf-go 提供了一个 eBPF 对象(ELF)加载器,旨在与上游的 libbpf 和 iproute2( tc / ip )项目保持兼容。ELF 文件通常是通过使用 LLVM 工具链( clang )编译 eBPF C 程序获得的。

本文描述了从已编译的 eBPF ELF 到内核中资源的整个过程。这涉及将 ELF 解析为 intermediate Go (Spec) types,这些类型可以在加载到内核之前进行修改和复制。

alt text

CollectionSpec

CollectionSpec 表示从 ELF 中提取的 eBPF 对象,可以通过调用 LoadCollectionSpec 获取。
在下面的示例中,在 eBPF C 中声明一个 Map 和 Program,然后使用 Go 加载并检查它们。

#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;
}
// 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 {
    panic(err)
}

// 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.
fmt.Println(p.Instructions)

可以以 ProgramSpec 的定义为例,更加深入的理解一下 Spec 的概念:

type ProgramSpec struct {
	// Name is passed to the kernel as a debug aid.
	//
	// Unsupported characters will be stripped.
	Name string

	// Type determines at which hook in the kernel a program will run.
	Type ProgramType

	// Network interface index the user intends to attach this program to after
	// loading. Only valid for some program types.
	//
	// Provides driver-specific context about the target interface to the
	// verifier, required when using certain BPF helpers.
	Ifindex uint32

	// AttachType of the program, needed to differentiate allowed context
	// accesses in some newer program types like CGroupSockAddr.
	//
	// Available on kernels 4.17 and later.
	AttachType AttachType

	// Name of a kernel data structure or function to attach to. Its
	// interpretation depends on Type and AttachType.
	AttachTo string

	// The program to attach to. Must be provided manually.
	AttachTarget *Program

	// The name of the ELF section this program originated from.
	SectionName string

	Instructions asm.Instructions

	// Flags is passed to the kernel and specifies additional program
	// load attributes.
	Flags uint32

	// License of the program. Some helpers are only available if
	// the license is deemed compatible with the GPL.
	//
	// See https://www.kernel.org/doc/html/latest/process/license-rules.html#id1
	License string

	// Version used by Kprobe programs.
	//
	// Deprecated on kernels 5.0 and later. Leave empty to let the library
	// detect this value automatically.
	KernelVersion uint32

	// The byte order this program was compiled for, may be nil.
	ByteOrder binary.ByteOrder
}

ProgramSpec 包含以下信息:

  • 程序身份信息
    • Name string: 程序名字,传给内核,主要用于调试和标识
    • SectionName string: 该程序所源自的ELF节名称
  • Type ProgramType: 这个程序属于哪种 eBPF 类型,例如:XDP、Kporbe、Tracepoint 等
  • attach 相关信息
    • Ifindex uint32: 这个程序之后可能会 attach 到的网卡索引
    • AttachType AttachType: 区分更细的 attach 场景。比如同样是 cgroup 类型程序,不同 attach 类型的上下文访问规则可能不同.
    • AttachTo string: 这个程序 attach 到什么地方
    • AttachTarget *Program: 这个程序要 attach 到另一个已经加载的 Program 上.
  • Instructions asm.Instructions: .bpf.c 编译后得到的字节码指令
  • 加载参数
    • Flags uint32: 表示额外加载选项。可以理解为传给 bpf(BPF_PROG_LOAD, ...) 的 flags.
    • License string
    • KernelVersion uint32
    • ByteOrder binary.ByteOrder: 字节序.

可以看出,同时也是顾名思义:ProgramSpec 是 eBPF Program 的加载说明书/规范.
它不是已经加载到内核里的 eBPF 程序,而是告诉 ebpf-go / Linux kernel:要加载一个什么样的 eBPF 程序。
而多个 xxxSpec 组成了 CollectionSpec.

NewCollection

将 ELF 解析为 CollectionSpec 后,可使用 NewCollection 将其加载到内核中,从而得到一个 Collection

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

// Instantiate a Collection from a CollectionSpec.
coll, err := ebpf.NewCollection(spec)
if err != nil {
    panic(err)
}
// 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 {
    panic(err)
}

Collection.Close 会关闭集合中的所有 Maps 和 Programs。在 Close() 之后与任何资源交互都将返回错误,因为它们底层的文件描述符会被关闭。请参阅 Object Lifecycle,以更好地理解 ebpf-go 如何管理其资源,以及处理 Maps 和 Programs 的最佳实践。

LoadAndAssign¶

LoadAndAssign 是一个便捷 API,可用于替代 NewCollection 。它有两个主要优势:

  • 它会自动从 Collection 中提取 Maps 和 Programs。不再需要 if m := coll.Maps["my_map"]; m == nil { return ... }
  • 选择性加载 Maps 和 Programs!仅将感兴趣的资源及其依赖项加载到内核中。非常适合处理只需部分加载的大型 CollectionSpecs。

先声明一个结构体,用来在把 Map 和 Program 加载到内核之后,接收指向它们的指针。再给这个结构体定义一个 Close() 方法,以便更方便地清理资源。

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
}

如果前面的代码片段看起来繁琐,可以使用 bpf2go。bpf2go 可以自动生成这类样板代码,并确保其与你的 C 代码保持同步。

接下来,实例化一个我们新声明类型的变量,并将其指针传递给 LoadAndAssign 。

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

// 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.
// 仅将 'obj' 中指定的资源插入(加载到)内核,并分配至各自字段。若ELF中未找到任何请求的资源,此操作将失败。加载映射或程序时遇到的任何错误也将在此处返回。
var objs myObjs
if err := spec.LoadAndAssign(&objs, nil); err != nil {
    panic(err)
}
defer objs.Close()

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

总结

整体阶段:

eBPF ELF .o 文件
        ↓
ebpf.CollectionSpec        // 加载前:描述信息
        ↓
ebpf.Collection / objects  // 加载后:真实内核对象 fd
        ↓ attach
真正被内核 hook 触发执行
  • CollectionSpec = 加载前,描述 eBPF object 里有什么
  • Collection = 加载后,持有内核里真实创建出来的对象

评论