如何获取Linux ebpf汇编?

12

我想学习Linux ebpf虚拟机,如果我编写一个ebpf程序test.c,并使用llvm:
clang -O2 -target bpf -o test.o test.c。如何获得类似于经典bpf中tcpdump -d的ebpf汇编代码呢?谢谢。


展示一下tcpdump的输出是什么样子的小例子。我猜它可能类似于objdump -d - Shift_Left

tcpdump -i any -d ip

(000) ldh [14] (001) jeq #0x800 jt 2 jf 3 (002) ret #65535 (003) ret #0
- Junli Ou
@JunliOu tcpdump -d 命令会以旧的 BPF (也称为 _cBPF_)格式打印指令。提示:使用 tcpdump -ddd 命令,每个显示的指令都有 4 个字段(eBPF 有 5 个字段)。 - Qeole
1个回答

17

这取决于您对“学习Linux ebpf vm”确切的理解。

语言本身

如果您是指学习eBPF指令,即类似汇编的语言本身,则可以查看内核文档(相当密集)或bcc项目中的此语法摘要

虚拟机

如果您想了解eBPF虚拟机的内部工作原理,可以查看各种演示文稿(我推荐D. Borkmann的演示文稿),我在这篇博客文章中有一个列表或者您可以直接阅读内核源代码,在linux/kernel/bpf下(特别是文件core.c)。另外,还提供了一个更简单的用户空间实现

转储eBPF指令

现在,如果您想查看从C编译到eBPF的代码,这里有几个解决方案。

读取目标文件

对于我来说,我使用tc-bpf手册中提供的命令进行编译:

__bcc() {
        clang -O2 -emit-llvm -c $1 -o - | \
        llc -march=bpf -filetype=obj -o "`basename $1 .c`.o"
}
alias bcc=__bcc

代码被翻译成eBPF并存储在所生成的ELF文件的一个部分中。然后,我可以使用诸如objdumpreadelf等工具检查我的程序。例如,如果我的程序在classifier部分中:
$ bcc return_zero.c
$ readelf -x classifier return_zero.o

Hex dump of section 'classifier':
   0x00000000 b7000000 02000000 95000000 00000000 ................

在上面的输出中,显示了两个指令(小端——以0x开头的第一个字段是节内偏移量)。我们可以解析它们以整理指令并得到:
b7 0 0 0000 00000002 // Load 0x02 in register r0
95 0 0 0000 00000000 // Exit and return value in r0

[2019年4月编辑] 转储内核中加载的eBPF程序

可以将内核中加载的程序指令转储出来(然后可能将其附加到可用的BPF挂钩之一),以eBPF汇编指令或者如果该程序已经 JIT 编译,则以机器指令的形式。 bpftool 是进行这种操作的首选工具,它依赖于 libbpf。例如,可以使用以下命令查看当前加载的程序,并注意它们的标识:

# bpftool prog show

然后,倾倒给定ID程序的指令就像这样简单:

# bpftool prog dump xlated id <id>
# bpftool prog dump jited  id <id>

对于eBPF或JIT(如果可用)指令,可以分别输出。如果需要,输出也可以格式化为JSON。

高级工具

根据您用于将BPF注入内核的工具,您通常可以转储内核验证器的输出,其中包含大多数以人类友好方式格式化的指令。

使用bcc工具集(与先前的命令没有直接关联,也与旧的16位编译器没有任何关系),您可以通过使用相关标志来获取BPF对象实例的此内容,而使用tc filter add dev eth0 bpf obj … verbose则可以使用verbose关键字完成此操作。

反汇编器

上述用户空间实现(uBPF)具有自己的汇编器和反汇编器,可能会对您感兴趣:它将“人性化”(add32 r0,r1等)指令作为输入并转换为目标文件,或者相反。
但更有趣的是,LLVM本身支持带有BPF反汇编器的调试信息:截至今天,它最近已合并,并且其作者(A. Starovoitov)已在netdev邮件列表中发送了一封电子邮件。这意味着使用clang / LLVM 4.0+,您应该能够使用llvm-objdump -S -no-show-raw-insn my_file.o来获得漂亮格式的输出。

bcc也是16位x86编译器的名称。我猜字母表里只有那么多字母,但一开始浏览时还是很惊讶的。另外顺便说一下,你可以直接调用shell函数bcc。为什么要使用别名呢? - Peter Cordes
还要记得引用变量扩展:clang -O2 -Wall -emit-llvm -c“$ @” -o - | llc -march = bpf -filetype = obj -o“ $(basename“ $ 1” .c). o”。请注意,$()允许轻松嵌套双引号,与反引号相比。我在clang命令行中使用了“$ @”,因此您可以传递foo.c -O3 -Wextra -mtune = native或其他内容。嗯,我想一些优化/调整选项需要在llc命令行上使用?所以也许是llc“ $ { @:2}”来获取除$1之外的选项。 - Peter Cordes
@PeterCordes 谢谢。在这里,我直接从手册中复制了代码。这只是一个例子,我猜bcc项目有一个经过精细调整和优化的调用llvm库的方法。我认为他们将其命名为别名的原因是确切的命令可能会发生变化(例如,llvm-objdump -S在编译时需要-g标志)。无论如何,重点在于字节码转储而不是编译步骤。话虽如此,你的命令仍然有效,再次感谢。 - Qeole
啊,我猜我应该在man手册上提交一个bug报告,并附上修复引用的方法。然而,这种推理并不能解释alias bcc=__bcc。你可以重新定义已经定义的shell函数,所以改变函数内容不需要额外的间接层。(另外,我认为你没有理解关于其他bcc的要点。它是一个旧项目,主要用于制作引导程序。16位x86机器代码比32位更过时,对于Linux内核来说是无用的,因为它运行在32或64位模式下!) - Peter Cordes
是的,显然你不可能在谈论布鲁斯的C编译器(因为它只能生成16位机器码),但那是我听说过的唯一一个bcc。 - Peter Cordes
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接