BPF binfmt-misc 处理器以透明模式执行 exec 时,为什么进程的 exe 链接、comm 和命令行仍然描述的是被执行的二进制,而不是实际运行的解释器?
因为内核刻意把"身份"固定到二进制上。BPF binfmt-misc 处理器通过 struct linux_binprm 上的 BPF 选择字段来挑选解释器;当这个选择带有透明标志时,exec 路径把实际运行的代码换成了解释器,却把进程身份——exe 链接、线程 comm、参数向量——继续指向二进制。解释器的选择被暂存在 bprm->bpf_interp、bprm->bpf_interp_file 和 bprm->bpf_flags 里,转换成 MISC_FMT_* 位之后就在 exec 路径内部被清零。exec 完成后,没有任何用户可见的接口,也没有任何 tracepoint 记录是哪个 BPF 处理器做的选择、用了哪些标志。
这正是观察盲区之所以是结构性的、而不是某处疏漏的全部原因:这个选择是一个加载期、内核内部的事实,而它被刻意不留在进程上,所以标准的 exec 观测工具天生看不见 BPF 这一侧。
机制
-
BPF 处理器是一个
struct binfmt_misc_opsstruct_ops map,由一个'B'binfmt-misc 条目激活。它的 load 程序是唯一能调用选择 kfunc 的地方——验证器把bpf_binprm_set_interp、bpf_binprm_select_interp、bpf_binprm_set_interp_arg和bpf_binprm_set_flags限制在这个 map 的 load 程序里。选中的解释器暂存在bprm->bpf_interp/bprm->bpf_interp_file,标志放在bprm->bpf_flags。 -
load_misc_binary调用entry_invocation_flags,把 BPF 标志翻译成MISC_FMT_*调用位——透明标志对应MISC_FMT_TRANSPARENT加MISC_FMT_OPEN_BINARY——然后把bprm->bpf_flags清零,避免标志累加进嵌套的解释器层级。在透明路径上它跳过build_interp_argv,调用bprm_change_interp把bprm->interp设成解释器字符串,并把bprm->interpreter设成解释器文件、置上BINPRM_FLAGS_TRANSPARENT_INTERP。 -
exec_binprm循环接下来以bprm->file = bprm->interpreter跑下一轮——解释器成为下一轮的文件。当bprm->have_execfd被置位时,原始二进制被暂存为bprm->executable,用于交给解释器的AT_EXECFD,循环把解释器深度上限设成 5,超过就返回-ELOOP。 -
只要置了
BINPRM_FLAGS_TRANSPARENT_INTERP,bprm_identity_file就返回bprm->executable——也就是二进制——而set_mm_exe_file和 comm 的推导都取自它。于是/proc/PID/exe和comm呈现的是二进制,argv原样保留,实际运行的代码是通过AT_EXECFD到达的解释器。按内核自己的注释,透明模式正是那条"为下一轮更新 interp"的路径,让这次 exec"看起来像一次直接执行"。
验证与排障路径
-
直接读 exec 身份:对比
/proc/PID/exe加comm,与/proc/PID/maps和AT_EXECFDauxv 项。透明模式下,exe 链接、cmdline、comm全都指向二进制,而实际运行的代码是解释器。 -
对比同一次 exec 的两个 sched 事件:
sched_prepare_exec带有interp路径,但依旧没有 handler 名或标志;而sched_process_exec——多数追踪器以它为准——只有filename加pid加old_pid。两者对同一次 exec 报告的内容不同,而 BPF handler 名和选择标志在两者里都缺失。 -
用 BPF 处理器复现:注册
'B'handler,建一个D状态条目,绑定一个解释器,再走一次 exec。二进制的exe链接保留,argv 保留,实际运行的代码是解释器。 -
解读观察到的标志值:一次记录为
0x48000000的选择就是MISC_FMT_TRANSPARENT或MISC_FMT_OPEN_BINARY——entry_invocation_flags里的翻译会附加透明标志自带的那个OPEN_BINARY含义。
局限
-
这个盲区不是彻底的:
sched_prepare_exec确实会报告解释器路径。没有任何现成事件或接口暴露的,是 BPF handler 名和选择标志。 -
这个身份行为是刻意的,不是 bug。透明模式的存在就是为了让这次 exec 以二进制的身份呈现,所以 exe 链接和 comm 跟着二进制走,
argv被保留。 -
RFC 提议的那个默认关闭的
binfmt_misc_selecttracepoint 是观察,不是强制。它事后报告选择,而 tracefs 访问仅限 root,root 可以把它关掉。 -
一个 BPF binfmt-misc 处理器注册在它自己的用户命名空间里,并不从祖先用户命名空间继承,所以哪个处理器生效取决于这次 exec 所在的那个命名空间。
-
Loader 模式是另一套机制:它用 loader 替代二进制的
PT_INTERP,原生运行二进制,并排除其它所有标志——它不是透明路径。
References
- 公开的 bpf 邮件列表 RFC:一个默认关闭的
binfmt_misc_selecttracepoint - 一封公开的 bpf 列表 RFC,提议在 BPF 处理器选好解释器之后触发一个默认关闭的 tracepoint,报告 filename、解释器、handler、屏蔽后的标志与 pid,明确定位为观察而非强制。 - 内核
fs/binfmt_misc_bpf.c- 暂存解释器与标志的 BPF struct_ops handler kfuncs。 - 内核
fs/binfmt_misc.c-load_misc_binary、entry_invocation_flags与MISC_FMT_*标志位。 - 内核
fs/exec.c-bprm_identity_file、bprm_change_interp与exec_binprm循环。 - 内核
include/trace/events/sched.h-sched_process_exec与sched_prepare_exec的字段列表。
当日社区讨论
一封公开的 bpf 邮件列表 RFC 提议一个默认关闭的 binfmt_misc_select tracepoint,在 BPF 处理器选好解释器之后触发,报告 filename、解释器、handler、屏蔽后的标志与 pid。它被明确定位为对 BPF 这一侧选择的观察——而这一事实,按现状,没有任何现有 tracepoint 或用户可见接口会暴露。该 RFC 在列表上公开、处于讨论中。
本次运行的存档来源在 7 天窗口内产出 0 条消息(两个 opt-in 存档来源在快照时为空),因此上面的问题取自被允许的公开 bpf 邮件列表,经其公开的 atom feed 审阅,RFC 线程作为一手参考。eunomia-bpf 与 sched-ext 的 Discord 频道以及 r/eBPF 在本次运行未覆盖(没有可用的可见浏览器会话),故标记为未覆盖而非安静。
继续阅读
返回索引
eBPF 问答
这里每天收录一个关于 eBPF、Linux 可观测性、性能分析、运行时扩展或安全问题的回答。每篇回答都以公开的一手资料为依据,并在末尾附上匿名化的当日社区讨论摘要。
上一篇 / 上一页
本地嵌入模型没有服务器也没有 token 计数,为什么它的 GenAI span 属性要声明期望缺失项,这些缺失项如何保持符合规范?
GenAI 语义规范把 span 的每个属性都按其取值的可得性来定级,而不是按 span 是否存在来定级。必需属性很少(操作名、提供方名),其余要么是条件必需(有值时),要么是建议级(尽力而为)。本地模型不暴露任何服务器,也不报告计费 token 用量,所以符合规范的做法是省略这些属性,并在插桩的 conformance scenario(一致性场景)中把这次省略声明为期望缺失(expected
下一篇 / 下一页
为什么不可睡眠的 BPF fentry 与 fexit 程序会悄悄跳过同一 CPU 上其他任务的调用,被跳过的调用又为什么会记成递归丢失?
因为 trampoline 的递归保护是 per-CPU 的,而且是在 preemption 仍然开启的状态下获取的。不可睡眠的 fentry/fexit 程序经由 BPF trampoline 执行,其入口路径会递增 prog-active,一个 per-CPU 计数器,而它是在 rcureadlockdontmigrate() 下获取的。这个锁只禁用了 CPU 迁移,并没有禁用
- 最后更新
- 2026年10月24日
- 首次发布
- 2026年10月24日
- 贡献者
- Yunwei 123
这个页面有帮助吗?