[转帖]BPF内部原理

bpf,内部,原理 · 浏览次数 : 0

小编点评

**BPF 内部逻辑图** BPF 的内部逻辑图可以分成五个阶段: 1. **bpftrace program → AST** 2. **AST → LLVM IR** 3. **LLVM IR → BPF bytecode** 4. **BPF bytecode → machine code** 5. ** machine code → userspace** **bpftrace program → AST** bpftrace 是一个内核动态 instrument 的工具,可以实时监控 Linux 内核的运行状态。它通过对内核函数的调用记录来构建抽象语法树 (AST),AST 是编译器的中间表示。 **AST → LLVM IR** LLVM (Low-Level Virtual Machine) 是一个编译器的中间表示语言。LLVM IR 是 AST 的低级表示。LLVM IR 可以通过一些编译器优化工具进行优化。 **LLVM IR → BPF bytecode** LLVM IR 可以通过 LLVM IR Builder 生成 BPF bytecode,这是机器码的低级表示。BPF bytecode 是 Linux 内核可执行的字节码。 **BPF bytecode → machine code** BPF bytecode 是一个机器可执行的字节码,它可以直接被 CPU 执行。 **kprobe 和 BPF** kprobe 是内核动态 instrumentation 的接口,可以对内核函数进行插桩。kprobe 可以在用户空间中动态插入代码,从而在内核代码执行之前或之后运行代码。 使用 kprobe 可以将 BPF bytecode 与内核代码集成,从而实现对内核函数的动态扩展。

正文

https://aijishu.com/a/1060000000220363

 

1. 简介

Brendan最近在USENIX LISA2021大会上做了一篇关于BPF内部原理的演讲,这篇演讲把BPF的内部逻辑剖析地非常清楚,本文大部分素材来自Brendan的这篇PPT, 额外加了一些kprobe原理的解释,分享给大家。

文中用到的术语解释

  • AST: Abstract Syntax Tree
  • LLVM: A compiler
  • IR: Intermediate Representation
  • JIT: Just-in-time compilation
  • kprobes: Kernel dynamic instrumentation
  • uprobes: User-level dynamic instrumentation
  • tracepoints: Kernel static instrumentation

下图是BPF内部逻辑图,可以看出如何从bpftrace program经过AST,LLVM IR,BPF bytecode这几个阶段,最终得到machine code由CPU来执行。接下来我们详细介绍一下每个阶段。

bpftrace_mid-level_internals.png

step.png

2. bpftrace program → AST

Screen Shot 2021-07-20 at 1.19.55 PM.png

我们以下面这个bpftrace One-Liner作为例子,介绍一下如何把bpftrace program转化成抽象语法树AST。

# bpftrace -e 'kprobe:do_nanosleep {
printf("PID %d sleeping...\n", pid);
}'

Screen Shot 2021-07-20 at 1.21.33 PM.png

Screen Shot 2021-07-20 at 1.23.34 PM.png
如上图所示,中间有个Parser模块包含了lex(词法分析)和yacc(语法分析)模块,学过编译原理的都知道,编译器把文本格式的代码翻译为中间代码一般会经过词法分析,语法分析,语义分析这几个阶段,这块内容我们在这不详细讨论,感兴趣可以阅读本文后面的参考文献。

Screen Shot 2021-07-20 at 1.24.10 PM.png
经过lex和yacc处理后,我们就得到了抽象语法树AST,可以通过bpftrace -d参数打印出AST。

3. AST → LLVM IR

Screen Shot 2021-07-20 at 1.25.45 PM.png
有了AST后,我们接下来看如何得到LLVM IR, 这其中需要经过几个模块的处理,上图中的Tracepoint & Clang struct parser模块在我们这个One-Liner例子中用不到,因为没有用到相关的结构体。

Screen Shot 2021-07-20 at 1.27.03 PM.png
上图中的语义分析器,在本例中主要作用就是一些错误处理,例如错把pid写成了pidd,语义分析器会发现错误并打出错误日志。

Screen Shot 2021-07-20 at 1.28.23 PM.png

经过Code Generation & IR Builder模块的处理,AST会被转化成LLVM IR,具体处理如下图所示。

Screen Shot 2021-07-20 at 1.29.00 PM.png

Screen Shot 2021-07-20 at 1.29.50 PM.png

Screen Shot 2021-07-20 at 1.31.13 PM.png

Screen Shot 2021-07-20 at 1.32.37 PM.png
LLVM IR也可以通过bpftrace -d打印出来。

4. LLVM IR → BPF bytecode

Screen Shot 2021-07-20 at 1.33.41 PM.png
如何把LLVM IR转成BPF bytecode, 这里就用到了LLVM编译器。下图是BPF bytecode指令的格式,

Screen Shot 2021-07-20 at 1.34.19 PM.png

BFP_CALL&JMP组合是0x85, get_current_pid_tgid对应的No是14,得到相应的BPF bytecode为85 00 00 e0 00 00 00,如下图所示。

Screen Shot 2021-07-20 at 1.35.16 PM.png

Screen Shot 2021-07-20 at 1.35.46 PM.png

Screen Shot 2021-07-20 at 1.37.57 PM.png

5. BPF bytecode → machine code

得到了BPF bytecode后,接下来我们看如何得到machine code。

Screen Shot 2021-07-20 at 1.39.04 PM.png

Screen Shot 2021-07-20 at 1.39.44 PM.png

首先,第一个经过的模块为验证器Verifier, 验证器会拒绝那些不安全的操作,包括对无界循环的检查:BPF程序必须在有限的时间内完成。如下图。

Screen Shot 2021-07-20 at 1.40.39 PM.png

经过了验证器验证,JIT编译器负责生成处理器可直接执行的机器指令。

Screen Shot 2021-07-20 at 1.49.16 PM.png

因为要生成机器指令,所以JIT在不同的体系结构下处理方式是不一样的,下图罗列了x86/arm/sparc下的处理函数。

Screen Shot 2021-07-20 at 1.49.33 PM.png

x86是在arch/x86/net/bpf_jit_comp.c的do_jit()函数中处理。
Screen Shot 2021-07-20 at 1.49.55 PM.png

arm64下是在arch/arm64/net/bpf_jit_comp.c的build_insn()函数中处理。
Screen Shot 2021-07-21 at 3.27.53 PM.png

我们可以用bpftool打印出最终的机器指令。
x86:
Screen Shot 2021-07-20 at 1.50.26 PM.png
arm64:
Screen Shot 2021-07-20 at 1.50.40 PM.png

6. kprobe

上面内容讲述了从bpftrace program到机器代码的转化过程,这时我们该考虑如何和kprobe结合了。
Screen Shot 2021-07-20 at 1.51.31 PM.png
Screen Shot 2021-07-20 at 3.25.20 PM.png
kprobe可以对任何内核函数进行插桩,可以实时在生产环境中启用,不需要重启系统,也不需要以特殊方式重启内核。
现在有以下三种接口可以访问kprobes.

  • kprobe API: 如register_kprobe()等
  • 基于Frace的,通过/sys/kernel/debug/tracing/kprobe_events:通过向这个文件写入字符串,可以配置开启和停止kprobes
  • perf_event_open(): 与perf工具所使用的一样,现在BPF跟踪工具也开始使用这些函数

通过strace可以看到这里是使用perf_event_open()接口来和kprobe打交道。

Screen Shot 2021-07-20 at 1.51.55 PM.png

kprobe工作原理

kprobe的工作过程如下(分几种情况):
一. 对于一个kprobe插桩来说:

1) 将要插桩的目标地址中的字节内容复制并保存;
2) 以单步中断指令覆盖目标地址:在ARM上是BRK指令,X86是int3;
3) 当指令流执行到断点时,断点处理函数会检查这个断点是否是由kprobes注册的,如果是,就会执行kprobes注册函数;
4) 原始的指令会接着执行,指令流继续
5) 当不在需要kprobes时,原始的字节内容会被复制回目标地址上,这样这些指令就回到了他们的初始状态。

二. 如果这个kprobe是一个Ftrace已经做过插桩的地址(一般位于函数入口处),那么可以基于Ftrace进行kprobe优化,过程如下:

1) 将一个Ftrace kprobe处理函数注册为对应函数的Ftrace处理器
2) 当在函数起始处执行内建入口函数时(x86架构上为__fentry__),该函数会调用Ftrace, Ftrace接下来会调用kprobe处理函数
3) 当kprobe不在被使用时,从Ftrace中移除Ftrace-kprobe处理函数

三. 如果是一个kretprobe:

1) 对函数入口进行kprobe插桩
2) 当函数入口被kprobe命中时,将返回地址保存并替换为一个「蹦床」(trampoline)函数:kretprobe_trampoline()
3) 当函数最终返回时(ret指令),CPU将控制交给蹦床函数处理
4) 在kretprobe处理完成之后再返回到之前保存的地址
5) 当不在需要kretprobe时,函数入口的kprobe就被移除了

Screen Shot 2021-07-20 at 1.52.38 PM.png

Screen Shot 2021-07-20 at 1.53.21 PM.png

最后我们看一下如何将结果返回给userspace, 这里用到了perf buffer空间。
Screen Shot 2021-07-20 at 3.25.48 PM.png

这块空间是per cpu的。
Screen Shot 2021-07-20 at 1.54.10 PM.png

Screen Shot 2021-07-20 at 1.54.29 PM.png
最终在userspace打印出来。这块不细说了,具体内容可以看后面参考文献。
Screen Shot 2021-07-20 at 1.54.59 PM.png

参考文献

https://www.brendangregg.com/blog/2021-06-15/bpf-internals.html
https://events.static.linuxfound.org/sites/events/files/slides/bpf_collabsummit_2015feb20.pdf
Linux include/uapi/linux/bpf_common.h
Linux include/uapi/linux/bpf.h
Linux include/uapi/linux/filter.h
https://docs.cilium.io/en/v1.9/bpf/#bpf-guide
BPF Performance Tools, Addison-Wesley 2020
https://ebpf.io/what-is-ebpf
http://www.brendangregg.com/ebpf.html
https://github.com/iovisor/bcc
https://github.com/iovisor/bpftrace
http://dinosaur.compilertools.net/

与[转帖]BPF内部原理相似的内容:

[转帖]BPF内部原理

https://aijishu.com/a/1060000000220363 LinuxKernel性能优化Arm 处理器arm64 1. 简介 Brendan最近在USENIX LISA2021大会上做了一篇关于BPF内部原理的演讲,这篇演讲把BPF的内部逻辑剖析地非常清楚,本文大部分素材来自Br

[转帖]使用bcc开发BPF程序的一点思路

https://zhuanlan.zhihu.com/p/488498453 之前的文章介绍了使用cilium工具开发BPF程序的例子。对于较新的系统内核来说,用这样较新的工具很不错,但是对于稍微旧一点的系统,如果不想直接写原生BPF程序的话,我们貌似只有一个选择,使用bcc。 一些常见的发行版的源

[转帖]Libbpf-tools —— 让 Tracing 工具身轻如燕

https://zhuanlan.zhihu.com/p/180433039 本篇文章概述了 BPF 的主要应用,重点描述了 libbpf-tools 解决了哪些 BCC 痛点以及在 PingCAP 内部的相关实践。 BPF 最初代表 Berkeley Packet Filter,但在 Linux

[转帖]Libbpf-tools —— 让 Tracing 工具身轻如燕

https://zhuanlan.zhihu.com/p/180433039 本篇文章概述了 BPF 的主要应用,重点描述了 libbpf-tools 解决了哪些 BCC 痛点以及在 PingCAP 内部的相关实践。 BPF 最初代表 Berkeley Packet Filter,但在 Linux

[转帖]Libbpf-tools —— 让 Tracing 工具身轻如燕

https://cloud.tencent.com/developer/inventory/600/article/1678208 本篇文章概述了 BPF 的主要应用,重点描述了 libbpf-tools 解决了哪些 BCC 痛点以及在 PingCAP 内部的相关实践。 BPF 最初代表 Berke

[转帖]BPF 进阶笔记(五):几种 TCP 相关的 BPF(sockops、struct_ops、header options)

http://arthurchiao.art/blog/bpf-advanced-notes-5-zh/ 整理一些 TCP 相关的 BPF 内容,主要来自 Facebook 和 Google 的分享。 关于 “BPF 进阶笔记” 系列 平时学习和使用 BPF 时所整理。由于是笔记而非教程,因此内容不

[转帖]【译文】使用BPF控制内核的ops结构体

https://zhuanlan.zhihu.com/p/105814639 Linux内核5.6版本的众多令人惊喜的功能之一是:TCP拥塞控制算法(congestion control algorithm)可作为用户空间的BPF(Berkeley Packet Filter)程序进行加载和执行。

[转帖]BPF的可移植性和CO-RE (Compile Once – Run Everywhere)

https://www.cnblogs.com/charlieroro/p/14206214.html 在上一篇文章中介绍了提高socket性能的几个socket选项,其中给出了几个源于内核源码树中的例子,如果选择使用内核树中的Makefile进行编译的话,可能会出现与本地头文件冲突的情况,如重复定

[转帖]linux 内核跟踪神器 BPF 及实战

https://cloud.tencent.com/developer/article/2031857?areaSource=103001.19&traceId=rX8kmZPurwFtXqEtY-bY- 1. 引言 作为一个程序员,在日常工作中,我们往往对于程序的运行情况十分关注,而随着计算机系统

[转帖]网络包的内核漂流记 Part 2 - BPF 跟踪 epoll/Envoy 事件与调度

https://blog.mygraphql.com/zh/notes/low-tec/network/bpf-trace-net-stack/ 为何 现代人好像都很忙,忙着跟遥远的人社交,却很容易忽视眼前的人事,更别提那些不直接体现出价值的基础认知了。要花时间认真看一编文章前,都要问一个问题:WH