0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

基于ebpf的性能工具-bpftrace脚本语法

Rice嵌入式开发技术分享 来源:Rice 嵌入式开发技术分享 作者:Rice 嵌入式开发技 2023-09-04 16:04 次阅读

bpftrace 通过高度抽象的封装来使用 eBPF,大多数功能只需要寥寥几笔就可以运行起来,可以很快让我们搞清楚 eBPF 是什么样的,而暂时不关心 eBPF 复杂的内部机理。由于 bpftrace 深受 AWK 和 c 的影响,bpftrace 使用起来于 AWK 非常相似,那些内核 hook 注入点几乎可以按普通字符串匹配来理解,非常容易上手。

前面我们介绍了如何部署bpftrace工具,并且介绍了如何运行bpftrace脚本,这篇文章将介绍bpftrace脚本的语法。

基于ubuntu22.04-深入浅出 eBPF

基于ebpf的性能工具-bpftrace

bpftrace脚本语法

脚本格式

  • bpftrace脚本基本格式如下:
probe{
actions;
}
  • bpftrace语法深受AWK的影响,{前的部分相当于AWK的condition,{}中的部分相当于AWK的action。只不过bpftrace执行actions的条件是触发probe名称指定的事件。
  • probe是探针的名称,我们知道内核中函数非常多,为了方便,内核对probe做了namespace处理,这里的probe通常是以冒号:分割的一组名称,比如:
tracepointtick_stop
kprobe:do_sys_open
  • 显然,最后一部分表示的是函数名称,其他部分则是namespace,这样做有两点好处:①便于查找函数;②便于定位不同模块中的同名函数。

  • bpftrace除了可以监听指定的probe事件,还有两个特殊的probe:BEGIN,END。这与AWK类似,它们分别在bpftrace程序执行开始、结束时,无条件的执行一些操作,比如完成一些初始化、清理工作等。

BEGIN{
print("helloworld.n");
}

END{
print("byeworld.n");
}
  • filter是可选的,有时候我们只需要探测特定条件下函数的行为,比如参数为某个值的时候,就可以用到filter,这需要了解bpftrace如何访问probe的变量,我们稍晚再说。

prbbe参数

ebpf支持的probe:hardware,iter,kfunc,kprobe,software,tracepoint,uprobe。

9c747342-4af9-11ee-bb52-92fbcf53809c.png
  1. dynamic tracing
  • ebpf提供了内核和应用的动态trace,分别用于探测函数入口处和函数返回(ret)处的信息

    • ①面向内核的 kprobe/kretprobe,k = kernel
    • ②面向应用的 uprobe/uretprobe,u = user land
  • kprobe/kretprobe 可以探测内核大部分函数,出于安全考虑,有部分内核函数不允许安装探针,另外也可以配合 offset 探测函数中任意位置的信息。

  • uprobe/uretprobe 则可以为应用的任意函数安装探针。

  • 动态 trace 技术依赖内核和应用的符号表,对于那些 inline 或者 static 函数则无法直接安装探针,需要自行通过 offset 实现。可以借助 nm 或者 strings 指令查看应用的符号表。

  • 这两种动态 trace 技术的原理与 GDB 类似,当对某段代码安装探针,内核会将目标位置指令复制一份,并替换为 int3 中断, 执行流跳转到用户指定的探针 handler,再执行备份的指令,如果此时也指定了 ret 探针,也会被执行,最后再跳转回原来的指令序列。

  • kprobe 和 uprobe 可以通过 arg0、arg1... ... 访问所有参数;kretprobe 和 uretprobe 通过 retval 访问函数的返回值。除了基本类型:char、int 等,字符串需通过 str() 函数才能访问。

  1. static tracing
  • 静态 trace,所谓 “静态” 是指探针的位置、名称都是在代码中硬编码的,编译时就确定了。静态 trace 的实现原理类似 callback,当被激活时执行,关闭时不执行,性能比动态 trace 高一些。

    • ① 内核中的静态trace:tracepoint
    • ② 应用中的静态trace: usdt = Userland Statically Defined Tracing
  • 静态 trace 已经在内核和应用中饱含了探针参数信息,可以直接通过 args->参数名 访问函数参数。tracepoint 的 参数 format 信息可以通过 bpftrace -v probe 查看:

youyeetoo@youyeetoo:~$bpftrace-lvtracepointsys_exit
tracepointsys_exit
longid
longret
youyeetoo@youyeetoo:~$
  • 或者访问debugfs:
youyeetoo@youyeetoo:~$cat/sys/kernel/debug/tracing/events/raw_syscalls/sys_exit/format
name:sys_exit
ID:348
format:
field:unsignedshortcommon_type;offset:0;size:2;signed:0;
field:unsignedcharcommon_flags;offset:2;size:1;signed:0;
field:unsignedcharcommon_preempt_count;offset:3;size:1;signed:0;
field:intcommon_pid;offset:4;size:4;signed:1;

field:longid;offset:8;size:8;signed:1;
field:longret;offset:16;size:8;signed:1;

printfmt:"NR%ld=%ld",REC->id,REC->ret
youyeetoo@youyeetoo:~$

内置变量

无论 Dynamic tracing 或者 Static tracing,它们的目的都是监听特定函数调用事件,这些函数即可以在内核中,也可以在用户态的应用或者 lib 中。获知这些函数调用时的参数、返回值就已经实现了开发者大半目标。除此之外,bpfstrace 还内置了一些变量,用户访获得探测对象自身信息。这些变量在 bpftrace 中直接访问即可,如下:

  • pid / tid:Bpftrace或者说eBPF工作在内核,因此这些变量都与内核中进程表示有关。先说tid,内核中线程与进程没做作明确区分,它们都是相同的调度对象task_sruct。tid是thread id的缩写,由于历史原因,在task中的成员是task_sruct.pid。所以对于Linux内核,线程=轻量级进程。而pid实际上指的是内核中进程组,由task中的task_sruct.tgid成员表示。也就是说,进程=线程组。
  • uid / gid:执行函数的用户ID、组ID。
  • nsecs:时间戳,纳秒。
  • elapsed:ebpfs 启动后的纳秒数。
  • numaid:NUMA = Non-Uniform Memory Access,与多核 CPU 的内存访问相关。
  • cpu:当前 cpu 编号,从 0 开始。
  • comm:进程名称,通常为进程可执行文件名。
  • kstack:内核栈。
  • ustack: 用户栈。
  • arg0, arg1, ..., argN:函数参数。
  • sarg0, sarg1, ..., sargN:函数参数(栈中)。
  • retval:返回值。
  • func:函数名,可以在可执行文件的符号表中这个函数名。
  • probe:探针的完整名称,也就是 bpftrace 中 形如 'kprobe:do_nanosleep'
  • curtask:当前 task struct。
  • rand:一个无符号 32 位随机数。
  • cgroup:当前进程的 Cgroup,内核资源组,类似 namespace,docker 等虚拟化技术即基于内核提供的这一基础设施。
  • cpid:子进程 pid,bpftrace 允许通过 -c 指定一个 cmd 运行,然后在该进程上安装 probe。
  • 2, ..., #:bpftrace 程序自身的位置参数

全局变量

  • 全局变量@name,所谓的全局变量:①对所有的probe actions可见,②bpftrace生命周期内可见。

  • bpftrace支持两种变量形式:

    • ① 简单变量,@name = value;简单变量就是单纯的变量名和值,很容易理解,你可以在脚本中创建任意数量的简单变量。
    • ② Map,@name[key] = value;Map非常接近Python中的Dict,或者C中的数组,但数组索引可以是数字、字符串等。例如借助内置变量tid可以为每个线程记录独立的数值。
  • 测试例子:

kprobe:do_nanosleep{
@start[tid]=nsecs;
}

kretprobe:do_nanosleep/@start[tid]!=0/{
printf("sleptfor%dmsn",(nsecs-@start[tid])/1000000);
delete(@start[tid]);
}
  • 运行效果:
youyeetoo@youyeetoo:~$bpftracebpf_test.bt
Attaching2probes...
sleptfor0ms
sleptfor0ms
sleptfor0ms
sleptfor0ms
sleptfor0ms
sleptfor0ms
sleptfor0ms

临时变量

$name, 只在当前action中有效,超出action的{}不具备记忆能力。

内置函数

bpftrace无法自定义函数,但提供了约36个内置函数,可以在bpftrace脚本的任意位置调用它们。完整的列表可以参考官方文档:(https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md)。

bpftrace的函数非常有限,原因是bpftrace脚本会编译为bytecode,交由内核中的eBPF VM执行,出于安全和效率考虑,eBPF VM不能允许用户执行任意函数,仅允许执行限定的函数,或缺有限的数据。

  1. printf -- printf(fmt, ...)bpftrace的printf函数行为与C语言基本一致,区别在于它只支持有限的格式化字符,不如C语言支持的那么多。
BEGIN{
print("helloworld.n");
}

END{
print("byeworld.n");
}
  1. time -- time(fmt)time函数用于打印当前时间,可以通过参数中的格式化字符串指定,如果没有指定格式化字符串,那么默认格式是%H:%M:%Sn。time函数完全兼容strftime的格式化字符,下面列出一些常用项:
  • %S 秒,00-60;
  • %M 分钟,00-59;
  • %I 小时,01-12;%H 小时,00-23;
  • %d 每月的第几天,01-31;
  • %w 星期,0-6, 0 指 星期日;
  • %m 月份,01-12;
  • %y 年份,00-99;%Y 完整的年份;

「注意:格式化字符结尾不要忘记换行,否则不会自动清空缓冲区到标准输出,就看不到输出了。」

youyeetoo@youyeetoo:~$bpftrace-e'interval1{time("%Y%H:%M:%Sn");}'
Attaching1probe...
202316:35:30
202316:35:31
202316:35:32
^C
  1. system该函数可以调用 shell,用于 probe 触发其他用户态可执行程序非常有用。下面是一个简单的例子,定时调用 `ps. 查看当前进程:
youyeetoo@youyeetoo:~$bpftrace--unsafe-e'kprobe:do_nanosleep{system("ps-p%dn",pid);}'
Attaching1probe...
PIDTTYTIMECMD
933?00:00:00cron
^C
  1. ustack当使用 uprobe 时,很可能需要关注用户进程的 stack 情况,ustack 函数接受 2 个参数,这两个参数可以同时使用,或者只用 1 个。
  • mode,stack 模式,可选 bpftrace、perf;
  • limit,一个整数,获取 stack 的最大深度;
youyeetoo@youyeetoo:~$bpftrace-e'uprobereadline{printf("%sn",ustack(perf,3));}'
stdin:1:1-21:WARNING:attachingtouprobetargetfile'/usr/bin/bash'butmatched2binaries
uprobereadline{printf("%sn",ustack(perf,3));}
~~~~~~~~~~~~~~~~~~~~
Attaching1probe...

56440bb42690readline+0(/usr/bin/bash)
56440bb42690readline+0(/usr/bin/bash)
56440bb42690readline+0(/usr/bin/bash)

控制语句

bpftrace 也提供了常见的流程控制语句:① 条件语句 ② 循环语句

  1. 条件语句
  • bpftrace的条件语句用法与C语言完全一样:
if(condition){
statements;//A
}else{
statements;//B
}
  • 当满足条件时执行 A 处语句,否则执行 B 处语句。当然也可能有以下更简单的形式,没有 else 部分,条件满足时执行 A 处语句,然后执行 B 处语句,否则跳过 A 处语句:
if(condition){
statements;//A
}

statements;//B
  • 多个 if-else 也可能连接在一起:
if(condition){
statements;//A
}elseif(condition){
statements;//B
}elseif(condition){
statements;//C
}else{
statements;//D
}
  • 测试样例:
BEGIN{
$num=$1;
if($num>=10){
$result="A";
}elseif($num>=5){
$result="B";
}else{
$result="C"
}
printf("result:%sn",$result);
exit();
}
  • 测试样例结果:
youyeetoo@youyeetoo:~$bpftracebpf_test.bt15
Attaching1probe...
result:A

youyeetoo@youyeetoo:~$bpftracebpf_test.bt8
Attaching1probe...
result:B

youyeetoo@youyeetoo:~$bpftracebpf_test.bt3
Attaching1probe...
result:C

youyeetoo@youyeetoo:~$
  1. 循环语句
  • bpftrace 支持一种最常见的循环形式:
while(condition){
//dosomething
}
  • 测试样例:
BEGIN{
$i=0;
while($i< 10){
printf("i=%dn",$i);
$i++
}
exit();
}

  • 测试样例结果:
youyeetoo@youyeetoo:~$bpftracebpf_test.bt
Attaching1probe...
i=0
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9

审核编辑 黄宇


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式
    +关注

    关注

    4983

    文章

    18295

    浏览量

    288597
  • 函数
    +关注

    关注

    3

    文章

    3904

    浏览量

    61311
  • 语法
    +关注

    关注

    0

    文章

    40

    浏览量

    9669
  • 脚本
    +关注

    关注

    1

    文章

    372

    浏览量

    14637
收藏 人收藏

    评论

    相关推荐

    解构内核源码eBPF样例编译过程

    了解和掌握纯c语言的ebpf编译和使用,有助于我们加深对于eBPF技术原理的进一步掌握,也有助于开发符合自己业务需求的高性能ebpf程序。
    的头像 发表于 04-17 14:05 1166次阅读

    Linux跟踪工具bpftrace的原理和使用

    这篇文章介绍一个基于ebpf技术的强大工具--bpftrace
    发表于 09-01 15:10 1209次阅读
    Linux跟踪<b class='flag-5'>工具</b><b class='flag-5'>bpftrace</b>的原理和使用

    如何在 Shell 脚本中执行语法检查调试模式

    LCTT 原创编译,Linux中国 荣誉推出我们开启了 Shell 脚本调试系列文章,先是解释了不同的调试选项,下面介绍如何启用 Shell 调试模式。写完脚本后,建议在运行脚本之前先检查
    发表于 12-31 11:04

    总结linux脚本语法和正则表达式的应用

    每日学一点之linux脚本语法以及正则表达式基础
    发表于 11-08 09:23

    Makefile脚本语法简介

    宏定义LEDS_CTL 的使用Makefile脚本语法简介Makefile测试
    发表于 12-22 06:39

    openEuler 倡议建立 eBPF 软件发布标准

    eBPF 被广泛应用在云原生、可观测、性能调优、安全、硬件加速等领域,并且其应用场景还在快速扩展,各种场景基于 eBPF 技术的创新 idea 呈现井喷现象,eBPF 的时代已经来临
    发表于 12-23 16:21

    强劲的Linux Trace工具 bpftrace for Linux 2018

    本文主要是Brendan Gregg在介绍 bpftrace在2018年的开发进展,以及对bpftrace的介绍和对Dtrace的区别介绍。
    的头像 发表于 06-04 15:44 1.2w次阅读
    强劲的Linux Trace<b class='flag-5'>工具</b> <b class='flag-5'>bpftrace</b> for Linux 2018

    win7字体转换脚本工具下载

    win7字体转换脚本工具下载
    发表于 05-17 11:47 15次下载

    eBPF是什么以及eBPF能干什么

    一、eBPF是什么 eBPF是extended BPF的缩写,而BPF是Berkeley Packet Filter的缩写。对linux网络比较熟悉的伙伴对BPF应该比较了解,它通过特定的语法
    的头像 发表于 07-05 15:17 9795次阅读
    <b class='flag-5'>eBPF</b>是什么以及<b class='flag-5'>eBPF</b>能干什么

    基于ebpf性能工具-bpftrace

    运行情况对于诊断问题、优化性能以及进行安全监控至关重要。bpftrace作为一款强大的跟踪工具,为开发人员和系统管理员提供了一种独特的方式来监视和分析Linux系统的内部运行。本文描述bpft
    的头像 发表于 09-04 16:02 345次阅读
    基于<b class='flag-5'>ebpf</b>的<b class='flag-5'>性能</b><b class='flag-5'>工具</b>-<b class='flag-5'>bpftrace</b>

    ebpf的快速开发工具--libbpf-bootstrap

    基于ubuntu22.04-深入浅出 eBPF 基于ebpf性能工具-bpftrace 基于ebpf
    的头像 发表于 09-25 09:04 396次阅读
    <b class='flag-5'>ebpf</b>的快速开发<b class='flag-5'>工具</b>--libbpf-bootstrap

    基于ebpf性能工具应用

    曾利用Valgrind工具成功地发现并解决了一个隐藏在软件中的bug,这充分体现了工具在开发过程中的重要性。 然而,同样强大的bpftrace工具同样具备简洁而直观的特点,能够协助我们
    的头像 发表于 11-08 16:19 200次阅读
    基于<b class='flag-5'>ebpf</b>的<b class='flag-5'>性能</b><b class='flag-5'>工具</b>应用

    脚本错误scripterror怎么解决

    脚本错误”(Script Error)通常是在运行或尝试运行一段脚本或程序时出现的错误。这种错误可能源于许多不同的原因,包括语法错误、运行环境问题、依赖库缺失等。解决脚本错误需要针对
    的头像 发表于 11-26 14:46 3091次阅读

    脚本调试工具有哪些?脚本调试工具怎么用?

    脚本调试是软件开发过程中非常重要的一环,它能帮助开发者快速定位并解决代码中的错误。大多数编程语言都提供了各种各样的脚本调试工具,本文将介绍一些常见的脚本调试
    的头像 发表于 12-01 14:40 302次阅读

    Shell脚本检查工具ShellCheck介绍

    ShellCheck是一个用于bash/sh shell脚本的静态分析工具,可以辅助检查脚本语法错误,给出建议增强脚本健壮性。
    的头像 发表于 12-27 13:43 570次阅读
    Shell<b class='flag-5'>脚本</b>检查<b class='flag-5'>工具</b>ShellCheck介绍