上手 bpftool

bpftool 是什么

bpftool 是一个用来检查 BPF 程序和映射的内核工具。

如何编译安装 bpftool

bpftool 源码在 tools/bpf/bpftool 中,直接 cd 到这个路径中,然后执行 make && make install 即可。

这里我使用了 V=1 来输出更详细的信息,过程记录如下:

root@debian-10:12:12:04] bpftool # make && make V=1 install
make[1]: 进入目录“/home/longyu/linux-git/tools/lib/bpf”
make[1]: 离开目录“/home/longyu/linux-git/tools/lib/bpf”
make -C /home/longyu/linux-git/tools/lib/bpf/ OUTPUT= libbpf.a
make[1]: 进入目录“/home/longyu/linux-git/tools/lib/bpf”
make -f /home/longyu/linux-git/tools/build/Makefile.build dir=. obj=libbpf
make[1]: 离开目录“/home/longyu/linux-git/tools/lib/bpf”
install -m 0755 -d /usr/local/sbin
install bpftool /usr/local/sbin/bpftool
install -m 0755 -d /usr/share/bash-completion/completions
install -m 0644 bash-completion/bpftool /usr/share/bash-completion/completions

执行 bpftool version 命令查看版本信息:

[root@debian-10:12:13:46] bpftool # bpftool version
bpftool v5.0.0

v5.0.0 版本与我使用的内核版本对应!

上手 bpftool

bpftool 帮助信息

单独执行 bpftool 命令会输出如下帮助信息:

Usage: bpftool [OPTIONS] OBJECT { COMMAND | help }
       bpftool batch file FILE
       bpftool version

       OBJECT := { prog | map | cgroup | perf | net }
       OPTIONS := { {-j|--json} [{-p|--pretty}] | {-f|--bpffs} |
	            {-m|--mapcompat} | {-n|--nomount} }

与 《Linux内核观测技术BPF》一书中不同的是,我这个版本并不支持 feature 选项。

bpftool prog

bpftool prog 可以用来检查系统中运行程序的情况,在我的系统中执行相关命令得到了如下信息:

[root@debian-10:12:14:52] bpftool # bpftool prog show
3: cgroup_skb  tag 7be49e3934a125ba  gpl
	loaded_at 2020-11-21T19:16:17+0800  uid 0
	xlated 296B  not jited  memlock 4096B  map_ids 2,3
4: cgroup_skb  tag 2a142ef67aaad174  gpl
	loaded_at 2020-11-21T19:16:17+0800  uid 0
	xlated 296B  not jited  memlock 4096B  map_ids 2,3
5: cgroup_skb  tag 7be49e3934a125ba  gpl
	loaded_at 2020-11-21T19:16:17+0800  uid 0
	xlated 296B  not jited  memlock 4096B  map_ids 4,5
6: cgroup_skb  tag 2a142ef67aaad174  gpl
	loaded_at 2020-11-21T19:16:17+0800  uid 0
	xlated 296B  not jited  memlock 4096B  map_ids 4,5
7: cgroup_skb  tag 7be49e3934a125ba  gpl
	loaded_at 2020-11-21T19:16:17+0800  uid 0
	xlated 296B  not jited  memlock 4096B  map_ids 6,7
8: cgroup_skb  tag 2a142ef67aaad174  gpl
	loaded_at 2020-11-21T19:16:17+0800  uid 0
	xlated 296B  not jited  memlock 4096B  map_ids 6,7

上面的输出中最左侧的数字表示程序标识符,可以用来获取更详细的信息。

可以使用 --json 来生成 json 格式的输出,同时指定 id 来过滤掉不需要的项目。

执行示例如下:

[root@debian-10:12:21:01] bpftool # bpftool prog show --json id 3 | jq
{
  "id": 3,
  "type": "cgroup_skb",
  "tag": "7be49e3934a125ba",
  "gpl_compatible": true,
  "loaded_at": 1605957377,
  "uid": 0,
  "bytes_xlated": 296,
  "jited": false,
  "bytes_memlock": 4096,
  "map_ids": [
    2,
    3
  ]
}
[root@debian-10:12:21:05] bpftool # bpftool prog show --json id 4 | jq
{
  "id": 4,
  "type": "cgroup_skb",
  "tag": "2a142ef67aaad174",
  "gpl_compatible": true,
  "loaded_at": 1605957377,
  "uid": 0,
  "bytes_xlated": 296,
  "jited": false,
  "bytes_memlock": 4096,
  "map_ids": [
    2,
    3
  ]
}

这个 jq 程序我的系统中并没有预装,可以执行下面这条命令来安装:

apt-get install jq

bpf prog dump

获取到了程序标识符后,可以执行 bpf prog dump 来获取整个程序的数据,能够看到由编译器生成的字节码。

我首先运行如下程序:

from bcc import BPF

bpf_source = """
int trace_go_main(struct pt_regs *ctx) {
  u64 pid = bpf_get_current_pid_tgid();
  bpf_trace_printk("New hello-bpf process running with PID: %d\\n", pid);
  return 0;
}
"""

bpf = BPF(text = bpf_source)
bpf.attach_uprobe(name = "/home/longyu/linux-observability-with-bpf/code/chapter-4/uprobes/hello-bpf", sym = "main.main", fn_name = "trace_go_main")
bpf.trace_print()

再次执行 bpftool prog show 发现增加了如下信息:

39: kprobe  name trace_go_main  tag 76ea4b5c961566b3  gpl
	loaded_at 2020-11-22T12:30:12+0800  uid 0
	xlated 200B  not jited  memlock 4096B

然后我使用 bpftool prog dump xlated 来 dump id 为 39 的程序的内容,相关信息如下:

[root@debian-10:12:30:23] bpftool # bpftool prog dump xlated id 39
   0: (85) call bpf_get_current_pid_tgid#84176
   1: (b7) r1 = 680997
   2: (63) *(u32 *)(r10 -8) = r1
   3: (18) r1 = 0x203a444950206874
   5: (7b) *(u64 *)(r10 -16) = r1
   6: (18) r1 = 0x697720676e696e6e
   8: (7b) *(u64 *)(r10 -24) = r1
   9: (18) r1 = 0x757220737365636f
  11: (7b) *(u64 *)(r10 -32) = r1
  12: (18) r1 = 0x7270206670622d6f
  14: (7b) *(u64 *)(r10 -40) = r1
  15: (18) r1 = 0x6c6c65682077654e
  17: (7b) *(u64 *)(r10 -48) = r1
  18: (bf) r1 = r10
  19: (07) r1 += -48
  20: (b7) r2 = 44
  21: (bf) r3 = r0
  22: (85) call bpf_trace_printk#-46368
  23: (b7) r0 = 0
  24: (95) exit

第一行 call 指令调用 bpf_get_current_pid_tgid 函数,这个函数是内核函数,它返回 pid 这个值。

从第 1 行指令到第 21 行指令都是在给 bpf_trace_printk 制作参数,注意第 21 行的 r3 = r0 操作,这个 r0 其实就是 bpf_get_current_pid_tgit 获取到的 pid 值。

为啥说 r0 就是 pid 的值呢?

可以看到第 23 行将 r0 赋值为 0,然后第 24 行调用 exit 退出,其实对应的就是 trace_go_main 函数中的 return 0 操作。

这样看来它传递参数应该有这样的规则:

r0 作为返回值
r1-rN 作为函数的参数

bpf_trace_printk 函数看来并不像表面那样使用了两个参数,其实它的参数有三个。

第一个是 r1,指向 “New hello-bpf process running with PID: %d\n” 字符串的存储位置,第二个是 r2,它被赋值为 44,这个 44 并不存在于代码中,它其实是 r1 指向的字符串长度,r3 被赋值为 r0,它其实就是 pid 的值。

从上面这些 bpf 指令中没有看到对栈的使用,但是调用 bpf_get_current_pid_tgid 这个函数应当是要使用栈的,我觉得这里没有用到到使用栈的指令能够说明 bpf_get_current_pid_tgid 函数并不会被编译成 bpf 指令

bpf_get_current_pid_tgid 的实现在内核源码树 kernel/bpf/helpers.c 中,相关内容如下:

BPF_CALL_0(bpf_get_current_pid_tgid)
{
        struct task_struct *task = current;

        if (unlikely(!task))
                return -EINVAL;

        return (u64) task->tgid << 32 | task->pid;
}

反汇编 helpers.o 得到了如下信息:

00000000000000c0 <bpf_get_current_pid_tgid>:
  c0:   e8 00 00 00 00          callq  c5 <bpf_get_current_pid_tgid+0x5>
  c5:   65 48 8b 14 25 00 00    mov    %gs:0x0,%rdx
  cc:   00 00 
  ce:   48 c7 c0 ea ff ff ff    mov    $0xffffffffffffffea,%rax
  d5:   48 85 d2                test   %rdx,%rdx
  d8:   74 15                   je     ef <bpf_get_current_pid_tgid+0x2f>
  da:   48 63 82 cc 04 00 00    movslq 0x4cc(%rdx),%rax
  e1:   48 63 92 c8 04 00 00    movslq 0x4c8(%rdx),%rdx
  e8:   48 c1 e0 20             shl    $0x20,%rax
  ec:   48 09 d0                or     %rdx,%rax
  ef:   c3                      retq   

这个反汇编的结果能够证明此函数是非 bpf 指令,但是这个函数的反汇编信息看上去很奇怪,与标准函数的汇编代码有所区别。

写到这里我想到了一个问题,既然这个 bpf_get_current_pid_tgid 函数是 x86 的机器码,按照物理机器的执行过程,这个函数执行完了就要返回到调用点的下一行代码,也就是下面这行 bpf 指令:

   1: (b7) r1 = 680997

想想从一个 x86 的机器码返回到 bpf 的机器码,这完全不科学。其实 bpf 的机器码也是用 x86 的机器码模拟的,这样这个问题就不存在了。

___bpf_prog_run 中对 bpf 的 call 指令进行下面的处理:

	/* CALL */
	JMP_CALL:
		/* Function call scratches BPF_R1-BPF_R5 registers,
		 * preserves BPF_R6-BPF_R9, and stores return value
		 * into BPF_R0.
		 */
		BPF_R0 = (__bpf_call_base + insn->imm)(BPF_R1, BPF_R2, BPF_R3,
						       BPF_R4, BPF_R5);
		CONT;

JMP_CALL 对应 bpf 的机器码,BPF_R0 存储了这个调用的返回值,它与我上文提到的 r0 寄存器是一致的,其实不过是一个局部变量数组中的某个项目,其它的寄存器也有类似的特征。

bpf 虚拟机实际是提供了一套解析执行 bpf 指令码的环境,这个环境由软件模拟,它最终也必须通过硬件指令来执行

写到这里,我想到了《The AWK Programming Language》中的第 6 章,内容是讲用 awk 来实现一些简单的语言,第一个例子就是汇编器与解析器,这个例子其实与这里的 bpf 虚拟机的原理有某种相似之处,它也是用软件模拟了指令的执行过程。

也许有人会这样想,既然这种虚拟机器最终都要通过实际的物理机器指令来执行,那么为什么还要转化成另外一种中间的机器码呢?

对一个硬件来说,它的指令集一般是固定的,这样我们不能在同一个硬件环境中执行其它架构的指令,要执行 x86 架构的指令需要使用 x86 架构的机器,要执行 arm 架构的指令需要使用 arm 架构的机器,如果我要同时执行 x86 架构与 arm 架构的程序,我们需要有这两种架构的机器。

而使用虚拟机将会让我们打破硬件的这种限定,不同的虚拟机通过软件来解析不同架构的指令,然后转化为目标硬件的物理架构指令执行,这样我们就能在同一个物理硬件架构上运行多个结构的程序了,这无疑极大的扩展了硬件的边界!

对机器的思考

物理机器一般会使用栈来存储临时变量并维持函数调用的层次,这其实是由函数执行的特点决定的,对于汇编语言来说其实根本不存在函数调用这种说法,函数算是高级语言最具代表性的抽象。

每一个函数其实对应都是一组机器码,它们的执行依赖的机器是共享的,这样就带来了一个问题,在函数嵌套调用过程中如何保存这些共享的内容,栈其实就是一个很好的解决方案。

栈的增减通过简单的拨动栈顶指针完成,基本上也就是一行机器指令,尽管如此简单,但是栈却是整个程序运行的基础元素。

操作系统中任务的切换其实跟函数嵌套调用有类似的特征,不过它保存的是一个完整的寄存器状态,那上下文切换又在干嘛呢?其实说白了也就是在切换栈帧而已!

好了,再次回到 bpftool 中!

将 bpf 程序持久化到 bpf 文件系统

bpftool 可以将 bpf 程序持久化保存到 bpf 文件系统,这样当程序终止后它也会存在。

示例命令如下:

bpftool prog load ./bpf_program.o /sys/fs/bpf/bpf_prog

ls 查看 bpf_prog 文件,确定成功创建,信息如下;

[root@debian-10:15:47:50] hello_world # ls -lh /sys/fs/bpf/bpf_prog 
-rw------- 1 root root 0 11月 22 15:44 /sys/fs/bpf/bpf_prog

使用 bpftool show 来观察程序仍旧存在,命令输出信息如下:

44: tracepoint  name bpf_prog  tag c6e8e35bea53af79  gpl
	loaded_at 2020-11-22T15:44:25+0800  uid 0
	xlated 112B  not jited  memlock 4096B

bpftool 检查 bpf 映射

bpftool 可以访问程序正在使用的 bpf 映射,命令类似上面 show 程序的形式。

执行示例如下:

[root@debian-10:15:49:55] hello_world # bpftool map show
2: lpm_trie  flags 0x1
	key 8B  value 8B  max_entries 1  memlock 4096B
3: lpm_trie  flags 0x1
	key 20B  value 8B  max_entries 1  memlock 4096B
4: lpm_trie  flags 0x1
	key 8B  value 8B  max_entries 1  memlock 4096B
5: lpm_trie  flags 0x1
	key 20B  value 8B  max_entries 1  memlock 4096B
6: lpm_trie  flags 0x1
	key 8B  value 8B  max_entries 1  memlock 4096B
7: lpm_trie  flags 0x1
	key 20B  value 8B  max_entries 1  memlock 4096B

bpftool 也可以用来创建、更新、查看映射信息,不进一步描述。

批量加载 bpftool 命令

bpftool 支持批处理模式,可以将命令写入到一个文件中,然后使用 bpftool batch file filepath 来执行。

在 /tmp/bpf 文件中写入如下两行:

prog show 
map show

然后执行 bpftool batch file /tmp/bpf 来运行,操作记录如下:

[root@debian-10:15:57:14] hello_world # bpftool batch file /tmp/bpf 
3: cgroup_skb  tag 7be49e3934a125ba  gpl
	loaded_at 2020-11-21T19:16:17+0800  uid 0
	xlated 296B  not jited  memlock 4096B  map_ids 2,3
4: cgroup_skb  tag 2a142ef67aaad174  gpl
	loaded_at 2020-11-21T19:16:17+0800  uid 0
	xlated 296B  not jited  memlock 4096B  map_ids 2,3
..........
2: lpm_trie  flags 0x1
	key 8B  value 8B  max_entries 1  memlock 4096B
3: lpm_trie  flags 0x1
	key 20B  value 8B  max_entries 1  memlock 4096B
..........
processed 2 commands

总结

bpf 的核心是 bpf 虚拟机,对 bpf 相关程序的学习是为了研究 bpf 虚拟机做准备,在上手 bpftool 的过程中有了初步的研究,后续需要进一步深化!同时在看内核代码的时候发现 trace 模块是我一直没有看到的内容,貌似很多书中也都没有讲这部分内容,也需要抽时间学习一下!

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Flowable是一个用于实现工作流的技术框架。如果你对工作流这块没有接触过,可以通过查看Flowable的官方文档来了解。\[1\]官方文档可以帮助你了解Flowable的基本概念和使用方法。在文档中,你可以找到关于Flowable的介绍、配置、API文档以及示例代码等内容。 在实际项目中,我们通常需要将流程流转到指定的人或拥有指定权限的人那边进行处理。对于这个问题,你可以参考官方文档中关于Flowable的权限管理和任务分配的章节。\[2\]通过配置相关的权限和任务分配规则,你可以确保只有具备相应权限的人才能执行特定的命令。 此外,Flowable还提供了Flowable UI应用,可以通过绘制流程图的方式来设计工作流,并导出BPMN的xml结构。\[3\]这样可以避免手动编写繁琐的xml配置。你可以下载最新稳定版本的Apache Tomcat和Flowable 6,将Flowable的war文件复制到Tomcat的webapps文件夹下,并启动Tomcat服务器。然后,通过访问http://localhost:8080/flowable-modeler来使用Flowable UI应用。 总之,要上手Flowable,你可以先阅读官方文档,了解Flowable的基本概念和使用方法。然后,根据实际项目需求,配置权限和任务分配规则。最后,可以使用Flowable UI应用来设计和管理工作流程。 #### 引用[.reference_title] - *1* *2* *3* [Flowable使用初体验](https://blog.csdn.net/ITlikeyou/article/details/124041657)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v4^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值