<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>eBPF · XB 现场笔记</title><link>https://hk.xbcatm.com/zh/tags/ebpf/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://hk.xbcatm.com/zh/tags/ebpf/index.xml" rel="self" type="application/rss+xml"/><item><title>BPF tracepoint 能窥探正在运行的内核</title><link>https://hk.xbcatm.com/zh/posts/bpf-tracepoint-perf-profiling/</link><pubDate>Mon, 06 Mar 2028 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/posts/bpf-tracepoint-perf-profiling/</guid><description>内核 tracepoint 与 BPF 可以统计选定的实时事件，但仍需核对挂载权限、测量范围与开销。</description><content:encoded>&lt;p&gt;BPF 程序可以挂到正在运行的内核事件上，不必重新构建应用或重启业务负载，但仍需要用户态加载器负责加载和管理探针。采集会消耗资源，所以“实时”不等于不可见或没有成本。其他剖析工具也可以观察运行中的程序，选择 BPF 应取决于问题、已有插桩与获准的环境，而不是把所有传统工具都描述为必须暂停进程。&lt;/p&gt;
&lt;p&gt;解释输出前，要定义事件、聚合键、观察时间和未覆盖的工作。系统调用次数、调度等待与设备延迟分布回答不同问题；没有选择相应事件与关联逻辑，就不能从一条计数命令推导文件延迟、TCP 重传或整台主机的全部行为。&lt;/p&gt;
&lt;h2 id="不断流地探测"&gt;不断流地探测&lt;/h2&gt;
&lt;p&gt;eBPF 程序可以在内核 map 中累计次数或直方图，同时让工作负载继续运行。聚合减少向用户态输出的数据，但每个被观察的事件仍可能执行探针逻辑。验证器按内核规则检查程序安全；接受加载不证明测量语义正确、开销可以忽略，也不是内核绝不可能发生故障的保证。不能给没有测量过的探针套用“个位数百分比”的通用成本。&lt;/p&gt;
&lt;p&gt;从有边界的问题开始。统计系统调用进入次数需要入口事件；测量调用持续时间通常还要关联入口与出口状态。块设备延迟则需要另一套事件生命周期。bpftrace 等高级工具有助于表达这些测量，但代码短并不自动说明单位、过滤条件与关联方式正确；同一个直方图外观，也可能承载完全不同的观察对象。&lt;/p&gt;
&lt;h2 id="tracepoint-是更稳定的名字"&gt;tracepoint 是更稳定的名字&lt;/h2&gt;
&lt;p&gt;tracepoint 提供有名称的内核事件，通常比探测内部函数更少依赖实现细节，但不是稳定 ABI：名称与字段都可能随内核改变。kprobe 也可以按符号挂载，并非只能钉住硬编码地址；内部函数契约同样可能变化。长期保留的探针应注明实际验证过的版本，不能承诺补丁升级后原程序一定继续正确。&lt;/p&gt;
&lt;p&gt;在目标内核上检查实际存在的事件及字段格式。能列出事件只证明可见性，不证明有权加载和挂载程序。找不到所需事件时，应记录覆盖范围限制，不能悄悄换成语义不同的事件，再把它的次数或耗时当作原问题的答案。选择入口、出口、调度或块层事件，都必须与要测量的边界一致。&lt;/p&gt;
&lt;h2 id="读直方图而不是开闸放水"&gt;读直方图，而不是开闸放水&lt;/h2&gt;
&lt;p&gt;聚合可以展示分布，避免逐条打印每个事件。延迟直方图可能提示快慢路径，但形状只是调查证据，不是诊断本身。没有测量对应区别，就不能从双峰断言某个路径造成异常，也不能凭外观认定慢请求恰好占百分之一。次数统计更不是延迟直方图，二者的坐标、单位与结论都应明确区分。&lt;/p&gt;
&lt;p&gt;有意识地选择聚合键。按线程名称分组，可能合并同名的无关任务；改按标识或参数分组，则会改变基数和成本。尽早应用相关过滤，限定采集范围，报告时保留探针原文与负载条件。把结论与独立观察对照，再将应用问题归因于某个组件；不能因为某组计数最多，就直接把它称为耗时根因。&lt;/p&gt;
&lt;p&gt;在获准的测试环境中比较挂载前后的业务延迟、CPU 与探针资源使用，并按具体工作负载确定可接受成本。不存在适用于所有探针的统一百分比门槛。延长打印间隔可能减少输出，却未必减少每个事件执行的工作；收紧事件或过滤条件时，也要重新说明因此失去的覆盖范围。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;加载可能因权限、内核配置、事件不可用，或工具特定的 BTF、头文件要求而失败。保留实际错误，不能把所有失败都归结为环境，也不能删除检查来得到表面的成功。列出 tracepoint 不等于挂载成功；没有计数可能来自负载未发生、过滤不匹配或采集失败，不能据此宣布系统没有相关活动。&lt;/p&gt;
&lt;p&gt;容器中的能力还取决于宿主内核及安全策略，不能为强行演示而削弱保护。热路径探针即使在内核内聚合，仍可能扰动工作负载，应在代表性条件下比较成本。改变过滤条件也会改变被观察总体，因此分布变化并不自动证明成本更低的测量更准确；应区分采集成本变化与样本范围变化。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;
&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/posts-bpf-tracepoint-perf-profiling.detail.zh.svg" alt="openat 入口事件按当前线程名称合并计数，不测量成功次数或延迟。" width="560" height="1629"&gt;
&lt;figcaption&gt;入口尝试按当前线程名称合并。示意计数不是实测输出；限定范围的实验提案尚未运行。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;以下前台实验提案面向 bpftrace 0.24 与已经授权的主机，本轮没有执行。继续之前先确认版本、精确 tracepoint 与 &lt;code&gt;/bin/cat&lt;/code&gt;；保留证据目录、标准错误和退出状态，不能为强行通过而提升权限。命令创建独立临时目录，只让指定子进程读取其中的固定普通文件，不把生产业务当成测试负载。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;work&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;mktemp -d&lt;span class="k"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Evidence directory: %s\n&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$work&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$work&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;controlled input\n&amp;#39;&lt;/span&gt; &amp;gt; input &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bpftrace --version &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bpftrace -lv &lt;span class="s1"&gt;&amp;#39;tracepoint:syscalls:sys_enter_openat&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; bpftrace -o counts.log -c &lt;span class="s1"&gt;&amp;#39;/bin/cat input&amp;#39;&lt;/span&gt; -e &lt;span class="s1"&gt;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; tracepoint:syscalls:sys_enter_openat /pid == cpid/ {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; @opens[comm] = count();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; }&amp;#39;&lt;/span&gt; &amp;gt; workload.out 2&amp;gt; collector.err&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;collector_status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;collector_status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;collector_status=%s\n&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$collector_status&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cat collector.err
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$collector_status&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -ne &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$collector_status&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cmp input workload.out &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cat counts.log
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;核对过的 0.24 实现会在探针挂载后才通过 &lt;code&gt;-c&lt;/code&gt; 启动这个有限子进程；&lt;code&gt;pid == cpid&lt;/code&gt; 限定事件总体。观察随子进程生命周期结束，而不是睡眠十秒后猜测是否采集成功。子进程继承采集器权限，因此即使工作很小也需要事先授权。shell 明确保留前台采集器的退出状态，不把它冒充负载子进程的退出码；&lt;code&gt;cmp&lt;/code&gt; 另外核对复制字节，也不证明子进程退出码。&lt;/p&gt;
&lt;p&gt;预期结果是 cat 的入口计数，包含启动时打开文件和最终失败的尝试，而不是恰好一次成功打开。相同线程名称仍在选定总体内合并，不能据此推导延迟。空 map 或错误都不能解释成没有活动。挂载、结束清理与负载退出行为仍需获准运行后核验；本轮只审阅源契约，不能把静态检查升级为运行通过。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://bpftrace.org/"&gt;bpftrace 项目&lt;/a&gt;，项目概览与追踪示例，已核验 2026-10-18。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://ebpf.io/what-is-ebpf/"&gt;eBPF 介绍&lt;/a&gt;，eBPF 官方文档，已核验 2026-09-05。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://bpftrace.org/docs/release_024/language"&gt;bpftrace 0.24 language&lt;/a&gt;，tracepoint 字段与次数、直方图聚合的区别，已核验 2026-10-18。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://bpftrace.org/docs/release_024/stdlib"&gt;bpftrace 0.24 standard library&lt;/a&gt;，comm 表示当前线程名称，已核验 2026-10-18。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://docs.kernel.org/bpf/bpf_design_QA.html"&gt;Linux BPF design Q&amp;amp;A&lt;/a&gt;，tracepoint 与 kprobe 挂载不是稳定 ABI，已核验 2026-10-18。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://docs.kernel.org/bpf/verifier.html"&gt;Linux BPF verifier&lt;/a&gt;，验证器检查不等于测量正确或低开销保证，已核验 2026-10-18。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.gnu.org/s/bash/manual/bash.html"&gt;GNU Bash manual&lt;/a&gt;，不指定子进程 ID 的 wait 不返回该子进程的失败状态，已核验 2026-10-18。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://bpftrace.org/docs/release_024/cli"&gt;bpftrace 0.24 CLI&lt;/a&gt;，子进程生命周期、权限、cpid 过滤与独立追踪输出，已核验 2026-10-19。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/bpftrace/bpftrace/blob/v0.24.0/src/bpftrace.cpp"&gt;bpftrace v0.24.0 runtime source&lt;/a&gt;，BPFtrace::run 先挂载探针，再启动非 USDT 子进程，已核验 2026-10-19。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>