首頁>技術>

eBPF 提供了強大的跟蹤、探測以及高效核心網路等功能,但由於其介面處於作業系統底層,新手入門起來還是有很大難度,特別是如何編寫 eBPF 程式是入門的一大難點。本文將介紹一些常用的 eBPF 程式設計框架。

BCC 的優點就是簡單易用,但也有很多缺點:

啟動時編譯,導致啟動緩慢,且編譯也需要耗費較高的 CPU 和記憶體資源。

編譯 eBPF 要求所有主機上都安裝核心標頭檔案。

編譯錯誤只有在執行的時候才能檢測到,排錯困難。

由於這些問題存在,BCC 正在基於 libbpf 將所有工具 轉換 為可直接執行的二進位制檔案,無需外部依賴,從而更易分發到實際生產環境中。轉換後的工具,因無需動態編譯和介面轉換,可以獲得更高的效能和更少的資源佔用。

除此之外,libbpf 還基於 BTF 和 CO-RE (Compile-Once Run-Everywhere) 提供了更好的便攜性(相容新舊核心版本):

BTF 是 BPF 型別格式,用於避免依賴 Clang 和核心標頭檔案。

CO-RE 則使得 BTF 位元組碼支援重定位,避免 LLVM 重新編譯的需要。

藉助於 BTF 和 CO-RE 的優勢,你也可以在 /sys/kernel/btf/vmlinux 找到核心的 BTF 資訊,甚至可以透過 bpftool 將其匯出(一般放到檔案 vmlinux.h 中):

bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

你可以在 libbpf-tools 找到 BCC 目前已遷移完成的工具。

libbpf-bootstrap

libbpf 在使用上並不是很直觀,所以 eBPF 維護者開發了一個腳手架專案 libbpf-bootstrap。它結合了 BPF 社群的最佳開發實踐,為初學者提供了一個簡單易用的上手框架。

在使用 libbpf-bootstrap 時,需要首先安裝 LLVM 和依賴庫檔案:

sudo apt install -y bison build-essential cmake flex git libedit-dev pkg-config libmnl-dev \   python zlib1g-dev libssl-dev libelf-dev libcap-dev libfl-dev llvm clang pkg-config \   gcc-multilib luajit libluajit-5.1-dev libncurses5-dev libclang-dev clang-tools

然後檢出其腳手架程式碼,檢查示例程式碼是否可以編譯透過:

# checkout libbpf-bootstrapgit clone https://github.com/libbpf/libbpf-bootstrap# update submodulesgit submodule update --init --recursive# build existing samplescd src && make

接下來,建立兩個檔案,分別是使用者空間的 hello.c 以及 BPF 程式 hello.bpf.c(libbpf-bootstrap 要求 BPF 檔案的格式總是 <APP-NAME>.bpf.c)。

/* cat hello.bpf.c */#include <linux/bpf.h>#include <bpf/bpf_helpers.h>SEC("tracepoint/syscalls/sys_enter_execve")int handle_tp(void *ctx){    int pid = bpf_get_current_pid_tgid()>> 32;    char fmt[] = "BPF triggered from PID %d.\n";    bpf_trace_printk(fmt, sizeof(fmt), pid);    return 0;}char LICENSE[] SEC("license") = "Dual BSD/GPL";
/* cat hello.c */#include <stdio.h>#include <stdlib.h>#include <string.h>#include <assert.h>#include <errno.h>#include <fcntl.h>#include <unistd.h>#include <sys/resource.h>#include <bpf/libbpf.h>#include "hello.skel.h"#define DEBUGFS "/sys/kernel/debug/tracing/"/* logging function used for debugging */static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args){#ifdef DEBUGBPF    return vfprintf(stderr, format, args);#else    return 0;#endif}/* read trace logs from debug fs */void read_trace_pipe(void){    int trace_fd;    trace_fd = open(DEBUGFS "trace_pipe", O_RDONLY, 0);    if (trace_fd < 0)        return;    while (1) {        static char buf[4096];        ssize_t sz;        sz = read(trace_fd, buf, sizeof(buf) - 1);        if (sz> 0) {            buf[sz] = 0;            puts(buf);        }    }}/* set rlimit (required for every app) */static void bump_memlock_rlimit(void){    struct rlimit rlim_new = {        .rlim_cur = RLIM_INFINITY,        .rlim_max = RLIM_INFINITY,    };    if (setrlimit(RLIMIT_MEMLOCK, &rlim_new)) {        fprintf(stderr, "Failed to increase RLIMIT_MEMLOCK limit!\n");        exit(1);    }}int main(int argc, char **argv){    struct hello_bpf *skel;    int err;    /* Set up libbpf errors and debug info callback */    libbpf_set_print(libbpf_print_fn);    /* Bump RLIMIT_MEMLOCK to allow BPF sub-system to do anything */    bump_memlock_rlimit();    /* Open BPF application */    skel = hello_bpf__open();    if (!skel) {        fprintf(stderr, "Failed to open BPF skeleton\n");        return 1;    }    /* Load & verify BPF programs */    err = hello_bpf__load(skel);    if (err) {        fprintf(stderr, "Failed to load and verify BPF skeleton\n");        goto cleanup;    }    /* Attach tracepoint handler */    err = hello_bpf__attach(skel);    if (err) {        fprintf(stderr, "Failed to attach BPF skeleton\n");        goto cleanup;    }    printf("Hello BPF started, hit Ctrl+C to stop!\n");    read_trace_pipe();cleanup:    hello_bpf__destroy(skel);    return -err;}

更新 Makefile 的 APPS 列表

APPS = minimal bootstrap uprobe hello

最後,編譯執行 hello 程式:

$ make  BPF      .output/hello.bpf.o  GEN-SKEL .output/hello.skel.h  CC       .output/hello.o  BINARY   hello$ ./helloHello BPF started, hit Ctrl+C to stop!<...>-241424  [006] d... 202520.596987: bpf_trace_printk: BPF triggered from PID 241424.

可以發現,用 libbpf-bootstrap 開發 BPF 程式非常方便。其原始碼庫中三個示例的解析可以參考 Building BPF applications with libbpf-bootstrap,而更多的示例則可以檢視 BCC 中的 libbpf-tools。

注意: libbpf 需要開啟核心選項 CONFIG_DEBUG_INFO_BTF=y 以及 CONFIG_DEBUG_INFO=y。在編譯核心時,推薦安裝 pahole 1.16+,否則的話,就無法生成 BTF。 或者,也可以從 https://kernel.ubuntu.com/~kernel-ppa/mainline/ 直接下載已經預設開啟這些選項的核心 DEB 包(比如 v5.10.9)。

核心原始碼

除了以上兩種方法,最後一種門檻更高一些的方法是從核心原始碼中直接編譯 BPF 程式。這種方法需要對核心編譯有一定了解,且需要善於運用搜索引擎解決編譯過程中的各種問題。

首先安裝必要的依賴:

sudo apt install -y bison build-essential cmake flex git libedit-dev pkg-config libmnl-dev \   python zlib1g-dev libssl-dev libelf-dev libcap-dev libfl-dev llvm clang pkg-config \   gcc-multilib luajit libluajit-5.1-dev libncurses5-dev libclang-dev clang-tools

然後檢出核心原始碼:

apt install linux-sourcecd /usr/src/linux-source-5.4.0 # 版本取決於具體系統tar jxf linux-source-5.4.0.tar.bz2cd linux-source-5.4.0

最後編譯核心 BPF 程式示例:

cp /boot/config-5.4.0-40-generic .configmake headers_installmake M=samples/bpf

而具體的 Hello World 可以參考 eBPF 環境搭建。

核心中的程式 hello_kern.c:

#include <linux/bpf.h>#include "bpf_helpers.h"#define SEC(NAME) __attribute__((section(NAME), used))SEC("tracepoint/syscalls/sys_enter_execve")int bpf_prog(void *ctx){    char msg[] = "Hello BPF!\n";    bpf_trace_printk(msg, sizeof(msg));    return 0;}char _license[] SEC("license") = "GPL";

使用者態的程式 hello_user.c:

#include <stdio.h>#include "bpf_load.h"int main(int argc, char **argv){    if(load_bpf_file("hello_kern.o") != 0)    {        printf("The kernel didn't load BPF program\n");        return -1;    }    read_trace_pipe();    return 0;}

在對應的位置修改 Makefile 檔案,新增以下三行:

hostprogs-y += hellohello-objs := bpf_load.o hello_user.oalways += hello_kern.o

最後編譯執行:

# V=1 檢視詳細編譯輸出make M=samples/bpf V=1cd samples/bpf./hello
小結

本文介紹了三種 eBPF 入門的程式設計方法,分別是 BCC、libbpf-bootstrap 以及核心原始碼。對於入門者來說,推薦用 libbpf-bootstrap 作為入門學習參考。

13
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Java,JUC併發工具包,記憶體可見性volatile關鍵字