运行 eBPF 程序前,内核需要开启一系列配置项,并且目标符号(kprobe)和事件(tracepoint)必须真实存在。如果环境不满足,程序加载时会报各种难懂的错误。

本文先给出需要开启的内核配置清单,再介绍一个自动化预检脚本,最后说明如何用脚本自动维护探针列表。

一、内核需要开启的配置

1.1 eBPF 基础特性

来源:BCC 官方文档(docs/kernel_config.md)。

CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_JIT=y
CONFIG_HAVE_EBPF_JIT=y
CONFIG_BPF_EVENTS=y
CONFIG_IKHEADERS=y

说明:

  • CONFIG_BPF_SYSCALL:提供 bpf() 系统调用。
  • CONFIG_BPF_JIT:BPF 程序 JIT 编译。
  • CONFIG_BPF_EVENTS:支持 kprobe/tracepoint 向 BPF 传递事件。
  • CONFIG_IKHEADERS:内核头文件打包在 /sys/kernel/kheaders.tar.xz,部分工具需要。

如果要用 BTF(BPF Type Format),需要额外开启:

CONFIG_DEBUG_INFO_BTF=y

1.2 探针与跟踪支持

# tracepoint
CONFIG_TRACEPOINTS=y
CONFIG_HAVE_SYSCALL_TRACEPOINTS=y

# kprobe
CONFIG_KPROBES=y
CONFIG_KPROBE_EVENTS=y

# uprobe
CONFIG_UPROBES=y
CONFIG_UPROBE_EVENTS=y

修改方式:编辑 .config 后重新 make,或使用 scripts/config 命令。

二、预检脚本

以下脚本可以在 eBPF 开发前一次性检查环境,输出彩色 PASS/FAIL 结果,并把缺失详情写入日志文件。

2.1 检查内核配置

读取 /boot/config-$(uname -r)(或 /proc/config.gz),检查关键配置是否为 =y:

kernel_config_path=/boot/config-$(uname -r)

function kernel_config() {
  config_field="$1=y"
  ret=0
  if [ -f $kernel_config_path ]; then
    ret=$(grep ${config_field} ${kernel_config_path} | wc -l)
  elif [ -f "/proc/config.gz" ]; then
    ret=$(zcat /proc/config.gz | grep ${config_field} | wc -l)
  fi
  echo $ret
}

# 检查 eBPF 相关配置
for cfg in CONFIG_BPF CONFIG_BPF_SYSCALL CONFIG_BPF_JIT CONFIG_BPF_EVENTS; do
  echo "$cfg = $(kernel_config $cfg)"
done

# 检查探针相关配置
for cfg in CONFIG_TRACEPOINTS CONFIG_HAVE_SYSCALL_TRACEPOINTS \
           CONFIG_KPROBES CONFIG_KPROBE_EVENTS \
           CONFIG_UPROBES CONFIG_UPROBE_EVENTS; do
  echo "$cfg = $(kernel_config $cfg)"
done

2.2 检查 kprobe 符号是否存在

/proc/kallsyms 中列出了可探测的内核符号。预检脚本把符号表读入字典,再逐个确认需要的函数:

kprobe_path=/proc/kallsyms
declare -A kprobe_func_dict

for func in `cat $kprobe_path | awk -F ' ' '{print $3}'`; do
  kprobe_func_dict[$func]=1
done

kprobe_func=("finish_task_switch" "blk_account_io_start" "sys_write"
             "tcp_v4_connect" "tcp_sendmsg" "udp_sendmsg" ...)

found=0
not_found=0
for kprobe in ${kprobe_func[*]}; do
  if [ -z ${kprobe_func_dict[$kprobe]} ]; then
    echo "not found kprobe func symbol $kprobe" >> precheck.log
    echo "find similar symbol:" >> precheck.log
    echo `grep $kprobe $kprobe_path` >> precheck.log
    ((not_found++))
  else
    ((found++))
  fi
done

如果符号缺失,日志会同时给出相似符号,方便确认是否是命名差异。

2.3 检查 tracepoint 事件

tracepoint 的可用列表位于:

trace_path=/sys/kernel/debug/tracing/available_events

格式为 subsystem:event,例如 sched:sched_switch、syscalls:sys_enter_write。预检脚本读取该文件后逐一比对:

trace_func=("sched:sched_switch" "syscalls:sys_enter_write"
            "syscalls:sys_exit_write" "sock:inet_sock_set_state" ...)

2.4 查找 vmlinux

BTF 或符号调试需要 vmlinux,脚本按常见路径依次查找:

os=$(uname -r)
paths=(
  "/sys/kernel/btf/vmlinux"
  "/lib/modules/$os/vmlinux-$os"
  "/lib/modules/$os/build/vmlinux"
  "/usr/lib/modules/$os/kernel/vmlinux"
  "/usr/lib/debug/boot/vmlinux-$os.debug"
  "/usr/lib/debug/lib/modules/$os/vmlinux"
)

2.5 完整输出

综合检查后输出示例:

OS Release: ...
Kernel Version: ...
bpf feature: bpf_syscall[on] bpf_jit[on] bpf_events[on] bpf_vm[on]
kernel event: tracepoint[on] syscall_tracepoint[on] uprobe[on] kprobe[on]
vmlinux found: /sys/kernel/btf/vmlinux
Check Kprobe [PASS]
Check TracePoint [PASS]

三、自动化维护探针列表

预检脚本中需要探测的 kprobe/tracepoint 列表,可以通过解析 Go 源码中的注册代码自动生成,避免手工维护:

kprobe_regx='Kprobe\(\"(.*?)\"'
trace_regx='Tracepoint\((\".*?\",\s+\".*?\")'

for file in `ls ../ | grep ^module_`; do
  for func in `grep -Eo $kprobe_regx $file`; do
    kprobe_func+=${func#Kprobe\(}
    kprobe_func+=$" "
  done
  for func in `grep -Eo $trace_regx $file`; do
    func=${func#Tracepoint\(}
    trace_point_func+=$(echo $func | sed -E 's/",\s+"/':'/g')
    trace_point_func+=$" "
  done
done

再通过 sed 在预检脚本的标记区间内自动替换:

#<<kprobe-begin>>
kprobe_func=(...)
#<<kprobe-end>>

#<<tracepoint-begin>>
trace_func=(...)
#<<tracepoint-end>>
kprobe_begin_line=$(awk '/#<<kprobe-begin>>/{print NR}' precheck.sh)
kprobe_end_line=$(awk '/#<<kprobe-end>>/{print NR}' precheck.sh)

# 删除旧内容
sed -i "$((kprobe_begin_line+1)), $((kprobe_end_line-1))d" precheck.sh

# 写入新内容
sed -i "$((kprobe_begin_line+1))i kprobe_func=(${kprobe_func[*]})" precheck.sh

这样源码新增探针后,只需重新运行生成脚本,预检列表即可同步。

四、总结

  • eBPF 依赖内核配置:CONFIG_BPF、BPF_SYSCALL、BPF_JIT、BPF_EVENTS、IKHEADERS,BTF 需要 DEBUG_INFO_BTF。
  • kprobe 符号需要在 /proc/kallsyms 中存在,tracepoint 需要在 available_events 中存在。
  • 预检脚本通过 PASS/FAIL 输出和详细日志,快速定位环境问题。
  • 探针列表可以从源码注册点自动生成,配合 sed 标记区间实现零手工维护。

相关脚本

整理自 cyy-tag/bash-example 仓库: