跟踪代码 Fedora 使用 bpftrace

[ad_1]

bpftrace 是 一个新的基于 eBPF 的跟踪工具 那是第一次包含在 Fedora 28. 它是由 Brendan Gregg、Alastair Robertson 和 Matheus Marchini 在一个松散的网络黑客团队的帮助下开发的。 跟踪工具可让您分析系统在幕后执行的操作。 它告诉您代码中的哪些函数正在被调用,使用哪些参数,调用了多少次,等等。

本文介绍了 bpftrace 的一些基础知识及其工作原理。 请继续阅读以获取更多信息和一些有用的示例。

eBPF(扩展伯克利数据包过滤器)

eBPF 是 Linux 内核中的一个微型虚拟机,或者更准确地说是一个虚拟 CPU。 eBPF 可以在内核空间以安全可控的方式加载和运行小程序。 这使其使用起来更安全,即使在生产系统中也是如此。 这个虚拟机有自己的指令集架构(ISA) 类似于现代处理器架构的一个子集。 ISA 可以轻松地将这些程序转换为真正的硬件。 内核对主要架构的本机代码执行即时转换,以提高性能。

eBPF 虚拟机允许以编程方式扩展内核。 现在有几个内核子系统利用了这种新的强大的 Linux 内核功能。 示例包括网络、seccomp、跟踪等。 主要思想是将 eBPF 程序附加到特定的代码点,从而扩展原始内核行为。

eBPF 机器语言非常强大。 但是直接在其中编写代码非常痛苦,因为它是一种低级语言。 这就是 bpftrace 的用武之地。它提供了一种高级语言来编写 eBPF 跟踪脚本。 然后该工具在 clang/LLVM 库的帮助下将这些脚本转换为 eBPF,然后附加到指定的代码点。

安装和快速启动

要安装 bpftrace,请在终端中使用以下命令运行以下命令 sudo:

$ sudo dnf install bpftrace

用“hello world”例子试试看:

$ sudo bpftrace -e 'BEGIN { printf("hello worldn"); }'

请注意,由于需要权限,您必须以 root 身份运行 bpftrace。 使用 -e 选项指定程序,并构建所谓的“单行”。 这个例子只打印hello world,然后等你按 Ctrl+C.

BEGIN 是一个特殊的探测器名称,仅在执行开始时触发一次。 每当探针被击中时,花括号 { } 内的每个动作都会触发——在这种情况下,它只是一个 printf。

现在让我们跳到一个更有用的例子:

$ sudo bpftrace -e 't:syscalls:sys_enter_execve { printf("%s called %sn", comm, str(args->filename)); }'

此示例打印父进程名称 (comm) 和系统中正在创建的每个新进程的名称。 t:syscalls:sys_enter_execve 是内核跟踪点。 它是 tracepoint:syscalls:sys_enter_execve 的简写,但两种形式都可以使用。 下一节将向您展示如何列出所有可用的跟踪点。

comm 是一个 bpftrace 内置函数,表示进程名称。 文件名是 t:syscalls:sys_enter_execve 跟踪点的一个字段。 您可以通过内置的 args 访问这些字段。

可以使用以下命令列出跟踪点的所有可用字段:

bpftrace -lv "t:syscalls:sys_enter_execve"

示例用法

列出探针

bpftrace 的一个核心概念是 探测点. 探测点是代码(内核或用户空间)中可以附加 eBPF 程序的检测点。 它们分为以下几类:

  • kprobe – 内核函数启动
  • kretprobe – 核函数返回
  • uprobe – 用户级功能启动
  • uretprobe – 用户级函数返回
  • 跟踪点 – 内核静态跟踪点
  • usdt – 用户级静态跟踪点
  • profile – 定时采样
  • 间隔 – 定时输出
  • 软件 – 内核软件事件
  • 硬件——处理器级事件

所有可用的 kprobe/kretprobe、tracepoints、软件和硬件探测器都可以用这个命令列出:

$ sudo bpftrace -l

uprobe/uretprobe 和 usdt 探针是特定于给定可执行文件的用户空间探针。 要使用它们,请使用本文后面显示的特殊语法。

配置文件和间隔探测器以固定的时间间隔触发。 本文不涉及固定时间间隔。

计算系统调用

地图 是特殊的 BPF 数据类型,用于存储计数、统计数据和直方图。 您可以使用映射来总结每个系统调用被调用的次数:

$ sudo bpftrace -e 't:syscalls:sys_enter_* { @[probe] = count(); }'

某些探针类型允许通配符匹配多个探针。 您还可以使用逗号分隔列表为操作块指定多个附加点。 在此示例中,操作块附加到名称以 t:syscalls:sys_enter_ 开头的所有跟踪点,这意味着所有可用的系统调用。

bpftrace 内置函数 count() 计算调用此函数的次数。 @[] 表示地图(关联数组)。 该映射的关键是probe,它是另一个bpftrace 内置函数,表示完整的探针名称。

在这里,每个系统调用都附加了相同的操作块。 然后,每次调用系统调用时,映射都会更新,并且映射中的条目相对于同一个系统调用递增。 当程序终止时,它会自动打印出所有声明的映射。

此示例计算全局调用的系统调用,也可以使用 bpftrace 过滤器语法按 PID 过滤特定进程:

$ sudo bpftrace -e 't:syscalls:sys_enter_* / pid == 1234 / { @[probe] = count(); }'

按进程写入字节

使用这些概念,我们来分析一下每个进程写了​​多少字节:

$ sudo bpftrace -e 't:syscalls:sys_exit_write /args->ret > 0/ { @[comm] = sum(args->ret); }'

bpftrace 将操作块附加到写入系统调用返回探针 (t:syscalls:sys_exit_write)。 然后,它使用过滤器丢弃负值,即错误代码 (/args->ret > 0/)。

映射键 comm 表示调用系统调用的进程名称。 sum() 内置函数累积为每个映射条目或进程写入的字节数。 args 是一个 bpftrace 内置函数,用于访问跟踪点的参数和返回值。 最后,如果成功,写入系统调用将返回写入的字节数。 args->ret 提供对字节的访问。

按进程读取大小分布(直方图):

bpftrace 支持创建直方图。 让我们分析一个创建按进程读取大小分布的直方图的示例:

$ sudo bpftrace -e 't:syscalls:sys_exit_read { @[comm] = hist(args->ret); }'

直方图是 BPF 映射,因此它们必须始终归属于映射 (@)。 在本例中,映射键是 comm。

该示例使 bpftrace 为调用读取系统调用的每个进程生成一个直方图。 要仅生成一个全局直方图,只需将 hist() 函数归因于 ‘@’(不带任何键)。

bpftrace 在程序终止时自动打印出声明的直方图。 用作直方图创建基础的值是通过 args->ret 找到的读取字节数。

跟踪用户空间程序

您还可以使用 uprobes/uretprobes 和 USDT(用户级静态定义跟踪)来跟踪用户空间程序。 下一个示例使用 uretprobe,它探测到用户级函数的末尾。 它获取在每个发出的命令行 bash 在系统中运行:

$ sudo bpftrace -e 'uretprobe:/bin/bash:readline { printf("readline: "%s"n", str(retval)); }'

列出所有可用的 uprobes/uretprobes bash 可执行文件,运行以下命令:

$ sudo bpftrace -l "uprobe:/bin/bash"

uprobe 检测用户级函数执行的开始,而 uretprobe 检测结束(其返回)。 readline() 是 /bin/ 的函数bash, 并返回键入的命令行。 retval 是检测函数的返回值,只能在 uretprobe 上访问。

使用 uprobes 时,您可以使用 arg0..argN 访问参数。 需要调用 str() 才能将 char * 指针指向字符串。

提供的脚本

bpftrace 包附带了许多有用的脚本。 您可以在 /usr/share/bpftrace/tools/ 目录中找到它们。

其中,您可以找到:

  • killsnoop.bt – 由 kill() 系统调用发出的跟踪信号。
  • tcpconnect.bt – 跟踪所有 TCP 网络连接。
  • pidpersec.bt – 每秒计算新进程(通过 fork)。
  • opensnoop.bt – 跟踪 open() 系统调用。
  • vfsstat.bt – 计算一些 VFS 调用,每秒汇总。

您可以直接使用脚本。 例如:

$ sudo /usr/share/bpftrace/tools/killsnoop.bt

您还可以在创建新工具时研究这些脚本。

链接

拍摄者 罗曼·罗马绍夫不飞溅.

[ad_2]

Related Posts