PAYATHON 2026

Linux UML 内核能否配置为支持 BPF tracepoints?

排障赵师傅

结论

UML 可以启用普通 tracing 和系统调用 tracepoints。但按上游 Linux v6.18 的配置依赖,仅修改 .config 无法启用题述的 BPF tracepoint 程序。CONFIG_TRACING 与 CONFIG_BPF_EVENTS 提供不同层次的支持,启用前者后,后者仍可能不可用。

这里依据的是上游 v6.18 源码。题述 6.18.26 的具体行为,还需在实际使用的源码树中复核,尤其是在有发行版或本地补丁的情况下。

SEC("tracepoint/syscalls/sys_enter_execve") 通常应对应 BPF_PROG_TYPE_TRACEPOINT。如果观察到 BPF_PROG_TYPE_TRACING,需要确认失败的加载请求来自目标程序,还是 libbpf 的内部能力探测。

为什么手动启用后没有 CONFIG_TRACING

CONFIG_TRACING 是没有用户提示项的内部符号,其值由其他配置通过 select 等规则决定。直接向 .config 写入 CONFIG_TRACING=y,不能保证 Kconfig 重新计算后会保留这个值。Kconfig 语言文档

scripts/config 只修改配置文件,不校验依赖。执行 make ARCH=um olddefconfig 或开始构建时,Kconfig 会重新计算不满足规则的设置。scripts/config 源码

要启用系统调用事件,应设置 CONFIG_FTRACE_SYSCALLS。上游的选择关系是:

FTRACE_SYSCALLS
  selects GENERIC_TRACER
    selects TRACING
      selects TRACEPOINTS、EVENT_TRACING 等

UML 已选择 HAVE_SYSCALL_TRACEPOINTS,也提供 tracing 所需的 TRACE_IRQFLAGS_SUPPORT 和 STACKTRACE_SUPPORT,没有一律禁用普通 CONFIG_TRACING 的规则。UML Kconfig、tracing Kconfig

原配置列表中的 FUNCTION_TRACER、DYNAMIC_FTRACE 各有架构依赖,启用普通系统调用 tracepoint 并不需要它们。

BPF tracing 为什么仍然不可用

上游 v6.18 中,BPF_EVENTS 的依赖包括:

BPF_SYSCALL
(KPROBE_EVENTS || UPROBE_EVENTS) && PERF_EVENTS

PERF_EVENTS 依赖 HAVE_PERF_EVENTS,kprobe 和 uprobe 事件也依赖相应的架构能力。上游 UML 的 Kconfig 没有选择所需的 perf、kprobe/uprobe 架构支持,无法通过普通配置满足这条依赖链。即使宿主机采用 x86,UML 也不会因此继承原生 x86 内核的这些能力。BPF_EVENTS 依赖、PERF_EVENTS 依赖、UML 的 x86 配置

这也能解释 find_prog_type() 为什么返回 -EINVAL:

  • BPF_PROG_TYPE_TRACEPOINT、BPF_PROG_TYPE_TRACING 等类型的操作表受 CONFIG_BPF_EVENTS 控制。
  • 如果对应操作表没有编译进内核,find_prog_type() 查不到该类型,就会返回 -EINVAL。
  • 这一步发生在 BPF verifier 检查之前,因此还不能判断程序指令或 BTF 内容是否有问题。

对应条件可见 bpf_types.h 和 find_prog_type() 实现。

配置与排查步骤

1. 先启用普通系统调用 tracing

在 Linux 内核源码根目录中,对已有的 UML 配置执行:

scripts/config --file .config \
    --enable FTRACE \
    --enable FTRACE_SYSCALLS

make ARCH=um olddefconfig

rg 'CONFIG_(FTRACE|FTRACE_SYSCALLS|TRACING|TRACEPOINTS|EVENT_TRACING|BPF_EVENTS|PERF_EVENTS)' .config

如果使用了 O=... 构建目录,scripts/config --file 应指向该目录里的 .config,后续 make 也必须使用相同的 O=...。

应检查 olddefconfig 处理后的配置。若设置被清除,可运行:

make ARCH=um menuconfig

按 / 搜索相关符号,查看当前值、依赖和 Selected by 信息。不要手动设置 HAVE_* 符号来绕过依赖,这些符号表示架构已经提供相应实现。

2. 启动新 UML 内核后确认事件存在

在 UML 内部,以具备相应权限的用户检查 tracefs。如果尚未挂载,执行:

mkdir -p /sys/kernel/tracing
mount -t tracefs tracefs /sys/kernel/tracing

随后检查:

cat /sys/kernel/tracing/events/syscalls/sys_enter_execve/id
cat /sys/kernel/tracing/events/syscalls/sys_enter_execve/format

事件存在只能证明普通 trace event 已注册,不能证明 BPF 程序可以附着到它。若事件不存在,先检查是否启动了新构建的 UML 内核,以及 CONFIG_FTRACE_SYSCALLS 是否已生效。

3. 核对实际加载的 BPF 程序类型

可以用下面的普通 tracepoint 最小程序,排除上下文读取、map 和 CO-RE 重定位等因素:

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

SEC("tracepoint/syscalls/sys_enter_execve")
int on_execve(void *ctx)
{
    return 0;
}

char LICENSE[] SEC("license") = "GPL";

libbpf 的标准映射关系是:

ELF section程序类型
tracepoint/...、tp/...BPF_PROG_TYPE_TRACEPOINT
raw_tracepoint/...、raw_tp/...BPF_PROG_TYPE_RAW_TRACEPOINT
tp_btf/...、fentry/...、fexit/...BPF_PROG_TYPE_TRACING

这些映射可见 libbpf section 定义。对象携带 BTF 信息,本身不会使 tracepoint/... 对应的程序变成 BPF_PROG_TYPE_TRACING。

可在 bpf_object__open_file() 成功后、bpf_object__load() 之前检查:

struct bpf_program *prog;

bpf_object__for_each_program(prog, obj) {
    fprintf(stderr, "name=%s section=%s type=%u\n",
            bpf_program__name(prog),
            bpf_program__section_name(prog),
            (unsigned int)bpf_program__type(prog));
}

如果目标程序仍显示为 BPF_PROG_TYPE_TRACING,检查加载器是否调用过 bpf_program__set_type()、加载的是否为预期对象,以及对象中是否还有其他程序。还需结合 libbpf 调试日志确认失败请求的来源:内核中观察到的请求可能属于能力探测,不能仅凭一次 BPF_PROG_LOAD 错误就认定目标程序加载失败。

可行的处理方式与注意事项

如果只需观察 UML 内的 execve,可以启用 FTRACE_SYSCALLS,通过 tracefs 使用普通系统调用事件。

运行 libbpf tracepoint 程序需要使用已支持并启用 CONFIG_BPF_EVENTS 的内核环境。如果必须留在 UML,则需要补齐相应架构实现,并调整配置依赖。继续追加 scripts/config --enable 命令无法完成这项工作。

CONFIG_BPF=y、CONFIG_BPF_SYSCALL=y 和 CONFIG_DEBUG_INFO_BTF=y 分别提供不同能力,单独启用其中任何一项都不能保证 BPF tracing 可用。将 section 改成 tp_btf/... 或 raw_tp/... 也无法绕过限制:在上述上游配置中,这些程序类型同样受 CONFIG_BPF_EVENTS 控制。

备注:内容仅供参考。