第一个eBPF程序.md

背景

了解bpf的童鞋都应该知道,bpf程序是可以attach到不同的probe点上来做内核级别的trace,那么对于刚入门的人来说,如何来编写一个初级的bpf程序呢?这就是本篇博文想要介绍的内容。

bpf程序的组成

BPF程序我们知道它需要先使用LLVM进行编译,完成后加载到内核中去执行,那么也就是说对于BPF程序来说,它应该包含有两部分:

  • 第一部分是用户态程序,它的作用是用来加载编译完成的内核BPF字节码文件,并且与内核态程序进行交互,一般是获取profiling数据。
  • 第二部分是内核态BPF程序,使用C语言编写,用来编译生成字节码。

参数获取

在编写内核程序时,有一个固定的格式,假如我们需要添加一些逻辑到一个probe点上,那么需要定义一个入口函数,这个入口函数的格式应该是怎样的呢?它应该带有什么样的参数呢?

这个格式实际是固定的,它只接受一个参数 struct pt_regs *ctx ,而我们想要trace的函数,比如系统调用sys_execve,它是有多个参数的:

asmlinkage long sys_execve(const char __user *filename,
                const char __user *const __user *argv,
                const char __user *const __user *envp);

那么我们如何从ctx中来获取这些参数值呢?想要获取一个函数的入口参数,那么需要的probe类型就要是kprobe 因为这种类型会在进入函数后立刻执行我们的BPF函数,那么参数值上下文都在寄存器中会直接传入到我们的入口BPF函数中,可以通过如下的函数来获取参数。内核已经预定义了bpf helper函数来获取这些参数信息。

#define PT_REGS_PARM1(x) ((x)->di)
#define PT_REGS_PARM2(x) ((x)->si)
#define PT_REGS_PARM3(x) ((x)->dx)
#define PT_REGS_PARM4(x) ((x)->cx)
#define PT_REGS_PARM5(x) ((x)->r8)
#define PT_REGS_RET(x) ((x)->sp)
#define PT_REGS_FP(x) ((x)->bp)
#define PT_REGS_RC(x) ((x)->ax)
#define PT_REGS_SP(x) ((x)->sp)
#define PT_REGS_IP(x) ((x)->ip)

那么另外一种情况,如果probe点是一个tracepoint的话,我们怎么知道入口函数中能获取什么信息呢?这里划重点!!!

比如,sys_enter_execve是内核预定义的一个tracepoint,那么如果我们BPF程序被attach到这个位置上,如何知道ctx中都保存了什么信息呢?其实可以通过:

/sys/kernel/debug/tracing/events/syscalls/sys_enter_execve/format

来查看:

# cat  /sys/kernel/debug/tracing/events/syscalls/sys_enter_execve/format
name: sys_enter_execve
ID: 663
format:
        field:unsigned short common_type;       offset:0;       size:2; signed:0;
        field:unsigned char common_flags;       offset:2;       size:1; signed:0;
        field:unsigned char common_preempt_count;       offset:3;       size:1; signed:0;
        field:int common_pid;   offset:4;       size:4; signed:1;

        field:int __syscall_nr; offset:8;       size:4; signed:1;
        field:const char * filename;    offset:16;      size:8; signed:0;
        field:const char *const * argv; offset:24;      size:8; signed:0;
        field:const char *const * envp; offset:32;      size:8; signed:0;

print fmt: "filename: 0x%08lx, argv: 0x%08lx, envp: 0x%08lx", ((unsigned long)(REC->filename)), ((unsigned long)(REC->argv)), ((unsigned long)(REC->envp))

最后的输出:

  field:int __syscall_nr; offset:8;       size:4; signed:1;
        field:const char * filename;    offset:16;      size:8; signed:0;
        field:const char *const * argv; offset:24;      size:8; signed:0;
        field:const char *const * envp; offset:32;      size:8; signed:0;

就是这个tracepoint位置上的参数列表。可以直接定义类似的数据结构来代替struct pt_regs *ctx传参到入口函数中,实际上是进行了一次强制类型转换。比如:

struct syscall_execve_args {
	unsigned long long unsed;
	long syscall_nr;
	long filename_ptr;
	long argv;
	long envp;
}

内核中有另外一个例子:https://github.com/torvalds/linux/blob/418baf2c28f3473039f2f7377760bd8f6897ae18/samples/bpf/syscall_tp_kern.c。

bcc中的BPF传参

借用BCC前端来写BPF程序,和纯C语言写BPF是由些许差异的,首先对于BCC来说,它把BPF程序的两个模块,内核程序和用户态程序合二为一了。
BCC前端可选python语言编写,这极大提升了开发效率。那么python它作为用户态语言,内核态程序要怎么写呢?
其实在python中,是以字符串类型来写内核BPF C语言代码,在执行时,会去做编译和加载的过程。截取我写的示例:

#!/usr/bin/python
from __future__ import print_function
from bcc import BPF
import time


# define BPF program
bpf_text = """
#include 
#include 
#include 
#include 

int syscall__execve(struct pt_regs *ctx,
    const char __user *filename,
    const char __user *const __user *__argv,
    const char __user *const __user *__envp)
{

    u32 pid = bpf_get_current_pid_tgid() >> 32;

    bpf_trace_printk("pid:%d Hello,world\\n", pid);
    return 0;
}

"""

# initialize BPF
b = BPF(text=bpf_text)
execve_fnname = b.get_syscall_fnname("execve")
b.attach_kprobe(event=execve_fnname, fn_name="syscall__execve")

start_ts = time.time()


# loop with callback to print_event
while 1:
    try:
        time.sleep(1);
        b.trace_print()
    except KeyboardInterrupt:
        exit()

从上面的片段可知,内核BPF代码直接作为了python中的一段string来处理的。

到这里有些人可能存在疑问了,前面不是说对应的入口函数只能有一个参数ctx吗,这里为什么传了3个参数???

实际上这是BCC库做的一些改造,为了更方便开发者使用syscall的入口参数,对于kprobe来说,BCC允许按照syscall的入口参数来传参,而在BCC底层处理时,会对这个bpf_text代码做转换操作,详细信息可以参考:https://github.com/iovisor/bcc/blob/v0.14.0/src/cc/frontends/clang/b_frontend_action.cc#L692

今天的关键点都记录到了,先写到这里吧。

你可能感兴趣的:(ebpf,ebpf)