网站优化seo,互联网保险发展现状分析,网站开发 需要用到什么软件有哪些,网站建设的主要缺陷1 什么是EBPF#xff1f;
EBPF是一种可以让程序员在内核态执行自己的程序的机制#xff0c;但是#xff0c;为了安全起见#xff0c;无法像内核模块一样随意调用内核的函数#xff0c;只能调用一些bpf提前定义好的函数。为了让内核执行程序员自己的代码#xff0c;需要指…1 什么是EBPF
EBPF是一种可以让程序员在内核态执行自己的程序的机制但是为了安全起见无法像内核模块一样随意调用内核的函数只能调用一些bpf提前定义好的函数。为了让内核执行程序员自己的代码需要指定HOOK点相当于当内核执行到某个地方时就会执行程序员的代码这有点类似于ftrace。
EBPF程序包含两个部分
内核态代码编译为.o文件在内核态执行将数据发送到队列用户态代码将内核态代码的.o文件加载到内核接收队列中的数据并分析
2 Hello World(BCC)
EBPF有多种开发方式按照语言来分的话有两种方式一种是内核态代码和用户态代码都用C语言开发另一种是内核态代码用C语言开发而用户态代码可以其他语言开发例如Python、Golang、Lua等。
BCC是一个方便构建EBPF程序的工具让编写EBPF程序更加简单内核态代码依旧采用C语言而用户态代码可以采用Python和Lua。
下面就是一个简单的Hello world版本的EBPF程序该程序对execve系统调用进行HOOK内核态程序只执行一行打印语句用户态程序负责将数据打印出来因此每当系统中创建一个进程该程序就会输出一行Hello, BPF World!。
在执行之前需要根据操作系统安装一些包Installing BCC。
from bcc import BPFprogram
#include uapi/linux/ptrace.hint syscall__execve(struct pt_regs *ctx,const char __user *filename,const char __user *const __user *__argv,const char __user *const __user *__envp)
{char msg[] Hello, BPF World!;bpf_trace_printk(%s, msg);return 0;
}
b BPF(textprogram)
execve_fnname b.get_syscall_fnname(execve)
b.attach_kprobe(eventexecve_fnname, fn_namesyscall__execve)
b.trace_print()可以看到使用BCC开发EBPF程序还是比较简单的用户态程序只需要定义好要HOOK的点并接收数据主要的逻辑其实还是在内核态代码中。
但是这种方式不便于理解EBPF的工作方式比如program这段程序代码是怎么载入到内核的呢载入之前需要编译吗那Python里面是怎么编译的呢
3 EBPF的基于C语言的开发模式
要理解EBPF的工作方式最好的方式还是基于纯C语言开发EBPF程序。
如果用C语言开发内核态代码和用户态代码就必须分开写内核态代码就需要知道可以调用哪些函数而用户态代码就需要知道如何载入内核态编译后的二进制。
现在常用的开发方式有两种一种是基于原生libbpf库开发另一种是基于libbpf-bootstrap开发。
4 基于原生libbpf库
首先是环境的搭建
安装依赖yum install clang elfutils-libelf-devel其中clang用于编译内核态代码elfutils-libelf-devel用于处理内核态代码编译成的二进制安装libbpfgit clone https://github.com/libbpf/libbpf cd libbpf/src make make install生成vmlinux.h解除对kernel header的依赖bpftool btf dump file /sys/kernel/btf/vmlinux format c vmlinux.h
内核态代码和用户态代码都需要引入的头文件
#ifndef __EXECVE_H
#define __EXECVE_Hstruct event {pid_t pid;pid_t ppid;
};#endif内核态代码
#include vmlinux.h
#include execve.h
#include bpf/bpf_helpers.hstatic const struct event empty_event {};struct {__uint(type, BPF_MAP_TYPE_HASH);__uint(max_entries, 1024);__type(key, pid_t);__type(value, struct event);
} execs SEC(.maps);struct {__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);__uint(key_size, sizeof(u32));__uint(value_size, sizeof(u32));
} events SEC(.maps);SEC(tracepoint/syscalls/sys_enter_execve)
int tracepoint_execve(struct syscall_trace_enter *ctx) {u64 id bpf_get_current_pid_tgid();pid_t pid (pid_t)id;pid_t tgid id 32;if(bpf_map_update_elem(execs, pid, empty_event, BPF_NOEXIST)) {return 0;}struct event *event bpf_map_lookup_elem(execs, pid);if(event NULL) {return 0;}event-pid tgid;bpf_perf_event_output(ctx, events, BPF_F_CURRENT_CPU, event, sizeof(struct event));bpf_map_delete_elem(execs, pid);return 0;
}char _license[] SEC(license) GPL;用户态代码
#include stdio.h
#include errno.h
#include bpf/libbpf.h
#include bpf/bpf.h
#include execve.h#define PERF_BUFFER_PAGES 64
#define PERF_POLL_TIMEOUT_MS 100static void handle_event(void *ctx, int cpu, void *data, __u32 data_sz) {const struct event *e data;printf(pid:%d\n, e-pid);
}int main() {int err;// 打开内核程序生成的二进制文件struct bpf_object *bpf_obj bpf_object__open(execve_kern.o);if(bpf_obj NULL) {printf(open .o faile failed\n);return -1;}// 载入内核程序int ret bpf_object__load(bpf_obj);if(ret ! 0) {printf(load bpf object failed\n);return -1;}// 根据内核函数的函数名找到内核程序struct bpf_program *bpf_prog bpf_object__find_program_by_name(bpf_obj, tracepoint_execve);struct bpf_link *bpf_link bpf_program__attach(bpf_prog);if(libbpf_get_error(bpf_link)) {return -1;}// 找到事件的mapint fd bpf_object__find_map_fd_by_name(bpf_obj, events);// 根据事件的map创建perf_buffer并指定perf_buffer中的数据的回调处理函数handle_eventstruct perf_buffer *pb NULL;pb perf_buffer__new(fd, PERF_BUFFER_PAGES, handle_event, NULL, NULL, NULL);if(pb NULL) {printf(open perf buffer failed\n);goto cleanup;}while(true) {err perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS);if(err 0 err ! -EINTR) {printf(poll failed\n);goto cleanup;}err 0;}cleanup:perf_buffer__free(pb);bpf_link__destroy(bpf_link);bpf_object__close(bpf_obj);return 0;
}编译内核态程序clang -g -target bpf -D__TARGET_ARCH_x86 -c execve_kern.c -o execve_kern.o编译用户态程序gcc -g -Wall -c execve_user.c -o execve_user.o将用户态程序编译为完整的二进制gcc -g -Wall execve_user.o -o execve -lbpf
执行./execve时如果有新的进程就会打印进程的Pid。
5 基于libbpf-bootstrap脚手架
基于libbpf的方式有个缺点最终的二进制包含内核态程序编译的二进制execve_kern.o和用户态程序编译的二进制execve不便于分发。
第二种方式也是基于libbpf库只是在上层做了一些封装并且可以整体打成一个二进制。
git clone https://github.com/libbpf/libbpf-bootstrap# 下载依赖的仓库的代码
cd libbpf-bootstrap git submodule update --init --recursivelibbpf-bootstrap仓库本身的内容只有examples(示例代码)以及与vmlinux.h相关的tools和vmlinux目录主要依赖其他的仓库
blazesym地址符号化和反向查找bpftool用于生成skel文件和vmlinux.h文件libbpf也就是上面的libbpf库
examples/c中的minimal.bpf.c和minimal.c就是最简单的示例代码。
// minimal.bpf.c
#include linux/bpf.h
#include bpf/bpf_helpers.hchar LICENSE[] SEC(license) Dual BSD/GPL;int my_pid 0;SEC(tp/syscalls/sys_enter_write)
int handle_tp(void *ctx)
{int pid bpf_get_current_pid_tgid() 32;if (pid ! my_pid)return 0;bpf_printk(BPF triggered from PID %d.\n, pid);return 0;
}内核态代码hook write系统调用当用户态程序执行write时就打印一行输出这里的my_pid会在用户态程序中被替换。
#include stdio.h
#include unistd.h
#include sys/resource.h
#include bpf/libbpf.h
#include minimal.skel.hstatic int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{return vfprintf(stderr, format, args);
}int main(int argc, char **argv)
{struct minimal_bpf *skel;int err;/* 安装libbpf库的错误和调试函数 */libbpf_set_print(libbpf_print_fn);/* 打开内核态程序 */skel minimal_bpf__open();if (!skel) {fprintf(stderr, Failed to open BPF skeleton\n);return 1;}/* 将当前进程的pid更新到内核态程序中 */skel-bss-my_pid getpid();/* 加载和验证内核态程序 */err minimal_bpf__load(skel);if (err) {fprintf(stderr, Failed to load and verify BPF skeleton\n);goto cleanup;}/* 将内核态程序附着到挂载点 */err minimal_bpf__attach(skel);if (err) {fprintf(stderr, Failed to attach BPF skeleton\n);goto cleanup;}// 打印一行提示可以通过查看/sys/kernel/debug/tracing/trace_pipe文件内容查看内核程序的输出printf(Successfully started! Please run sudo cat /sys/kernel/debug/tracing/trace_pipe to see output of the BPF programs.\n);for (;;) {/* 执行下面的fprintf会调用write系统调用就会触发内核态挂载点函数的执行 */fprintf(stderr, .);sleep(1);}cleanup:minimal_bpf__destroy(skel);return -err;
}对上述代码进行编译
make minimal会在当前路径下生成minimal二进制执行minimal就可以看到效果此时内核态二进制代码也在minimal二进制中。
6 libbpf-boostrap中示例的编译过程
在执行make minimal时会看到整体的编译过程
创建本地的编译目录.output编译libbpf编译libbpf-bootstrap/libbpf并将中间文件和最终生成.a文件放到.output/libbpf中编译bpftool编译libbpf-bootstrap/bpftool将minimal.bpf.c编译为minimal.bpf.o使用bpftool工具将minimal.bpf.o转换成minimal.skel.h将minimal.c编译为minimal.o生成最终的二进制minimal
但是上述命令只能看到大概的编译过程如果想看到执行编译的完整命令可以执行make minimal -e V1。
例如编译过程中会打印以下信息
... libbfd: [ OFF ]
... clang-bpf-co-re: [ on ]
... llvm: [ OFF ]
... libcap: [ OFF ]其实是因为在编译bpftool过程中对上述库进行分别测试。
最终编译minimal的代码使用了以下命令
# 使用clang将minimal.bpf.c编译为minimal.tmp.bpf.o
clang -g -O2 -target bpf -D__TARGET_ARCH_x86 \-I.output -I../../libbpf/include/uapi -I../../vmlinux/x86/ -I/root/ebpf/libbpf-bootstrap/blazesym/capi/include -idirafter /usr/bin/../lib/clang/18/include -idirafter /usr/local/include -idirafter /usr/include \-c minimal.bpf.c -o .output/minimal.tmp.bpf.o# 使用bpftool将minimal.bpf.o编译为minimal.tmp.bpf.o
/root/ebpf/libbpf-bootstrap/examples/c/.output/bpftool/bootstrap/bpftool gen object .output/minimal.bpf.o .output/minimal.tmp.bpf.o# 使用bpftool将minimal.bpf.o转换成minimal.skel.h
/root/ebpf/libbpf-bootstrap/examples/c/.output/bpftool/bootstrap/bpftool gen skeleton .output/minimal.bpf.o .output/minimal.skel.h# 使用gcc将minimal.c编译为minimal.o
cc -g -Wall -I.output -I../../libbpf/include/uapi -I../../vmlinux/x86/ -I/root/ebpf/libbpf-bootstrap/blazesym/capi/include -c minimal.c -o .output/minimal.o# 使用gcc将minimal.o编译为minimal
cc -g -Wall .output/minimal.o /root/ebpf/libbpf-bootstrap/examples/c/.output/libbpf.a -lelf -lz -o minimal在最终生成minimal二进制的过程中主要依赖三个库libbpf、libelf、libzlib其中只有libbpf是静态库另外两个都是动态库。
7 参考文档
perf-tools基于ftrace和perf开发的性能分析工具libbpf-tools基于libbpf开发的工具BPF Performance ToolsBPF性能之巅的配套代码eBPF动手实践系列三基于原生libbpf库的eBPF编程改进方案libbpf-bootstraplibbpf的脚手架项目BPF DocumentationLinux内核中的BPF文档BPF Portability and CO-REelfutils