Golang编程语言知识介绍


  • 首页

  • todo

  • 思考

  • life

  • food

  • OS

  • lua

  • redis

  • Golang

  • C

  • TCP/IP

  • ebpf

  • p4

  • OpenVPN

  • IPSec

  • L2TP

  • DNS

  • distributed

  • web

  • OpenWRT

  • 运维

  • Git

  • 鸟哥的私房菜

  • IT杂谈

  • 投资

  • About Me

  • 友情链接

  • FTP

  • 搜索
close

【BPF入门系列-8】文件打开记录跟踪之 perf_event 篇

时间: 2023-08-24   |   分类: ebpf     |   阅读: 1440 字 ~3分钟

ebpf_perf_output 介绍

在上一篇 ”使用 ebpf 实时持续跟踪进程文件记录“ 中,我们简单介绍了使用 eBPF 跟踪文件打开记录的跟踪。为了简单演示功能,我们直接使用了 bpf_trace_printk 进行演示,正如上文所述,bpf_trace_printk 存在一些限制:

  • 最大只支持 3 个参数,而且只运行一个 %s 的参数;
  • 程序共享输出共享 /sys/kernel/debug/tracing/trace_pipe 文件,可能导致文件输出错乱;
  • 该实现方式在数据量大的时候,性能也存在一定的问题;

本文中我们将使用更加高效且提供隔离功能的 BPF_PERF_OUTPUT 机制来实现数据的传递,而且由于数据通过结构体定义的方式,也不存在参数数量和数据大小等限制。

为了使用 BPF_PERF_OUTPUT 机制,需要约定 Probe 程序和用户空间程序的通信协议。相比简单使用 bpf_trace_printk,在内核中的 Probe 程序需要以下操作:

  • 定义一个通信的结构体,用于 Probe 程序与用户空间通信程序的数据传输约定;
  • 定一个用于通信的 perf_event 对象,BCC 提供了宏 BPF_PERF_OUTPUT 实现;
  • 内核中的 Probe 程序捕获事件,将数据按照第一步定义好的结构体填充,并将 event 事件发布;

在用户空间程序,在本文中为基于 BCC 的 Python代码:

  • 定义和声明通信的结构体;(基于 BCC 的程序已经自动生成,无需再定义)
  • 定义消费 event 事件的函数;
  • 持续消费事件程序;

代码实现

原始代码如下:

#!/usr/bin/python
from bcc import BPF

prog = """
int trace_syscall_open(struct pt_regs *ctx, const char __user *filename, int flags) {
    u32 pid = bpf_get_current_pid_tgid() >> 32;

    bpf_trace_printk("%d [%s]\\n", pid, filename);
    return 0;
}
"""

b = BPF(text=prog)
b.attach_kprobe(event=b.get_syscall_fnname("open"), fn_name="trace_syscall_open")
try:
    b.trace_print()
except KeyboardInterrupt:
    exit()

按照上述的步骤,调整后的 Probe 的程序如下:

prog = """
#include <uapi/linux/limits.h> // for  NAME_MAX

// 1 define struct
struct event_data_t {
    u32 pid;
    char fname[NAME_MAX];  // max of filename
};

// 2. declare BPF_PERF_OUTPUT define
BPF_PERF_OUTPUT(open_events);

int trace_syscall_open(struct pt_regs *ctx, const char __user *filename, int flags) {
    u32 pid = bpf_get_current_pid_tgid() >> 32;

    // 3.1 define event data and fill data
    struct event_data_t evt = {};

    evt.pid = pid;
    bpf_probe_read(&evt.fname, sizeof(evt.fname), (void *)filename);

    // bpf_trace_printk("%d [%s]\\n", pid, filename); =>
    // 3.2 submit the event
    open_events.perf_submit(ctx, &evt, sizeof(evt));

    return 0;
}
"""

这里将详细介绍我们进行的相关调整:

  1. 定义了内核 Probe 程序与用户空间程序通信的结构体 event_data_t,包含 pid 和 filename 两个字段;
  2. 使用 BCC 提供的宏 BPF_PERF_OUTPUT(open_events) 完成内核中 open_events 变量的定义;
  3. 在 trace_syscall_open 函数中,增加变量的定义 struct event_data_t evt = {}; ,需要注意的是结构体变量 evt.fname 的赋值,需要使用 eBPF 提供的辅助函数 bpf_probe_read 来帮助,这是因为内核对于非简单类型的赋值需要进行安全边界的检查,避免在内核中进行越界访问,破坏内核稳定性和安全性的保障;
  4. 最后,使用 open_events.perf_submit 将 event 数据发送至用户空间;

上述代码,完成了我们在内核 Probe 程序中的所有工作。用户空间 Python 程序则需要定义 event 消费函数,并使用 perf_buffer_poll 函数轮训消费即可。

# 1.1 define process event
def print_event(cpu, data, size):
  event = b["open_events"].event(data)
  print("Rcv Event %d, %s"%(event.pid, event.fname))

# 1.2 loop with callback to print_event
b["open_events"].open_perf_buffer(print_event)
while True:
    try:
        b.perf_buffer_poll()  # 2. perf poll
    except KeyboardInterrupt:
        exit()

在用户空间的 Python 代码中,当前我们只需要定义事件处理函数,将事件与函数进行关联,然后持续轮询数据即可。

  1. 我们定义了事件处理函数 print_event,然后读取出对应的数据并生成结构数据 ,event = b["open_events"].event(data),此处不用再声明 Python 中的结构体变量,BCC 已经协助处理,否则需要我们显示定义,在一些早期的 BCC 代码中还可以看到手工转换的场景。

  2.  import ctypes
     class OpenEvt(ctypes.Structure):
         _fields_ = [
             ("pid",   ctypes.c_uint),
             ("fname", ctypes.c_char * MAX_STR_LEN),
         ]
    
     # event 处理函数中强制 cast 使用    
     # event = ct.cast(data, ct.POINTER(OpenEvt)).contents
    

    `

  3. 然后在主体函数中使用 b.perf_buffer_poll() 持续轮询即可;

运行结果如下:

#./open_perf_output.py
Rcv Event 1732, /var/log/secure
Rcv Event 12846, /usr/lib64/python2.7/encodings/ascii.so

完整样例可以参考 open_perf_output.py。

3. 总结

通过我们上述代码样例,相信你已经非常熟悉了如何使用 BPF_PERF_OUTPUT 方式,在直接编写的各种跟踪程序中,优先推荐使用这种方式进行高效的数据通信。

  • 原文作者:DavidDi
  • **原文链接:**https://www.ebpf.top/post/ebpf_trace_file_open_perf_output/
  • **版权声明:**本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
#ebpf#
【BPF入门系列-9】文件打开记录结果跟踪篇
【BPF入门系列-7】使用 ebpf 实时持续跟踪进程文件记录
shankusu2017@gmail.com

shankusu2017@gmail.com

日志
分类
标签
GitHub
© 2009 - 2025
粤ICP备2021068940号-1 粤公网安备44011302003059
0%