# Version of libbpf to fetch headers from LIBBPF_VERSION=0.5.0
# The headers we want prefix=libbpf-"$LIBBPF_VERSION" headers=( "$prefix"/src/bpf_core_read.h "$prefix"/src/bpf_helper_defs.h "$prefix"/src/bpf_helpers.h "$prefix"/src/bpf_tracing.h )
# Fetch libbpf release and extract the desired headers curl -sL "https://github.com/libbpf/libbpf/archive/refs/tags/v${LIBBPF_VERSION}.tar.gz" | \ tar -xz --xform='s#.*/##'"${headers[@]}"
vmlinux.h
vmlinux.h 是使用工具生成的代码文件。它包含了系统运行 Linux 内核源代码中使用的所有类型定义。当我们编译 Linux 内核时,会输出一个称作 vmlinux 的文件组件,其是一个 ELF 的二进制文件,包含了编译好的可启动内核。vmlinux 文件通常也会被打包在主要的 Linux 发行版中。
//go:generate sh -c "echo Generating for amd64" //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang BPFDemo ./bpf/kprobe.c -- -DOUTPUT_SKB -D__TARGET_ARCH_x86 -I./bpf/headers
// BPFDemoMaps contains all maps after they have been loaded into the kernel. // // It can be passed to LoadBPFDemoObjects or ebpf.CollectionSpec.LoadAndAssign. type BPFDemoMaps struct { KprobeMap *ebpf.Map `ebpf:"kprobe_map"` }
// BPFDemoPrograms contains all programs after they have been loaded into the kernel. // // It can be passed to LoadBPFDemoObjects or ebpf.CollectionSpec.LoadAndAssign. type BPFDemoPrograms struct { KprobeExecve *ebpf.Program `ebpf:"kprobe_execve"` }
// BPFDemoObjects contains all objects after they have been loaded into the kernel. // // It can be passed to LoadBPFDemoObjects or ebpf.CollectionSpec.LoadAndAssign. type BPFDemoObjects struct { BPFDemoPrograms BPFDemoMaps }
// LoadBPFDemoObjects loads BPFDemo and converts it into a struct. // // The following types are suitable as obj argument: // // *BPFDemoObjects // *BPFDemoPrograms // *BPFDemoMaps // // See ebpf.CollectionSpec.LoadAndAssign documentation for details. funcLoadBPFDemoObjects(obj interface{}, opts *ebpf.CollectionOptions)error { spec, err := LoadBPFDemo() if err != nil { return err }
return spec.LoadAndAssign(obj, opts) }
实际查看 LoadAndAssign 可以看到它会加载 BPF Program 和 BPF Map 到内核
// LoadAndAssign loads Maps and Programs into the kernel and assigns them // to a struct. // struct { // Foo *ebpf.Program `ebpf:"xdp_foo"` // Bar *ebpf.Map `ebpf:"bar_map"` // Ignored int // } func(cs *CollectionSpec)LoadAndAssign(to interface{}, opts *CollectionOptions)error { loader := newCollectionLoader(cs, opts) defer loader.cleanup()
// Support assigning Programs and Maps, lazy-loading the required objects. assignedMaps := make(map[string]bool) getValue := func(typ reflect.Type, name string)(interface{}, error) { switch typ {
case reflect.TypeOf((*Program)(nil)): return loader.loadProgram(name)
case reflect.TypeOf((*Map)(nil)): assignedMaps[name] = true return loader.loadMap(name)
default: returnnil, fmt.Errorf("unsupported type %s", typ) } } //... }
// Open a Kprobe at the entry point of the kernel function and attach the // pre-compiled program. Each time the kernel function enters, the program // will increment the execution counter by 1. The read loop below polls this // map value once per second. kp, err := link.Kprobe(fn, objs.KprobeExecve) if err != nil { log.Fatalf("opening kprobe: %s", err) } defer kp.Close()
// kprobe opens a perf event on the given symbol and attaches prog to it. // If ret is true, create a kretprobe. funckprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions, ret bool)(*perfEvent, error) { // ... args := probeArgs{ pid: perfAllThreads, symbol: platformPrefix(symbol), ret: ret, }
// Use kprobe PMU if the kernel has it available. tp, err := pmuKprobe(args) if err == nil { return tp, nil } // ... // Use tracefs if kprobe PMU is missing. args.symbol = platformPrefix(symbol) tp, err = tracefsKprobe(args) // ...
// pmuProbe opens a perf event based on a Performance Monitoring Unit. // // Requires at least a 4.17 kernel. // e12f03d7031a "perf/core: Implement the 'perf_kprobe' PMU" // 33ea4b24277b "perf/core: Implement the 'perf_uprobe' PMU" // // Returns ErrNotSupported if the kernel doesn't support perf_[k,u]probe PMU funcpmuProbe(typ probeType, args probeArgs)(*perfEvent, error) { // ... switch typ { case kprobeType: // Create a pointer to a NUL-terminated string for the kernel. sp, err = unsafeStringPtr(args.symbol)
attr = unix.PerfEventAttr{ Type: uint32(et), // PMU event type read from sysfs Ext1: uint64(uintptr(sp)), // Kernel symbol to trace Config: config, // Retprobe flag } case uprobeType: // ... } rawFd, err := unix.PerfEventOpen(&attr, args.pid, 0, -1, unix.PERF_FLAG_FD_CLOEXEC) fd, err := sys.NewFD(rawFd) // ... // Kernel has perf_[k,u]probe PMU available, initialize perf event. return &perfEvent{ typ: typ.PerfEventType(args.ret), name: args.symbol, pmuID: et, cookie: args.cookie, fd: fd, }, nil }
// attach the given eBPF prog to the perf event stored in pe. // pe must contain a valid perf event fd. // prog's type must match the program type stored in pe. funcattachPerfEvent(pe *perfEvent, prog *ebpf.Program)(Link, error) { if prog == nil { returnnil, errors.New("cannot attach a nil program") } if prog.FD() < 0 { returnnil, fmt.Errorf("invalid program: %w", sys.ErrClosedFd) }
switch pe.typ { case kprobeEvent, kretprobeEvent, uprobeEvent, uretprobeEvent: if t := prog.Type(); t != ebpf.Kprobe { returnnil, fmt.Errorf("invalid program type (expected %s): %s", ebpf.Kprobe, t) } case tracepointEvent: if t := prog.Type(); t != ebpf.TracePoint { returnnil, fmt.Errorf("invalid program type (expected %s): %s", ebpf.TracePoint, t) } default: returnnil, fmt.Errorf("unknown perf event type: %d", pe.typ) }
funcattachPerfEventIoctl(pe *perfEvent, prog *ebpf.Program)(*perfEventIoctl, error) { if pe.cookie != 0 { returnnil, fmt.Errorf("cookies are not supported: %w", ErrNotSupported) }
// Assign the eBPF program to the perf event. err := unix.IoctlSetInt(pe.fd.Int(), unix.PERF_EVENT_IOC_SET_BPF, prog.FD()) if err != nil { returnnil, fmt.Errorf("setting perf event bpf program: %w", err) }
// PERF_EVENT_IOC_ENABLE and _DISABLE ignore their given values. if err := unix.IoctlSetInt(pe.fd.Int(), unix.PERF_EVENT_IOC_ENABLE, 0); err != nil { returnnil, fmt.Errorf("enable perf event: %s", err) }
pi := &perfEventIoctl{pe}
// Close the perf event when its reference is lost to avoid leaking system resources. runtime.SetFinalizer(pi, (*perfEventIoctl).Close) return pi, nil }
查看 Map 信息
定期查看 eBPF map 的更新:
1 2 3 4 5 6 7 8 9 10 11 12 13
// Read loop reporting the total amount of times the kernel // function was entered, once per second. ticker := time.NewTicker(1 * time.Second)
log.Println("Waiting for events..")
forrange ticker.C { var value uint64 if err := objs.KprobeMap.Lookup(mapKey, &value); err != nil { log.Fatalf("reading map: %v", err) } log.Printf("%s called %d times\n", fn, value) }
容器镜像
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
FROM ubuntu:20.04 RUN apt update -y -q RUN DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y -q curl build-essential ca-certificates RUN curl -s https://storage.googleapis.com/golang/go1.16.3.linux-amd64.tar.gz| tar -v -C /usr/local -xz ENV PATH $PATH:/usr/local/go/bin RUN apt install -y wget gnupg2 RUNprintf"deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-12 main" | tee /etc/apt/sources.list.d/llvm-toolchain-xenial-12.list RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - RUN apt -y update RUN apt install -y llvm clang git WORKDIR /ebpf COPY . . RUN make RUN chmod a+x /ebpf ENTRYPOINT ["./ebpf"] CMD ["./ebpf"]