08月10, 2023

bpftrace 实战(2)-向进程注入钩子

上一实战案例中,展示了 bpftrace 最常规的用法:观察。利用 bpftrace 可以我们可以从内核层面,观察整个服务的任何函数调用状态。借助收集到的信息,辅助定位 bug。

最近几个月我都投入在一个分布式系统的开发,在做在线故障恢复时,需要根据一些触发条件模拟故障场景调试。这些场景都不太可能通过人工直接模拟,一般采用这些方法:

直接修改代码,在复合条件时 abort

  • 动态库注入;
  • 利用 LD_PRELOAD 挟持函数,改变其默认行为;
  • 利用一些测试框架。 但我们有 bpftrace,就有了新选择,无需编译,随编随用,不要太方便。

system 函数

bpftrace 提供了类似 C 中的 system 函数,用于执行 shell 命令。

前面我们提到,bpftrace 脚本会被编译为 ByteCode,交由内核中的 bpf VM 执行。内核是整个系统的中枢,容不得一点闪失,放任所有资源被用户脚本控制实在太危险了。如果希望通过 bpftrace 直接修改进程行为,显然是不为系统安全策略允许的(至少目前如此)。因此 bpftrace 需要在便利和安全之间权衡,结果就是绝大多数敏感资源都被 bpf VM 隔离,用户只能获得经过审查的,限制的能力。

虽然我们无法直接通过 bpftrace 影响进程的运行状态,但是 system 函数提供了通往 shell 的道路,我们可以借助 shell 实现目的。当然 bpftrace 出于安全考虑,system 函数的调用需要 root 权限,使用时还需为 bpftrace 传入 --unsafe 参数,表明我们要做一些 “危险” 的事。

回到我要处理的问题:在满足条件时,模拟进程故障,也就是杀死某些进程。下面是虚构的例子:

// killme.bpf
uprobe:/my/lib/path/mylib.so:killme {
    system("kill -9 %d", pid)
}

当 mylib.so 中的 killme 函数被调用时,我们就向这个进程发送 kill 信号杀掉它。

然后让我们启动 bpftrace ,静静等待事件被触发:

bpftrace --unsafe killme.bpf

很简单对吧,试想一下你甚至无需拥有这个库的源码,不需要繁杂的编译过程就可以轻松实现目的,真方便!

总结

system 函数为我们开了一个窗口,借助 shell 我们几乎可以轻松的实现任何目的。在我的实际应用中,甚至实现了多台机器之间通过 bpftrace 和网络彼此触发,构成复杂的消息网络(虽然有点过度使用,但很有意思)。

本文链接:http://www.thinkinpython.com/post/bpftrace_example_hook_app.html

-- EOF --