eBPF监控工具bcc系列八BPF C


在之前的bcc代码中我们知道其程序是分为两部分的,一部分是C语言,另一部分是基于Python的。本篇是关于C语言部分的。

1.   事件和参数

1.1     kprobes

使用kprobe的语法是:

kprobe__kernel_function_name

其中kprobe__是前缀,用于给内核函数创建一个kprobe(内核函数调用的动态跟踪)。也可通过C语言函数定义一个C函数,然后使用python的BPF.attach_kprobe()来关联到内核函数。

            例如:

int kprobe__tcp_v4_connect(struct pt_regs *ctx, struct sock *sk)

            其中参数struct pt_regs *ctx是寄存器和BPF文件

            sock *sk是tcp_v4_connect的第一个参数。

1.2     kretprobes

kretprobes动态跟踪内核函数的返回,语法如下:

kretprobe__kernel_function_name,前缀是kretprobe__。也可以使用python的BPF.attach_kretprobe()来关联C函数到内核函数。

            例如:

int kretprobe__tcp_v4_connect(struct pt_regs *ctx)

{     int ret = PT_REGS_RC(ctx);     [...]

}

            返回值保存在ret中。

1.3     Tracepoints

语法如下:TRACEPOINT_PROBE(category,event)

TRACEPOINT_PROBE是一个宏, tracepiont定义的方式是categroy:event,

            可以的参数是通过结构体args获取的,获取参数相关格式的方法是cat文件:

/sys/kernel/debug/tracing/events/category/event/format

            结构体args可以在函数中使用例如:

TRACEPOINT_PROBE(random, urandom_read) {    

// args is from /sys/kernel/debug/tracing/events/random/urandom_read/format     bpf_trace_printk("%d\\n", args->got_bits);     return 0;

}

1.4     uprobes

通过python的BPF.attach_uprobe()可以将普通C函数关联到uprobe探针。

参数可以通过PT_REGS_PARM宏来检测。

            程序本身名字使用宏PT_REGS_PARM1,第一个参数使用宏PT_REGS_PARM2。

 

1.5     uretprobes

同uprobes,只不过该探针是在函数返回时候触发。

     返回的值通过PT_REGS_RC(ctx)获取,例如:

BPF_HISTOGRAM(dist); int count(struct pt_regs *ctx) {     dist.increment(PT_REGS_RC(ctx));     return 0;

}

在直方图dist中,根据增加返回键对应的值。

1.6     USDT probes

User Statically-Defined Tracing用户静态定义的探针,会在应用和库中定义用来提供用户级别追踪。BPF中提供对USDT的支持方法是enable_probe。

使用方法同其他probes,定义C函数,然后通过USDT.enable_probe()来关联USDT probe。

            参数可以通过bpf_usdt_readarg(index,ctx,&addr)来读取。

            例如:

int do_trace(struct pt_regs *ctx) {   

 uint64_t addr;    

char path[128];    

bpf_usdt_readarg(6, ctx, &addr);    

bpf_probe_read(&path, sizeof(path), (void *)addr);     bpf_trace_printk("path:%s\\n", path);    

return 0;

};

1.7     汇总

事件

C语法

Python方式

参数获取

kprobes

kprobe__

BPF.attach_kprobe()

struct pt_regs *ctx

kretprobes

kretprobe__

BPF.attach_kretprobe()

PT_REGS_RC

tracepoints

TRACEPOINT_PROBE

BPF.attach_tracepoint()

struct args

uprobes

 

BPF.attach_uprobe()

PT_REGS_PARM

uretprobes

 

BPF.attach_uretprobe()

PT_REGS_RC

USDT probes

 

USDT.enable_probe

bpf_usdt_readarg

 

2.   数据

上节是关于事件和参数的,这边是看下如何获取所监控函数中数据。

2.1     bpf_probe_read

语法:int bpf_probe_read(void *dst, int size, const void *src)

如果成功返回0.

            该函数将内容复制到BPF栈中,用于后续使用。为了安全起见,所有内存读取都通过bpf_probe_read函数。

 

2.2     bpf_probe_read_str

语法:int bpf_probe_read_str(void *dst, int size, const void *src)

复制NULL结尾的字符串到BPF栈,用于后续使用。

2.3     bpf_ktime_get_ns

语法:u64 bpf_ktime_get_ns(void)

获取当前时间,以纳秒方式。

2.4     bpf_get_current_pid_tgid

语法:u64 bpf_get_current_pid_tgid(void)

            低32位为当前进程ID,高32位是组ID。

2.5     bpf_get_current_uid_gid

语法:u64 bpf_get_current_uid_gid(void)

返回用户ID和组ID.

2.6     bpf_get_current_common

语法:bpf_get_current_comm(char *buf, int size_of_buf)

用当前进程名字填充第一个参数地址。

2.7     bpf_get_current_task

返回指向当前task_struct对象的指针。可用于计算CPU在线时间,内核线程,运行队列和其他相关信息。

2.8     bpf_log2l

返回提供值的log-2结果,经常用于创建直方图索引构建直方图。

2.9     bpf_get_prandom_u32

返回一个无符号32位伪随机值。

3.   调试

3.1     bpf_override_return

语法:int bpf_override_return(struct pt_regs *, unsigned long rc)

            用于关联到函数入口的kprobe,忽略要执行的函数,返回rc,用于目标的故障注入。

            这个函数在允许故障注入的kprobe白名单中才能工作。白名单在内核代码树中会标记BPF_ALOW_ERROR_INJECTION()。

 

4.   输出

4.1     bpf_trace_printk

语法:int bpf_trace_printk(const char *fmt, int fmt_size, ...)

            简单的内核printf函数,输出到/sys/kernel/debug/tracing/trace_pipe。这个在之前有描述过,其最多3个参数,智能输出%s,而且trace_pipe是全局共享的并发输出会有问题,生产中工具使用BPF_PERF_OUTPUT()函数。

4.2     BPF_PERF_OUTPUT

通过perf ring buffer创建BPF表,将定义的事件数据输出。这个是将数据推送到用户态的建议方法。

例如:

struct data_t {

    u32 pid;

    u64 ts;

    char comm[TASK_COMM_LEN];

};

BPF_PERF_OUTPUT(events);

 

int hello(struct pt_regs *ctx) {

    struct data_t data = {};

 

    data.pid = bpf_get_current_pid_tgid();

    data.ts = bpf_ktime_get_ns();

    bpf_get_current_comm(&data.comm, sizeof(data.comm));

 

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

 

    return 0;

}

代码中的输出表是events,数据通过events.perf_submit来推送。

 

4.3     perf_submit

语法:int perf_submit((void *)ctx, (void *)data, u32 data_size)

该函数是BPF_PERF_OUTPUT表的方法,将定义的事件数据推到用户态。

 

5.   映射

映射的Maps是BPF数据保存,是高级对象表、哈希表和直方图的基础。

5.1     BPF_TABLE

语法:BPF_TABLE(_table_type, _key_type, _leaf_type, _name, _max_entries)

创建一个映射名字为_name。大多数时候会被高层宏使用,例如BPF_HASH,BPF_HIST等。

            还有map.lookup(), map.lookup_or_init(), map.delete(), map.update(), map.insert(), map.increment().

5.2     BPF_HASH

语法BPF_HASH(name [, key_type [, leaf_type [, size]]])

创建一个name哈希表,中括号中是可选参数。

            默认:BPF_HASH(name, key_type=u64, leaf_type=u64, size=10240)

            相关函数:map.lookup(), map.lookup_or_init(), map.delete(), map.update(), map.insert(), map.increment().

5.3     BPF_ARRAY

语法:BPF_ARRAY(name [, leaf_type [, size]])

            创建一个整数索引整列,用于快速查询和更新。

默认参数如下:BPF_ARRAY(name, leaf_type=u64, size=10240)

相关函数:map.lookup(), map.update(), map.increment()

            整列中数据是预分配的,不能删除,所有没有删除操作了。

5.4     BPF_HISTOGRAM

语法:BPF_HISTOGRAM(name [, key_type [, size ]])

创建一个直方图,默认是由64整型桶索引。

相关函数:map.increment().

5.5     BPF_STACK_TACK

语法:BPF_STACK_TRACE(name, max_entries)

创建一个栈跟踪映射,叫做stack_traces。相关函数:map.get_stackid().

 

5.6     BPF_PERF_ARRAY

语法:BPF_PERF_ARRAY(name, max_entries)

创建perf array,参数中max_entries保持和系统中CPU数量一致。这个映射用于获取硬件性能计数器.

例如:

text="""

BPF_PERF_ARRAY(cpu_cycles, NUM_CPUS);

"""

b = bcc.BPF(text=text, cflags=["-DNUM_CPUS=%d" % multiprocessing.cpu_count()])

b["cpu_cycles"].open_perf_event(b["cpu_cycles"].HW_CPU_CYCLES)

创建一个名字为cpu_cycles的perf array,入口和cpu数量一致。

            Array配置计数器HW_CPU_CYCLES,后续可以通过map.perf_read函数读取到。每个表一次只能配置一种硬件计数器。

5.7     BPF_PERCPU_ARRAY

语法:BPF_PERCPU_ARRAY(name [, leaf_type [, size]])

创建NUM_CPU个整数索引数组,优化了快速查找和更新。每个CPU的数组相互之间不用同步。

5.8     BPF_LPM_TRIE

语法:BPF_LPM_TRIE(name [, key_type [, leaf_type [, size]]])

创建LPM trie映射。

 

5.9     BPF_PROG_ARRAY

语法:BPF_PROG_ARRAY(name, size)

创建映射程序的数组,每个值要么是一个文件描述符指向bpf程序要么是NULL.

这个数组可以作为跳转表,可以同跳转到其他的bpf程序。

相关函数:map.call()

5.10 Map.lookup

语法:*val map.lookup(&key)

寻找map中键为key的值,如果存在则返回指向该健值的指针。

5.11 Map.lookup_or_init

语法:*val map.lookup_or_init(&key, &zero)

在map中寻找键,找到返回健值的指针,找不到则初始化为第二个参数。

5.12 Map.delete

从map中删除某个健值。

5.13 Map.update

语法:map.update(&key, &val)

更新健值。

5.14 Map.insert

插入健值。

5.15 Map.increment

增加指定键的值,用于直方图。

5.16 Map.get_stackid

该函数从堆栈中寻找指定参数,返回栈跟踪的唯一ID。

5.17 Map.perf_read

从BPF_PERF_ARRAY中定义的数组返回硬件性能计数。

5.18 Map.call

语法:void map.call(void *ctx, int index)

调用bpf_tail_call来执行BPF_PROG_ARRAY数值中索引指向的程序,调用方式为tail-call表示不会返回到原先调用的函数。

例如:

BPF_PROG_ARRAY(prog_array, 10);

 

int tail_call(void *ctx) {

    bpf_trace_printk("Tail-call\n");

    return 0;

}

 

int do_tail_call(void *ctx) {

    bpf_trace_printk("Original program\n");

    prog_array.call(ctx, 2);

    return 0;

}

b = BPF(src_file="example.c")

tail_fn = b.load_func("tail_call", BPF.KPROBE)

prog_array = b.get_table("prog_array")

prog_array[c_int(2)] = c_int(tail_fn.fd)

b.attach_kprobe(event="some_kprobe_event", fn_name="do_tail_call")

            tail_call()赋给了prog_array[2],在函数do_tail_call()中调用prog_array.call(ctx,2)来调用tail_call()并执行。

 

 

你可能感兴趣的:(eBPF监控工具bcc系列八BPF C)