如何使用c语言开发ebpf程序

1、前言:

最近开始陆续负责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在云原生等各个领域的应用

2、我们使用ebpf 可以在哪里插桩

这里单用kprobe 为例子:

bpftrace -l "kprobe:*"

这样就可以列出所有插桩点,比如我们要对sys_write进行插装就可以在后面的c程序中

SEC("kprobe/__x64_sys_write")

3、用户态和内核态程序如何通信

1、文件通信 

2、使用ringBuffer

我看到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实现用户和内核态通信的

3、使用map

map类似与共享内存

案例代码:

kernel ebpf 程序部分:

// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2020 Facebook */
#include <unistd.h>
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

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 <bpf/bpf.h>
#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;
}

猜你喜欢

转载自blog.csdn.net/qq_32783703/article/details/127563864
今日推荐