bpftrace是一种基于eBPF的新跟踪工具,首次在Fedora28中引入。BrendanGregg、AlastairRobertson和MatheusMarchini在一群松散的在线黑客的帮助下开发了bpftrace。它是一个跟踪工具,可以让你分析系统在幕后做了什么,告诉你代码中调用了哪些函数,传递给函数的参数,函数被调用了多少次等等。这篇文章涵盖了bpftrace的一些基础知识及其工作原理,请继续阅读以获取更多信息和一些有用的示例。eBPF(extendedBerkeleyPacketFilter)eBPF是一个微型虚拟机,更准确地说是Linux内核中的一个虚拟CPU。eBPF可以在内核空间以安全可控的方式加载和运行小程序,使得eBPF的使用更加安全,即使是在生产环境系统中。eBPF虚拟机有自己的指令集架构(ISA),类似于现代处理器架构的一个子集。使用此ISA,可以轻松地将eBPF程序转换为真实硬件上的代码。内核立即将程序转换为主流处理器架构上的本机代码,从而提高性能。eBPF虚拟机允许通过编程扩展内核。目前,一些内核子系统使用了这个新的强大的Linux内核功能,例如网络、安全计算和跟踪。这些子系统的主要思想是通过向特定代码点添加eBPF程序来扩展原生内核行为。eBPF机器语言虽然强大,但是由于是低级语言,直接写代码非常费力。bpftrace就是为了解决这个问题而诞生的。eBPF提供了编写eBPF跟踪脚本的高级语言,然后借助clang/LLVM库将这些脚本转换为eBPF,最后添加到特定的代码点。安装和快速入门在终端中使用sudo安装bpftrace:$sudodnfinstallbpftrace使用“helloworld”进行实验:$sudobpftrace-e'BEGIN{printf("helloworld\n");}'注意,对于权限级别,您必须以root身份运行bpftrace,使用-e选项指定程序,并构建所谓的“one-liner”。此示例将只打印“helloworld”,然后等待您按Ctrl+C。BEGIN是一个特殊的探针名称,只在开始执行时生效一次;每次探测命中时,都会执行花括号{}内的操作(??在本例中,只是一个printf)。现在让我们转向一个更有用的例子:$sudobpftrace-e't:syscalls:sys_enter_execve{printf("%scalled%s\n",comm,str(args->filename));}'此示例打印父进程的名称(comm)以及系统中正在创建的每个新进程的名称。t:syscalls:sys_enter_execve是一个内核跟踪点,tracepoint:syscalls:sys_enter_execve的简写,两种形式都可以使用。下一节将向您展示如何列出所有可用的跟踪点。comm是一个bpftrace内置的,表示进程名称;filename是t:syscalls:sys_enter_execve跟踪点的一个字段,可以通过内置的args访问。可以使用以下命令列出跟踪点的所有可用字段:bpftrace-lv"t:syscalls:sys_enter_execve"代码中的测量点可以分为以下几类:kprobe——内核函数的开始kretprobe——内核函数的返回uprobe——用户级函数的开始uretprobe——用户级函数的开始返回tracepoint—kernelstatictracepointusdt—user-levelstatictracepointprofile—time-basedsamplinginterval—time-basedoutputsoftware—kernelsoftwareeventhardware—processor-leveleventallavailablekprobe/kretprobetracepoints,软硬件探测都可以列出通过此命令:$sudobpftrace-luprobe/uretprobe和usdt是用户空间探测器,专用于可执行文件。要使用这些探测器,请使用下面的特殊语法。profile和interval探测器定期触发;固定间隔不在本文讨论范围之内。统计系统调用计数映射是特殊的BPF数据类型,包含计数、统计数据和直方图,您可以使用映射来计算每个系统调用被调用的次数:$sudobpftrace-e't:syscalls:sys_enter_*{@[probe]=数数();}'有些探针类型允许使用通配符来匹配多个探针,您也可以使用逗号分隔的列表为一个操作块指定多个连接点。在上面的示例中,操作块连接到名称以t:syscalls:sysenter_开头的所有跟踪点,即所有可用的系统调用。bpftrace的内置函数count()统计系统调用被调用的次数;@[]表示一个映射(一个关联数组)。该映射的关键探针是另一个表示完整探针名称的内置函数。在这个例子中,同一个操作块连接到每个系统调用,然后每调用一次系统调用,映射就会更新,映射中对应系统调用的条目会递增。当程序终止时,自动打印出所有声明的映射。以下示例统计所有系统调用,然后使用bpftrace过滤器语法过滤掉使用PID的特定进程调用的系统调用:$sudobpftrace-e't:syscalls:sys_enter_*/pid==1234/{@[probe]=计数();}'进程写入的字节数让我们使用上面的概念来分析每个进程正在写入的字节数:$sudobpftrace-e't:syscalls:sys_exit_write/args->ret>0/{@[comm]=总和(args->ret);}'bpftrace将操作块连接到write系统调用的返回探针(t:syscalls:sys_exit_write),然后使用过滤器丢弃代表错误代码的负值(/arg->ret>0/)。map的keycomm代表调用系统调用的进程名;内置函数sum()累加每个映射条目或进程写入的字节数;args是bpftrace内置指令,用于访问tracepoint的参数和返回值。如果成功,write系统调用返回写入的字节数,arg->ret用于访问这个字节数。进程的读取大小分布(直方图):bpftrace支持创建直方图。让我们分析一个创建进程的读取大小分布的直方图示例:$sudobpftrace-e't:syscalls:sys_exit_read{@[comm]=hist(args->ret);}'直方图是BPF图,所以必须保存为图(@),本例中图key为comm。此示例使bpftrace为每个调用read系统调用的进程生成直方图。要生成全局直方图,请将hist()函数直接保存到@(不带任何键)。当程序终止时,bpftrace自动打印出声明的直方图。建立直方图的基准值是通过args->ret得到的读取字节数。跟踪用户空间程序您还可以通过uprobes/uretprobes和USDT(用户级静态定义跟踪)跟踪用户空间程序。下一个示例使用uretprobe,它探测用户级函数的结尾,以获取系统上运行的每个bash发出的命令行:$sudobpftrace-e'uretprobe:/bin/bash:readline{printf("readline:\"%s\"\n",str(retval));}'要列出可执行bash的所有可用uprobes/uretprobes,请执行此命令:$sudobpftrace-l"uprobe:/bin/bash"uprobepointstouser-levelfunctions在执行开始时,uretprobe指向执行结束(返回);readline()是/bin/bash的一个函数,它返回键入的命令行;retval是被探测指令的返回值,只能在uretprobe中访问。使用uprobe时,您可以使用arg0..argN访问参数。您需要调用str()将char*指针转换为字符串。bpftrace包附带了许多有用的脚本,可以在/usr/share/bpftrace/tools/目录中找到。在这些脚本中,您可以找到:killsnoop.bt-跟踪来自kill()系统调用的信号tcpconnect.bt-跟踪所有TCP网络连接pidpersec.bt-计算每秒创建的新进程(通过fork)opensnoop.bt-跟踪打开()systemcallbfsstat.bt-tracessomeVFScalls,以秒为单位你可以直接使用这些脚本,例如:$sudo/usr/share/bpftrace/tools/killsnoop.bt你也可以在创建新工具时参考这些脚本.链接到bpftrace参考指南-https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.mdLinux2018bpftrace(DTrace2.0)-http://www.brendangregg.com/blog/2018-10-08/dtrace-for-linux-2018.htmlBPF:通用内核虚拟机-https://lwn.net/Articles/599755/Linux扩展BPF(eBPF)跟踪工具-http://www.brendangregg.com/ebpf.html深入BPF:阅读清单-https://qmonnet.github.io/whirl-offload/2016/09/01/dive-into-bpf
