关于ebpf:ebpf-月报-2023-年-2-月

6次阅读

共计 4529 个字符,预计需要花费 12 分钟才能阅读完成。

本刊物旨在为中文用户提供及时、深刻、有态度的 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 这个我的项目给出了本人的答案。

笔者泛泛看了下,外加和开发者探讨,认为该我的项目次要是想要达到上面两点指标:

  1. 让控制器和 ebpf 一样可能跨平台散发
  2. 反对将打包完的 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 先退出,堆栈信息中的有些函数就只显示地址。

正文完
 0