可观测性 kprobe ebpf
介绍
eBPF 是从 BPF(Berkeley Packet Filter)技术扩展而来,最初是为了实现快速捕获和过滤符合特定规则的网络数据包,后来这个思路受到欢迎,使得bpf不再局限于网络,成为了一项令人兴奋的新技术,它使内核编程变得灵活、安全且可供开发人员使用。
具有以下优势:
- 无需修改源代码即可使内核动态编程。
- 安全、快速、非常灵活且可扩展。
- 应用范围广,包括可观测性、网络、安全、跟踪和分析。
但是eBPF一项相当复杂的技术,具有较高的门槛,对于不懂内核和网络内部工作机制的普通开发人员来说并不容易掌握。
所以便出现了类似于bcc、bpftrace这样致力于降低ebpf开发难度的工具。
这两个工具不是本篇的重点。如果大家感兴趣的话,我可以在后续的ebpf专题系列中介绍。
工作机制
ebpf程序分为用户态和内核态两部分,用户态编写的C程序由llvm转化成字节码,通过bpf系统调用把用户态ebpf程序加载并attach到指定的hook点,特定的事件触发hook执行,通过maps或者ringbuffer输出到用户态。
对于tracing eBPF使用bpf_trace_printk()
输出到 /sys/kernel/debug/tracing/trace_pipe
ebpf程序由内核中的事件触发,当执行某些特定的指令时,事件将会被hook捕获,并执行该hook点特定的行为动作。
ebpf支持的事件如下图所示
对于tracing相关的hook类型有三种:
kprobes、tracepoints、perf events
- BPF_PROG_TYPE_KPROBE
- BPF_PROG_TYPE_TRACEPOINT
- BPF_PROG_TYPE_PERF_EVENT
ebpf的hook类型很多,在后续的ebpf系列中讲解。
实验
编译时遇到了一些问题,在后续的ebpf系列主题中讲解。
在__netif_receive_skb
函数处打桩。
修改samples/bpf/tracex1_kern.c代码,当进入到__netif_receive_skb函数时打印一句话。
主要的内核态代码如下
//SEC("kprobe/__netif_receive_skb_core")
SEC("kprobe/__netif_receive_skb")
int bpf_prog1(struct pt_regs *ctx)
{
/* attaches to kprobe __netif_receive_skb_core,
* looks for packets on loobpack device and prints them
*/
char devname[IFNAMSIZ];
struct net_device *dev;
struct sk_buff *skb;
int len;
/* non-portable! works for the given kernel only */
bpf_probe_read_kernel(&skb, sizeof(skb), (void *)PT_REGS_PARM1(ctx));
dev = _(skb->dev);
len = _(skb->len);
bpf_probe_read_kernel(devname, sizeof(devname), dev->name);
char fmt[] = "im in __netif_receive_skb \n";
bpf_trace_printk(fmt, sizeof(fmt));
if (devname[0] == 'l' && devname[1] == 'o') {
char fmt[] = "skb %p len %d\n";
/* using bpf_trace_printk() for DEBUG ONLY */
bpf_trace_printk(fmt, sizeof(fmt), skb, len);
}
return 0;
}
用户态代码,加载ebpf程序,ping localhost 然后读取trace
int main(int ac, char **argv)
{
struct bpf_link *link = NULL;
struct bpf_program *prog;
struct bpf_object *obj;
char filename[256];
FILE *f;
snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
obj = bpf_object__open_file(filename, NULL);
if (libbpf_get_error(obj)) {
fprintf(stderr, "ERROR: opening BPF object file failed\n");
return 0;
}
prog = bpf_object__find_program_by_name(obj, "bpf_prog1");
if (!prog) {
fprintf(stderr, "ERROR: finding a prog in obj file failed\n");
goto cleanup;
}
/* load BPF program */
if (bpf_object__load(obj)) {
fprintf(stderr, "ERROR: loading BPF object file failed\n");
goto cleanup;
}
link = bpf_program__attach(prog);
if (libbpf_get_error(link)) {
fprintf(stderr, "ERROR: bpf_program__attach failed\n");
link = NULL;
goto cleanup;
}
f = popen("taskset 1 ping -c5 localhost", "r");
(void) f;
read_trace_pipe();
cleanup:
bpf_link__destroy(link);
bpf_object__close(obj);
return 0;
}
编译
# make VMLINUX_BTF=/sys/kernel/btf/vmlinux -C samples/bpf
运行
# ./tracex1 tracex1
samples/bpf# ./tracex1 tracex1
ping-65965 [000] d.s41 7464.353130: bpf_trace_printk: im in __netif_receive_skb
ping-65965 [000] dNs41 7464.353147: bpf_trace_printk: im in __netif_receive_skb
ping-65965 [000] d.s41 7465.384240: bpf_trace_printk: im in __netif_receive_skb
ping-65965 [000] d.s41 7465.384315: bpf_trace_printk: im in __netif_receive_skb
ping-65965 [000] d.s41 7466.408695: bpf_trace_printk: im in __netif_receive_skb
ping-65965 [000] d.s41 7466.408801: bpf_trace_printk: im in __netif_receive_skb
ping-65965 [000] d.s41 7467.432237: bpf_trace_printk: im in __netif_receive_skb
ping-65965 [000] d.s41 7467.432342: bpf_trace_printk: im in __netif_receive_skb
ping-65965 [000] d.s41 7468.455720: bpf_trace_printk: im in __netif_receive_skb
ping-65965 [000] d.s41 7468.455756: bpf_trace_printk: im in __netif_receive_skb
参考
https://lwn.net/Articles/740157/ samples/bpf/ Kernel Tracing With eBPF
欢迎大家转发分享。未经授权,严禁任何复制、转载、摘编或以其它方式进行使用,转载须注明来自eBPFLAB并附上本文链接。如果有侵犯到您权益的地方,请及时联系我删除。