eBPF监控工具bcc系列八BPF C

Posted badman250

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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);

"""

Linux内核 eBPF基础:BCC (BPF Compiler Collection)

深入浅出 eBPF: (Linux/Kernel/XDP/BCC/BPFTrace/Cillium)

BCC 和 Ftrace 追踪内核网络模块实战

eBPF理解

eBPF理解

高效入门eBPF