Linux 内核 libbpf 编程模型:BPF 程序类型、Attach 类型与 ELF 段名完整指南
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
本文以 Linux 内核仓库中 libbpf 官方文档《Program Types and ELF Sections》为主体,系统讲解 BPF 程序类型(BPF_PROG_TYPE_*)、Attach 类型与 libbpf 所识别的 ELF 段名(SEC(...))之间的完整对应关系。读完本文,你可以准确写出任意一类 BPF 程序的SEC()段声明,理解extras命名规则(如kprobe/<function>+<offset>、tracepoint/<category>/<name>)、区分可休眠(sleepable)程序,并能结合 libbpf 源码 追溯段名解析的底层实现,为 BPF 开发、加载与挂载(attach)打下坚实基础。
一、核心概念:程序类型、Attach 类型与 ELF 段名
libbpf 是一个用户态库(源码位于 tools/lib/bpf 目录),负责解析 BPF 对象文件的 ELF 段(section)、推断程序类型、并通过bpf()系统调用加载和挂载 BPF 程序。开发者在 BPF C 代码中用SEC("...")宏指定段名,libbpf 依据段名完成三件事:
- 推断程序类型:例如段名以
xdp开头则推断为BPF_PROG_TYPE_XDP; - 推断 Attach 类型:例如
cgroup/skb对应BPF_CGROUP_INET_INGRESS/EGRESS; - 执行自动挂载(auto-attach):段名中的
extras部分(/之后的内容)提供如何挂载的具体细节,如SEC("tracepoint/sched/sched_switch")表示挂载到sched类别下的sched_switch追踪点。
libbpf 对段名的识别遵循两条基本规则(原文档核心内容):
type为精确匹配:段名必须完全等于类型名,例如SEC("socket");type+表示可扩展匹配:既可以是精确的SEC("type"),也可以是良构的SEC("type/extras"),其中type与extras之间用/分隔。
当指定了extras时,它描述的是“如何自动挂载该 BPF 程序”的细节。extras的格式取决于程序类型,例如 tracepoint 为SEC("tracepoint/<category>/<name>"),USDT 探针为SEC("usdt/<path>:<provider>:<name>")。各类extras的具体格式见本文第四节。
从源码结构看段名解析
在 tools/lib/bpf/elf.c 中,libbpf 通过bpf_object__elf_collect()遍历 ELF 文件的各个 section,根据 section 名前缀匹配程序类型表,并设置bpf_prog结构中的prog_type、expected_attach_type与attach_prog_name等字段。程序类型的字符串化映射表bpf_prog_type_str定义在 tools/lib/bpf/libbpf.c 附近(如[BPF_PROG_TYPE_CGROUP_DEVICE] = "cgroup_device"),这与 uAPI 头文件 include/uapi/linux/bpf.h 中enum bpf_prog_type的定义一一对应。
二、程序类型与 ELF 段名对照总表
下表完整继承自原文档,列出 libbpf 支持的所有程序类型、对应的 Attach 类型、ELF 段名以及是否支持可休眠(Sleepable)。
| 程序类型 | Attach 类型 | ELF 段名 | 可休眠 |
|---|---|---|---|
BPF_PROG_TYPE_CGROUP_DEVICE | BPF_CGROUP_DEVICE | cgroup/dev | |
BPF_PROG_TYPE_CGROUP_SKB | (无 attach 类型) | cgroup/skb | |
BPF_CGROUP_INET_EGRESS | cgroup_skb/egress | ||
BPF_CGROUP_INET_INGRESS | cgroup_skb/ingress | ||
BPF_PROG_TYPE_CGROUP_SOCKOPT | BPF_CGROUP_GETSOCKOPT | cgroup/getsockopt | |
BPF_CGROUP_SETSOCKOPT | cgroup/setsockopt | ||
BPF_PROG_TYPE_CGROUP_SOCK_ADDR | BPF_CGROUP_INET4_BIND | cgroup/bind4 | |
BPF_CGROUP_INET4_CONNECT | cgroup/connect4 | ||
BPF_CGROUP_INET4_GETPEERNAME | cgroup/getpeername4 | ||
BPF_CGROUP_INET4_GETSOCKNAME | cgroup/getsockname4 | ||
BPF_CGROUP_INET6_BIND | cgroup/bind6 | ||
BPF_CGROUP_INET6_CONNECT | cgroup/connect6 | ||
BPF_CGROUP_INET6_GETPEERNAME | cgroup/getpeername6 | ||
BPF_CGROUP_INET6_GETSOCKNAME | cgroup/getsockname6 | ||
BPF_CGROUP_UDP4_RECVMSG | cgroup/recvmsg4 | ||
BPF_CGROUP_UDP4_SENDMSG | cgroup/sendmsg4 | ||
BPF_CGROUP_UDP6_RECVMSG | cgroup/recvmsg6 | ||
BPF_CGROUP_UDP6_SENDMSG | cgroup/sendmsg6 | ||
BPF_CGROUP_UNIX_CONNECT | cgroup/connect_unix | ||
BPF_CGROUP_UNIX_SENDMSG | cgroup/sendmsg_unix | ||
BPF_CGROUP_UNIX_RECVMSG | cgroup/recvmsg_unix | ||
BPF_CGROUP_UNIX_GETPEERNAME | cgroup/getpeername_unix | ||
BPF_CGROUP_UNIX_GETSOCKNAME | cgroup/getsockname_unix | ||
BPF_PROG_TYPE_CGROUP_SOCK | BPF_CGROUP_INET4_POST_BIND | cgroup/post_bind4 | |
BPF_CGROUP_INET6_POST_BIND | cgroup/post_bind6 | ||
BPF_CGROUP_INET_SOCK_CREATE | cgroup/sock_create | ||
| (无 attach 类型) | cgroup/sock | ||
BPF_CGROUP_INET_SOCK_RELEASE | cgroup/sock_release | ||
BPF_PROG_TYPE_CGROUP_SYSCTL | BPF_CGROUP_SYSCTL | cgroup/sysctl | |
BPF_PROG_TYPE_EXT | (无 attach 类型) | freplace+ | |
BPF_PROG_TYPE_FLOW_DISSECTOR | BPF_FLOW_DISSECTOR | flow_dissector | |
BPF_PROG_TYPE_KPROBE | (无 attach 类型) | kprobe+ | |
kretprobe+ | |||
ksyscall+ | |||
kretsyscall+ | |||
uprobe+ | |||
uprobe.s+ | 是 | ||
uretprobe+ | |||
uretprobe.s+ | 是 | ||
usdt+ | |||
usdt.s+ | 是 | ||
BPF_TRACE_KPROBE_MULTI | kprobe.multi+ | ||
kretprobe.multi+ | |||
BPF_TRACE_KPROBE_SESSION | kprobe.session+ | ||
BPF_TRACE_UPROBE_MULTI | uprobe.multi+ | ||
uprobe.multi.s+ | 是 | ||
uretprobe.multi+ | |||
uretprobe.multi.s+ | 是 | ||
BPF_TRACE_UPROBE_SESSION | uprobe.session+ | ||
uprobe.session.s+ | 是 | ||
BPF_PROG_TYPE_LIRC_MODE2 | BPF_LIRC_MODE2 | lirc_mode2 | |
BPF_PROG_TYPE_LSM | BPF_LSM_CGROUP | lsm_cgroup+ | |
BPF_LSM_MAC | lsm+ | ||
lsm.s+ | 是 | ||
BPF_PROG_TYPE_LWT_IN | (无 attach 类型) | lwt_in | |
BPF_PROG_TYPE_LWT_OUT | lwt_out | ||
BPF_PROG_TYPE_LWT_SEG6LOCAL | lwt_seg6local | ||
BPF_PROG_TYPE_LWT_XMIT | lwt_xmit | ||
BPF_PROG_TYPE_NETFILTER | netfilter | ||
BPF_PROG_TYPE_PERF_EVENT | perf_event | ||
BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE | raw_tp.w+ | ||
raw_tracepoint.w+ | |||
BPF_PROG_TYPE_RAW_TRACEPOINT | raw_tp+ | ||
raw_tracepoint+ | |||
BPF_PROG_TYPE_SCHED_ACT | action(已废弃) | ||
BPF_PROG_TYPE_SCHED_CLS | classifier(已废弃) | ||
tc(已废弃) | |||
BPF_NETKIT_PRIMARY | netkit/primary | ||
BPF_NETKIT_PEER | netkit/peer | ||
BPF_TCX_INGRESS | tc/ingress | ||
BPF_TCX_EGRESS | tc/egress | ||
BPF_TCX_INGRESS | tcx/ingress | ||
BPF_TCX_EGRESS | tcx/egress | ||
BPF_PROG_TYPE_SK_LOOKUP | BPF_SK_LOOKUP | sk_lookup | |
BPF_PROG_TYPE_SK_MSG | BPF_SK_MSG_VERDICT | sk_msg | |
BPF_PROG_TYPE_SK_REUSEPORT | BPF_SK_REUSEPORT_SELECT_OR_MIGRATE | sk_reuseport/migrate | |
BPF_SK_REUSEPORT_SELECT | sk_reuseport | ||
BPF_PROG_TYPE_SK_SKB | sk_skb | ||
BPF_SK_SKB_STREAM_PARSER | sk_skb/stream_parser | ||
BPF_SK_SKB_STREAM_VERDICT | sk_skb/stream_verdict | ||
BPF_PROG_TYPE_SOCKET_FILTER | socket | ||
BPF_PROG_TYPE_SOCK_OPS | BPF_CGROUP_SOCK_OPS | sockops | |
BPF_PROG_TYPE_STRUCT_OPS | struct_ops+ | ||
struct_ops.s+ | 是 | ||
BPF_PROG_TYPE_SYSCALL | syscall | 是 | |
BPF_PROG_TYPE_TRACEPOINT | tp+ | ||
tracepoint+ | |||
BPF_PROG_TYPE_TRACING | BPF_MODIFY_RETURN | fmod_ret+ | |
fmod_ret.s+ | 是 | ||
BPF_TRACE_FENTRY | fentry+ | ||
fentry.s+ | 是 | ||
BPF_TRACE_FEXIT | fexit+ | ||
fexit.s+ | 是 | ||
BPF_TRACE_FSESSION | fsession+ | ||
fsession.s+ | 是 | ||
BPF_TRACE_ITER | iter+ | ||
iter.s+ | 是 | ||
BPF_TRACE_RAW_TP | tp_btf+ | ||
BPF_PROG_TYPE_XDP | BPF_XDP_CPUMAP | xdp.frags/cpumap | |
xdp/cpumap | |||
BPF_XDP_DEVMAP | xdp.frags/devmap | ||
xdp/devmap | |||
BPF_XDP | xdp.frags | ||
xdp |
表格要点解读
- 可休眠(Sleepable)程序:段名中带
.s后缀的(如uprobe.s、lsm.s、fentry.s、struct_ops.s、iter.s等)以及syscall类型程序,允许在程序体内调用会睡眠的辅助函数(如bpf_spin_lock、bpf_get_stack等需要睡眠上下文的 helper)。这是原文档表格中最易被忽略的一列,直接使用fentry/<fn>而写成fentry.s/<fn>即可获得可休眠能力,代价是只能在允许睡眠的上下文中执行。 - 废弃的 TC 段名:
tc、classifier、action三类传统 TC 挂载段名已被标记废弃,官方建议使用tcx/*(如tcx/ingress、tcx/egress)作为替代。SCHED_CLS类型的新程序应使用BPF_TCX_INGRESS/BPF_TCX_EGRESS或BPF_NETKIT_*attach 类型。 - XDP 的重定向变体:
xdp.frags表示该 XDP 程序支持分片(fragment)skb 的 XDP 重定向;xdp.frags/devmap与xdp.frags/cpumap分别声明程序输出目标为 devmap 或 cpumap,libbpf 据此在加载时正确设置attach_btf_id相关属性。 raw_tp与raw_tp.w:不带.w的段名映射到BPF_PROG_TYPE_RAW_TRACEPOINT(只读,不能修改原始追踪点的上下文),带.w的段名映射到BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE,可在程序内修改追踪点上下文参数。
三、典型场景:从SEC()声明到自动挂载
下面以几个常见场景说明段名如何驱动 libbpf 的自动挂载行为。
3.1 挂载 kprobe 到内核函数
SEC("kprobe/tcp_v4_connect") int BPF_KPROBE(handle_connect, struct sock *sk) { return 0; }段名kprobe/tcp_v4_connect中:kprobe精确匹配程序类型BPF_PROG_TYPE_KPROBE;extras为tcp_v4_connect,即要探测的内核函数名。libbpf 加载后调用bpf_program__attach_kprobe()系列 API 完成挂载。函数体内使用BPF_KPROBE宏(定义在 tools/lib/bpf/bpf_tracing.h)可自动解包PT_REGS_PARM*寄存器参数。
3.2 挂载 tracepoint
SEC("tracepoint/sched/sched_switch") int handle(struct trace_event_raw_sched_switch *ctx) { return 0; }extras 采用<category>/<name>格式,sched/sched_switch对应内核追踪点目录中的类别与名称(可用/sys/kernel/tracing/events/查看当前系统实际存在的追踪点)。
3.3 TCX 网络分类程序
SEC("tcx/ingress") struct __sk_buff *handle_ingress(struct __sk_buff *skb) { return skb; }段名前缀tcx/ingress会被推断为BPF_PROG_TYPE_SCHED_CLS+BPF_TCX_INGRESS,libbpf 的bpf_program__attach_tcx()会借助 netlink 将程序挂载到指定 netdev 的 tcx ingress hook 上。
3.4 加载流程与 skeleton
在 samples/bpf 中可以看到大量使用这些段名的示例(如xdp_pass、kprobe相关示例)。libbpf 提供的 skeleton 机制(由bpftool gen skeleton生成,核心逻辑在 tools/lib/bpf/skel_internal.h)会把每个段的类型与 attach 信息固化到 skeleton 头文件里,用户态只需调用bpf_object__load()即可完成“解析 → 加载 → 挂载”全流程。段名解析的关键入口在 tools/lib/bpf/elf.c 中的 section 类型推断函数族,程序类型到上下文类型名的映射表(如[BPF_PROG_TYPE_CGROUP_DEVICE] -> "bpf_cgroup_dev_ctx")位于 tools/lib/bpf/libbpf.c。
四、extras挂载格式详解(原文档脚注全集)
以下为原文档全部脚注对应的挂载格式规则,是编写SEC()段名时的权威依据:
| 程序族 | 段名格式 | 规则说明 |
|---|---|---|
fentry族 | fentry[.s]/<function> | extras 为内核函数名,.s表示可休眠 |
kprobe族 | kprobe/<function>[+<offset>] | function的合法字符为a-zA-Z0-9_.,offset必须是非负整数(如kprobe/finish_task+0) |
ksyscall族 | ksyscall/<syscall> | extras 为系统调用名 |
uprobe族 | uprobe[.s]/<path>:<function>[+<offset>] | 用户态二进制路径 + 函数名(或符号偏移) |
uprobe.multi族 | uprobe.multi[.s]/<path>:<function-pattern> | function-pattern支持*与?通配符 |
usdt族 | usdt/<path>:<provider>:<name> | 三段式:目标二进制路径、探针 provider、探针名称 |
kprobe.multi族 | kprobe.multi/<pattern> | 支持*与?通配符;合法字符为a-zA-Z0-9_.*? |
lsm | lsm[.s]/<hook> | extras 为 LSM hook 名(如lsm/file_open),.s表示可休眠 |
raw_tp | raw_tracepoint[.w]/<tracepoint> | .w变体为可写类型 |
struct_ops | struct_ops[.s]/<name> | name会被忽略,官方建议直接使用SEC("struct_ops")或SEC("struct_ops.s");实际的挂载定义写在标注为SEC(".struct_ops[.link]")的结构体初始化器中 |
tracepoint | tracepoint/<category>/<name> | 类别/名称两级格式 |
iter | iter[.s]/<struct-name> | extras 为被遍历的目标结构体名(如iter/task) |
五、与 libbpf 文档体系的衔接
本文对应的原文档是 Documentation/bpf/libbpf/program_types.rst,它属于 libbpf 用户文档系列(总入口见 Documentation/bpf/libbpf/index.rst)。建议结合以下仓库资料进一步阅读:
- Documentation/bpf/libbpf/libbpf_overview.rst:libbpf 的整体架构与 API 概览;
- Documentation/bpf/libbpf/libbpf_build.rst:如何构建与安装 libbpf;
- Documentation/bpf/libbpf/libbpf_naming_convention.rst:
SEC()段命名约定的官方说明; - tools/lib/bpf/libbpf.h:
bpf_program__attach_*系列挂载 API 的完整声明; - include/uapi/linux/bpf.h:
bpf_prog_type、bpf_attach_type枚举的权威定义; - samples/bpf:各类程序类型的可运行示例。
使用前提与限制说明
- 段名与程序类型的映射以当前仓库中 libbpf 源码(tools/lib/bpf/elf.c)支持为准;较新内核版本引入的 attach 类型(如
kprobe.multi、uprobe.session)同时要求运行环境的内核支持对应的bpf_attach_type能力,加载时可通过bpf_prog_attach的返回错误判断; tc/classifier/action段名仍可用但已废弃,新开发应优先采用tcx/*与netkit/*;- 可休眠程序(
.s段名)只能挂载在允许睡眠的上下文(如 LSM hook、uprobe 路径),挂载到不支持睡眠的 hook 会导致加载或挂载失败。
六、小结
libbpf 的“程序类型 + attach 类型 + ELF 段名”三元组是 BPF 开发的基础心智模型:SEC()段名是唯一入口,libbpf 依据前缀精确匹配或type+可扩展匹配推断程序类型,再解析extras决定自动挂载目标。本文完整给出了全部类型对照表、extras挂载格式规则(含可休眠变体)以及tcx替代传统tc段名的迁移建议,并指回了 tools/lib/bpf 源码中可追溯的实现位置,可作为 BPF 段名编写时的速查手册与深入源码阅读的起点。
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考