最近开始陆续负责ebpf相关的项目,于是对ebpf相关知识内容进行复习。
ebpf的优势是避免了痛苦的kernel开发工作,如果没有ebpf之前,我们如果想做内核可视化,必须要开发kernel的驱动,但是如果我们有了ebpf,开发的程序就可以很好的和kernel隔离开,避免了kernel的崩坏。
学习回顾之前的博客:
ebpf c 学习_序冢--磊的博客-CSDN博客
相关查阅资料:
https://ebpf.io/summit-2020-slides/eBPF_Summit_2020-Lightning-Lorenzo_Fontana-Debugging_the_BPF_Virtual_Machine.pdf
https://qmo.fr/docs/talk_20200202_debugging_ebpf.pdf
开发ebpf程序你需要掌握哪些知识
1、linux 内核相关的知识
2、掌握常用的ebpf 工具
3、熟悉libbpf
4、有一定c语言开发功底
ebpf 主要有两部分程序组成,一部分是kernel程序,需要使用clang 编译成字节码,另一部分是用户态程序,负责加载字节码
ebpf 编译采用clang + llvm虚拟机嵌入到内核中
本篇笔记探究的点为:
1、我们使用ebpf 可以在哪里插桩
2、用户态程序和bpf程序是如何通信的
后续会继续对内容进行逐步探索,探索ebpf在云原生等各个领域的应用
这里单用kprobe 为例子:
bpftrace -l "kprobe:*"
这样就可以列出所有插桩点,比如我们要对sys_write进行插装就可以在后面的c程序中
SEC("kprobe/__x64_sys_write")
我看到libbpf里可以使用ringBuffer 通信,甚至可能还支持epoll多路复用,日后有需要还会研究,因为工作中主要使用golang 和 ebpfc 通信,所以暂时只看个大概
LIBBPF_API struct ring_buffer *
ring_buffer__new(int map_fd, ring_buffer_sample_fn sample_cb, void *ctx,
const struct ring_buffer_opts *opts);
LIBBPF_API void ring_buffer__free(struct ring_buffer *rb);
LIBBPF_API int ring_buffer__add(struct ring_buffer *rb, int map_fd,
ring_buffer_sample_fn sample_cb, void *ctx);
LIBBPF_API int ring_buffer__poll(struct ring_buffer *rb, int timeout_ms);
LIBBPF_API int ring_buffer__consume(struct ring_buffer *rb);
LIBBPF_API int ring_buffer__epoll_fd(const struct ring_buffer *rb);
代码里还有ring_buffer__epoll_fd,后续研究下如何使用这些api实现用户和内核态通信的
map类似与共享内存
案例代码:
kernel ebpf 程序部分:
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2020 Facebook */
#include
#include
#include
char LICENSE[] SEC("license") = "Dual BSD/GPL";
int my_pid = 0;
struct{
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 8192);
__type(key, pid_t);
__type(value, __u64);
}mapTest SEC(".maps");
SEC("kprobe/__x64_sys_write")
int handle_tp(void *ctx)
{
pid_t pid = bpf_get_current_pid_tgid() >> 32;
__u64 ts = bpf_ktime_get_ns();
int ret = bpf_map_update_elem(&mapTest, &pid, &ts, BPF_ANY);
char msg[] = "hello:%d;ret:%d;pidpr:%p\n";
bpf_trace_printk(msg, sizeof(msg), pid, ret, &pid
);
return 0;
}
这里我们程序是当有程序使用write一类的函数就会触发本函数,然后我们拿到进程id和系统时间,放到map里,map定义:
struct{
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 8192);
__type(key, pid_t);
__type(value, __u64);
}mapTest SEC(".maps");
放到map 以后会写到文件里
#define DEBUGFS "/sys/kernel/debug/tracing/trace"
用户态程序会去一直读这个文件,读到内容后去遍历map
将c程序编译为字节码:
/usr/bin/clang-14 -g -O2 -target bpf -D__TARGET_ARCH_x86_64 -c data.c -o kernel_write.o
用户部分程序:
主要是加载字节码,以及读ebpf kernel部分程序写入文件和map里的内容
#include
#include "common.h"
/* SPDX-License-Identifier: GPL-2.0 */
static const char *__doc__ = "Simple XDP prog doing XDP_PASS\n";
#define DEBUGFS "/sys/kernel/debug/tracing/"
static void bump_memlock_rlimit(void)
{
struct rlimit rlim_new = {
.rlim_cur = RLIM_INFINITY,
.rlim_max = RLIM_INFINITY,
};
if (setrlimit(RLIMIT_MEMLOCK, &rlim_new)) {
fprintf(stderr, "Failed to increase RLIMIT_MEMLOCK limit!%s\n", strerror(errno));
exit(1);
}
}
void read_trace_pipe(struct bpf_object *obj)
{
int trace_fd;
trace_fd = open(DEBUGFS "trace_pipe", O_RDONLY, 0);
printf("%s\n", DEBUGFS "trace_pipe");
if (trace_fd < 0) {
printf("%s\n", strerror(errno));
return;
}
struct bpf_map * map = bpf_object__find_map_by_name(obj, "mapTest");
if (!map) {
return;
}
int map_fd = bpf_map__fd(map);
pid_t* look_key = NULL;
pid_t next_key = 0;
pid_t value = 0;
while (1) {
static char buf[4096];
ssize_t sz;
sz = read(trace_fd, buf, sizeof(buf) - 1);
if (sz > 0) {
buf[sz] = 0;
puts(buf);
while (bpf_map_get_next_key(map_fd, look_key, &next_key) != -1) {
printf("%d\n", next_key);
if (look_key != NULL) {
printf("look:%d\n", *look_key);
}
look_key = &next_key;
}
sleep(4);
}
}
if (map_fd < 0) {
return;
}
// pid_t pid = getpid();
// printf("true pid:%d\n", pid);
// unsigned long long value1 = 10;
// int nn = bpf_map_update_elem(map_fd, &pid, (const void *) (&value1), BPF_ANY);
// printf("nn:%d\n", nn);
// int result = bpf_map_lookup_elem(map_fd, (const void *) &pid, (void *) &value1);
// printf("pid:%d\n", pid);
}
int main(int argc, char **argv)
{
char msg[255];
bump_memlock_rlimit();
struct bpf_object * obj = bpf_object__open_file("./kernel_write.o", NULL);
if (libbpf_get_error(obj)) {
fprintf(stderr, "open object file error!\n");
return -1;
}
struct bpf_map * prog = bpf_object__find_program_by_name(obj, "handle_tp");
if (!prog) {
fprintf(stderr, "ERROR: finding a prog in obj file failed\n");
return -1;
}
int ret = bpf_object__load(obj);
if (ret != 0) {
fprintf(stderr, "load object file error!%s\n", libbpf_strerror(errno, msg, sizeof(msg)));
return -1;
}
struct bpf_link* link = bpf_program__attach(prog);
if (libbpf_get_error(link)) {
fprintf(stderr, "ERROR: bpf_program__attach failed\n");
return -1;
}
read_trace_pipe(obj);
return 0;
}