一,点总结
????????学习内核模块的一个比较好的方法,是直接找内核源码samples目录对应的实例编译运行:比如想要了解 connector 模块以 netlink 协议通信的流程,可以找 samples/connector 例子编译.??学习 eBPF也是,通过查看 samples/bpf 说明(Makefile)以及源码,会收获良多。 ????????初步折腾的比较多了,大概也就会发现eBPF也就是 跟踪的函数、函数的参数及返回值、参数及返回值的内核结构体、map、perf event、ring buf、bpf_helpers.h,CORE 等着几个东西。如果需要在进一步,就需要阅读内核源码了。
二,知识点预热
? ? ? ? 在 X86_64 架构下,定义了6个寄存器保存函数参数:%rdi, %rsi, %rdx, %rcx,%r8, %r9 如果函数参数多于6个,则多余的参数按照32位架构方式以从左到右的顺序压栈:
三,代码来源
????????https://github.com/ehids/ehids-agent.git
四,代码备注解释
SEC("kprobe/tcp_set_state") ?//以 kprobe 方式跟踪内核态 tcp_set_state 函数 int kprobe__tcp_set_state(struct pt_regs *ctx) { ? ? //暂且认为ctx是linux内核进入 tcp_set_state 函数时的寄存器状态 ? ? //#define PT_REGS_PARM1(x) ((x)->di), 寄存器 di 存储的是 tcp_set_state 函数的第一个参数对象地址 ? ? struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);
? ? if (!sk) return 0;
? ? //#define PT_REGS_PARM1(x) ((x)->si), 寄存器 si 存储的是 tcp_set_state 函数的第一个参数对象地址 ? ? int state = (int)PT_REGS_PARM2(ctx);
? ?....
? ? if (state == TCP_SYN_SENT) { ? ? ? ?....
? ? //通过bpf_helpers.h函数获取sock源目的地址 ? ??bpf_probe_read(&data.lport, sizeof(data.lport), &sk->__sk_common.skc_num); ? ? bpf_probe_read(&data.rport, sizeof(data.rport), &sk->__sk_common.skc_dport);
? ? //通过perf event发送到应用层 ? ??bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &data, sizeof(data)); ? ? ... }
五,结合内核函数说明
? ? ? ? 看下图,如果之前不明白的话,期望现在你有种豁然开朗的感觉.?
? ? ? ? 以kprobe方式为例,除了 tcp_set_state 函数外,linux内核还有哪些可以跟踪?
? ? ? ? 这么多的函数,eBPF真的可以为所欲为了!?
六,编译运行
????????当前很多文章或者github上所要求的eBPF编译内核版本都比较高,一般都是 linux 5.X 版本。因为bpf代码会涉及内核定义的结构体,但是不同linux内核版本的结构体可能会有差异,这样就引出了内核版本兼容的问题。 ? ? ? ? 因为项目环境的原因,需要在ubuntu 16.04 的 linux 4.9.X 版本上编译eBPF,文中从github上拿来的bpf代码?tcp_set_state_kern.c,在 编译确实会遇到很多头文件引用的问题。 ????????这里有三种解决方案,一是 使用BPF CORE特性(4.9版本不支持BTF,也就不考虑了);二是 使用系统头文件;三是 直接拷贝所需的结构体并做简化修改。
七,一些经验教训
????????前期使用的是第三种方法,eBPF程序也确实运行起来了,但是该种方法破坏了生产环境工程规范:易出错,难拓展,难维护。 ????????遇到这个问题其实就需要找人商讨解决了,大BOSS是不认同改结构体的方法的,我也觉的此法甚搓,经过大BOSS亲自上阵不断编译尝试,最终成功回归到第二种方法的正确轨道上来。 ????????其实我知道不修改结构体直接引用系统头文件是条正确的道路,但是还是有取巧的心理直接拷贝修改结构体。所以坚持、确信走该走的路的这种品质,还是要多多注意培养。
????????
|