BPF学习笔记2-简单的BPF测试程序

446 阅读2分钟

结合前面一篇针对BPF的学习,本篇文章重点介绍编写一个对内核系统调用exec的例子。本测试例子基本上包含了全部的,syscall类别系统调用的BPF的框架。

#!/usr/bin/python

  from __future__ import print_function
  from bcc import BPF
  from collections import defaultdict

bpf_text = """
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>
#include <linux/fs.h>

#define ARGSIZE 256
struct data_t {
      u32 pid;  // PID as in the userspace term (i.e. task->tgid in kernel)
      char comm[TASK_COMM_LEN];
      char argv[ARGSIZE];
 };

BPF_PERF_OUTPUT(events);

int syscall__execve(struct pt_regs *ctx,
                   const char  __user *filename,
                   const char  __user *const __user *__argv,
                   const char  __user *const __user *__envp)
{
    struct data_t data = {};


    bpf_trace_printk("Hello, World!222%s\\n",filename);
    data.pid = bpf_get_current_pid_tgid();
    bpf_get_current_comm(&data.comm,sizeof(data.comm));

    bpf_probe_read_user(data.argv, sizeof(data.argv), filename);

    events.perf_submit(ctx, &data, sizeof(struct data_t));

    return 0;
}

"""
b = BPF(text=bpf_text)
execve_fnname = b.get_syscall_fnname("execve")
b.attach_kprobe(event=execve_fnname, fn_name="syscall__execve")
print("%-18s %-16s %-14s" % ("COMM", "PID","ARGS"))


argv = defaultdict(list)

def print_event(cpu, data, size):
    event = b["events"].event(data)
    argv[event.pid].append(event.argv)

    argv_text = b' '.join(argv[event.pid]).replace(b'\n', b'\\n')

    print("%-18s %-16d  %-14s" % (event.comm, event.pid,argv_text))


b["events"].open_perf_buffer(print_event)
while 1:
    try:
        b.perf_buffer_poll()
    except KeyboardInterrupt:
        exit()

主要注意点:

  1. BPF_PERF_OUTPUT   创建BPF的table,通过Perf 的环形缓存区,把用户定义的event事件的数据推送到用户空间,这是把事件数据推送到用户空间的首选的方式。 也就是如果把从内核中获取到的数据,push到用户空间进行处理和展示,在代码中添加该宏。

  2. b.attach_kprobe(event=execve_fnname, fn_name="syscall__execve") 这里的fn_name="syscal__execve"函数名,保持格式的绝对一致,也就是syscall__ 后面两个下划线,execve名字和内核中syscall保持一致。\

  3.  在内核中插桩syscall__execve 函数时,这里的第一个参数struct pt_regs *ctx 保持固定,其余的参数是syscall类函数保持一致的,下面是调用execve的参数。

 int execve(const char *pathname, char *const argv[], char *const envp[]);

  1.  其实整个程序包含了两个部分,一个是python的客户端,一个是以字符串的形式bpf_text出现的插桩函数,使用C语言编写,整个测试程序包含了,用户空间和内核空间的代码。

5  bpf_trace_printk函数,类似于C语言中printf,编写bpf程序时,可以用来调试打印相关信息。注意该打印函数会将信息输出到下面的文件中:/sys/kernel/debug/tracing/trace_pipe,通过跟踪该文件进行查看。

  1. print_even 函数时对内核中获取的数据和信息的统一展示。