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)