本刊物旨在为中文用户提供及时、深刻、有态度的 ebpf 资讯。
如果你吃了鸡蛋感觉好吃,还想意识下蛋的母鸡,欢送关注:
笔者的 twitter:https://twitter.com/spacewand…
bpftrace 公布 0.17.0 版本
https://github.com/iovisor/bp…
时隔数月,bpftrace 公布了新版本 0.17.0
。这个版本,容许间接比拟整数数组,还新增了对以下几个架构的反对:
- 龙芯:https://github.com/iovisor/bp…
- ARM32:https://github.com/iovisor/bp…
此外,一个较大的改变是反对内核模块的 BTF 文件:
https://github.com/iovisor/bp…
bpftrace 以前就已反对了解决内核的 BTF 文件,新版本把这一性能拓展到内核模块上,算是百尺竿头更进一步。
BTF 是 eBPF 世界内的 debuginfo。通过 BTF,咱们能够在二进制和程序代码间架起桥梁。举个例子,bpftool 可能 dump 一个 BPF map 中的数据。如果没有 BTF 来正文 BPF map 存储的数据结构,dump 的后果只能是一堆二进制。有了 BTF,能力看得懂在 map 外面存储的信息。
作为一个 tracing 畛域的工具,BTF 对于 bpftrace 十分重要。如果没有 BTF,那么 bpftrace 脚本中有时须要显式定义一个内核构造体,比方 https://github.com/iovisor/bp…
为了让这段代码可能编译:
$nd = (struct nameidata *)arg0;
printf("%-8d %-6d %-16s R %s\n", elapsed / 1e6, pid, comm,
str($nd->last.name));
须要在文件结尾定义相干的构造体:
#include <linux/fs.h>
#include <linux/sched.h>
// from fs/namei.c:
struct nameidata {
struct path path;
struct qstr last;
// [...]
};
有了 BTF,就能很天然地应用内核中的构造体定义。
好在较新的内核均已提供了 BTF。如果可怜没有,能够到 btfhub 上找找。
Wasm-bpf:架起 Wasm 和 eBPF 间的桥梁
https://mp.weixin.qq.com/s/2I…
Wasm 和 eBPF 都是近年来风行的技术,两者联合在一起,会碰撞出怎么的火花?
Wasm-bpf 这个我的项目给出了本人的答案。
笔者泛泛看了下,外加和开发者探讨,认为该我的项目次要是想要达到上面两点指标:
- 让控制器和 ebpf 一样可能跨平台散发
- 反对将打包完的 Wasm 代码,作为网络 proxy 或者可观测性 agent 的插件
在笔者看来,Wasm-bpf 这个我的项目将来的倒退,更多取决于 Wasm 的生态能不能起来。毕竟在 Wasm 和 eBPF 两者中,Wasm 是绝对不足简单利用场景的那一个。比方说,如果想要在打包完的 Wasm 代码外面实现数据上报的性能,如果不依附 Wasm 宿主的能力,那么须要期待 Wasi-socket 这样正在开发中 的性能足够成熟。所以当初联合 Wasm 做 eBPF,还更多地处于技术积攒的阶段。
诚实说,即便对 Wasm 的反对可能更加成熟,也不肯定走 eBPF -> Wasm 的路线。比方说,bpf2go 可能把 eBPF 程序打包到 Go 代码中,那么用户当初可用 Go 来编写并散发 eBPF 插件,未来也能够走 eBPF -> Go -> Wasm 这条路线。(权且先疏忽 Go 不反对 Wasi 这一事实,毕竟咱们的前提是“对 Wasm 的反对可能更加成熟”,所以能够不负责任地空想一番)
Exein Pulsar 公布 0.5.0
https://github.com/Exein-io/p…
初看还认为 Apache Pulsar 跨界搞 eBPF 了,再看一眼才发现原来是新东方厨艺和新东方英语的区别。Exein 的这个 Pulsar 同样采纳了“Pulsar”(脉冲星)这个比喻来形容事件流,只不过它的事件是由部署环境上的零碎调用触发的。
像许多同样基于 eBPF 的可观测性的软件一样,Pulsar 也抉择了“控制器 + eBPF 模块”的架构。跟许多同类软件不同的是,Pulsar 采纳 Rust 来作为控制器开发语言,加载 eBPF 的库用的是 Aya。他们之所以这么选型,兴许是因为 Exein 的人偏好 Rust,且他们的指标环境是 IoT。
Pulsar 采纳一个宏来包裹 eBPF 的挂载点:
PULSAR_LSM_HOOK(path_mknod, struct path *, dir, struct dentry *, dentry,
umode_t, mode, unsigned int, dev);
static __always_inline void on_path_mknod(void *ctx, struct path *dir,
struct dentry *dentry, umode_t mode,
...
这个宏定义如下:
#define PULSAR_LSM_HOOK(hook_point, args...) \
static __always_inline void on_##hook_point(void *ctx, TYPED_ARGS(args)); \
\
SEC("lsm/" #hook_point) \
int BPF_PROG(hook_point, TYPED_ARGS(args), int ret) { \
on_##hook_point(ctx, UNTYPED_ARGS(args)); \
return ret; \
} \
\
SEC("kprobe/security_" #hook_point) \
int BPF_KPROBE(security_##hook_point, TYPED_ARGS(args)) { \
on_##hook_point(ctx, UNTYPED_ARGS(args)); \
return 0; \
}
能够看到,它会给每个函数设置两个挂载点,一个是传统的 BPF_PROG_TYPE_KPROBE,另一个是 Linux 5.7+ 引入的 BPF_PROG_TYPE_LSM 类型。
LSM(Linux 平安模块)其实是一套在内核相干函数减少的 hook 框架,开发者能够通过这些 hook 来退出细粒度的安全策略。赫赫有名的 selinux 和 apparmor 就都属于一种 LSM 的实现。BPF_PROG_TYPE_LSM 类型旨在容许开发者通过 eBPF 来编写策略代码,挂载到对应的 LSM hook 上。察看上述宏定义,咱们能够看到 lsm 挂载点上的函数容许 eBPF 代码里返回一个 ret
值。在 BPF_PROG_TYPE_LSM 类型的 eBPF 中,开发者可能在调用被 hook 的函数之前,返回一个错误码,比方:
SEC("lsm/xxxxx")
int BPF_PROG(xxx, int ret)
{
// 前一个 hook 返回了非 0 值,示意该调用曾经被回绝。让咱们把错误码持续传递下来
if (ret) {return ret;}
// 做些安全策略
if (!ok) {return -EPERM;}
return 0;
}
当然咱们能够看到上述宏定义里其实并没有设置 ret 的值。Pulsar 只是对要害调用做了事件上报,没有做策略判断。这也是为什么它可能在低版本的 Linux 上 fallback 到一般的 BPF_PROG_TYPE_KPROBE。
后面咱们提到,LSM 其实是一套在内核中减少的 hook。这一类的 hook 的命名有一套规定,都以 security_
打头。所以某个 BPF_PROG_TYPE_LSM 的加载点 xxx,也正好对应内核函数 security_xxx
。
应用 eBPF 减速 delve trace
https://developers.redhat.com…
delve 是一个 Go 调试器。相似于 strace,delve 有一个 trace Go 函数调用的性能,也同样是基于 ptrace
零碎调用实现的。
本文阐明了他们是如何通过 eBPF 让 trace 的速度比起之前有了天壤之别。原理很简略:用 eBPF 的 uprobe 换掉了 ptrace 零碎调用。没有了频繁的零碎调用,性能天然下来了。
在这篇文章中,作者提到 eBPF 后端是实验性的。的确如此,我尝试应用 eBPF 后端的体验并不如本来的 ptrace 实现。比方 ptrace 下,反对用如下形式打印波及函数的调用栈:
$ ./go/bin/dlv trace -s 3 '.*Printf.*' --exec ./go/bin/dlv
...
> goroutine(1): fmt.(*pp).doPrintf((*fmt.pp)(0xc0000a6a90), "%%-%ds", []interface {} len: 824635347800, cap: 824635347800, [...])
Stack:
0 0x00000000004f91af in fmt.(*pp).doPrintf
at /usr/local/go/src/fmt/print.go:1021
1 0x00000000004f3719 in fmt.Sprintf
at /usr/local/go/src/fmt/print.go:239
2 0x0000000000962e3f in github.com/spf13/cobra.rpad
at ./go/pkg/mod/github.com/spf13/cobra@v1.1.3/cobra.go:153
3 0x00000000004675a9 in runtime.call32
at :0
(truncated)
Stack:
而 eBPF 后端目前并不反对打印调用栈。如果没有调用栈信息,其实很难晓得某个函数是否在失当的机会被调用。况且在非生产环境上,ptrace 的实现曾经足够快了。所以 eBPF 后端目前的性能就挺鸡肋,只适宜于在生产环境上理解某个函数是否被调用,而且对环境的要求比拟高,又不如 strace 那么通用。
如果只是想晓得函数有没有被调用到,用 bpftrace 也能达到同样的成果:
$ bpftrace -e 'uprobe:./go/bin/dlv:"fmt.(*pp).doPrintf"{printf("%s\n", ustack(3));}' -c './go/bin/dlv exec ./go/bin/dlv'
...
fmt.(*pp).doPrintf+0
github.com/go-delve/delve/pkg/terminal.New+2103
github.com/go-delve/delve/cmd/dlv/cmds.connect+528
用上面的通配符模式,会更靠近后面 dlv trace
的成果:
bpftrace -e 'uprobe:./go/bin/dlv:*Printf* {printf("%s\n", ustack(3));}' -c './go/bin/dlv exec ./go/bin/dlv'
仔细的读者可能留神到了,我这里执行的命令换成了 ./go/bin/dlv exec ./go/bin/dlv
。这是因为 bpftrace 有个 bug,如果 traced 的过程比 bpftrace 先退出,堆栈信息中的有些函数就只显示地址。