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

#!/usr/bin/python
from __future__ import print_function
from bcc import BPF
from collections import defaultdict
 
bpf_text = """
#include
#include
#include
#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[]);

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

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

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