bpftrace内核探针 - 文件监控实现

一、bpftrace简介

bpftrace 是基于 eBPF 和 BCC 的开源系统跟踪工具。

bpftrace 使用 LLVM 作为后端将脚本编译为 BPF 字节码,并利用 BCC 与 Linux BPF 系统进行交互,通过探针机制采集内核和程序运行的信息,帮助开发者找到隐藏较深的Bug、安全问题和性能瓶颈。
在这里插入图片描述

二、bpftrace安装

bpftrace 官方建议 Linux 内核高于 4.9 ,实际低版本内核也可编译安装,但某些功能可能不可用。

麒麟V10-SP1桌面可直接联网apt安装:

apt-get install bpftrace

麒麟V10-SP1服务器需配置SP2 Update源安装:

[ks10-adv-updates-sp2]
name = Kylin Linux Advanced Server 10 - Updates - sp2
baseurl = http://update.cs2c.com.cn:8080/NS/V10/V10SP2/os/adv/lic/updates/$basearch/
gpgcheck = 1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-kylin
enabled = 1
yum install bpftrace
yum info bpftrace

Name         : bpftrace
Version      : 0.11.0
Release      : 8.ky10
Architecture : x86_64
Size         : 4.2 M
Source       : bpftrace-0.11.0-8.ky10.src.rpm
Repository   : @System
From repo    : ks10-adv-updates-sp2
Summary      : High-level tracing language for Linux eBPF
URL          : https://github.com/iovisor/bpftrace
License      : ASL 2.0
Description  : BPFtrace is a high-level tracing language for Linux enhanced Berkeley Packet
             : Filter (eBPF) available in recent Linux kernels (4.x). BPFtrace uses LLVM as a
             : backend to compile scripts to BPF-bytecode and makes use of BCC for
             : interacting with the Linux BPF system, as well as existing Linux tracing
             : capabilities: kernel dynamic tracing (kprobes), user-level dynamic tracing
             : (uprobes), and tracepoints. The BPFtrace language is inspired by awk and C,
             : and predecessor tracers such as DTrace and SystemTap

三、bpftrace简单使用

3.1 探针

在这里插入图片描述

如图,kprobeb/kretprobe 为动态跟踪、内核级探针,kprobeb 是检测函数执行的开始,kretprobe 为检测结束(返回)。uprobe/uretprobe 为动态跟踪、用户级探针,uprobeb 是检测用户级函数执行的开始,uretprobe 为检测结束(返回)。tracepoint 为静态跟踪、用户级探针。

3.2 基础语法

3.2.1 单行语句

单行语句可以直接在命令行输入执行,ctrl + c 结束执行。

//打印输出
bpftrace -e 'BEGIN { printf("Hello, World!\n"); }'
//跟踪系统打开文件的进程,并打印文件名
bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }'


tracepoint:syscalls:sys_enter_openat: 该探针类型为内核静态跟踪,并且当进入openat()系统调用时执行该探针;
comm 是内建变量,代表当前进程的名字。其它类似的变量还有 pid 和 tid,分别表示进程标识和线程标识;
args 为指针,指向该 tracepoint 的参数;
str() 用来把字符串指针转换成字符串;
3.2.2 文件形式

bpftrace 也可以执行 C style 的代码文件,格式为 bpftrace <filename> 。这种方式可以实现相对复杂的逻辑。比如通过 bpftrace 获取当前所有 shell 的输入,代码如下:

// 初始化时执行,主要用于打印提示信息
BEGIN
{
        printf("Tracing bash commands... Hit Ctrl-C to end.\n");
        printf("%-9s\t%s\t%s\t%s\n", "TIME", "UID", "PID", "COMMAND");
}

//探针追踪内容
uretprobe:/bin/bash:readline
{
        time("%H:%M:%S\t");
        printf("%d\t%d\t%s\n", uid, pid, str(retval));
}

//bpftrace结束时执行
END  
{
        printf("end-test");
}

更多 bpftrace 的使用说明可以参考官方文档:

bpftrace/reference_guide.md at master · iovisor/bpftrace (github.com)

bpftrace/tutorial_one_liners_chinese.md at master · iovisor/bpftrace (github.com)

四、文件监控实现

4.1 需求

通过监控 passwd 文件来找到是哪个进程或者是哪个用户删除了该文件

4.2 具体实现

创建 trace-delete.bt 文件

#include <linux/sched.h>
#include <linux/dcache.h>

tracepoint:syscalls:sys_enter_unlink,tracepoint:syscalls:sys_enter_unlinkat {
    printf("%s deleted by command '%s' with pid %d\n",
            str(args->pathname), comm, pid);

    //取内核 task_struct 中 real_parent 字段
    $p = curtask->real_parent;

    /*
     * 循环打印父进程名和 pid
     * 5.3以上内核可使用 while
     */
    unroll(10) {
        printf(" ... whose parent is '%s' command with pid %d\n",
               $p->comm,
               $p->tgid);
        $p = $p->parent;
    }
}

Linux 下文件的删除一般通过2个系统调用:unlink 和 unlinkat。内核在系统调用出入口都有 tracepoint,能够暴露传入的参数。bpftrace 程序可以在这些点挂载 BPF 程序来采集实时状态。
注意:5.3以上的内核开始支持 C style whlie 和 for loop,并且可以使用 continue 和 break 关键字进行循环控制。5.3以下的内核只可使用 unroll() 方法。

4.3 演示(基于Kylin-Server-V10SP1-0518)

1、创建 bpftrace 监控程序和测试用文件夹
trace-delete.bt
在这里插入图片描述

测试文件夹
在这里插入图片描述

2、编写 DeleteFile 程序删除 /root/test-delete 文件夹,编译导出为可执行jar文件,模拟应用删除文件

package test;
import java.io.File;  

public class DeleteFile {
    public static void main(String[] args) {
        File file = new File("/root/test-delete");
        remove(file);
        file.delete();
        if (!file.exists()){
            System.out.println("删除成功!");
        }
        try {
            //模拟常驻运行的某服务
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void remove(File file){
        File[] files = file.listFiles();
        if (files!=null){
            for (int i = 0; i < files.length; i++) {
            if (files[i].isFile()){
                files[i].delete();
            }else if(files[i].isDirectory()){
                remove(files[i]);
            }
            files[i].delete();
            }    
        }
    }
}

3、运行监控程序

bpftrace trace-delete.bt

4、执行java程序删除文件夹

java -jar DeleteFile.jar

5、查看输出结果
在这里插入图片描述

更多信创知识分享,请关注 《大冻梨XC运维》

请添加图片描述

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐