Cómo usar el lenguaje c para desarrollar el programa ebpf

1. Introducción:

Recientemente, comencé a ser responsable de proyectos relacionados con ebpf, por lo que revisé los conocimientos relevantes de ebpf.

La ventaja de ebpf es evitar el doloroso trabajo de desarrollo del kernel.Si no hay ebpf, si queremos hacer una visualización del kernel, debemos desarrollar el controlador del kernel, pero si tenemos ebpf, el programa desarrollado puede estar bien aislado del kernel. , para evitar el colapso del núcleo.

Aprende a revisar blogs anteriores:

ebpf c learning_Preface--Lei's Blog-CSDN Blog

Información relacionada:

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

¿Qué conocimientos necesitas dominar para desarrollar programas ebpf?

1. Conocimiento sobre el kernel de Linux

2. Domine las herramientas comunes de ebpf

3. Familiarizado con libbpf

4. Tener ciertas habilidades de desarrollo del lenguaje C

ebpf se compone principalmente de dos partes del programa, una parte es el programa kernel, que debe compilarse en bytecode usando clang, y la otra parte es el programa de modo de usuario, que es responsable de cargar el bytecode

La compilación ebpf usa la máquina virtual clang + llvm para incrustarse en el kernel

Los puntos explorados en esta nota son:

1. ¿Dónde podemos usar ebpf para insertar pilotes?

2. ¿Cómo se comunican los programas de estado de usuario con los programas bpf?

En el seguimiento, continuaremos explorando gradualmente el contenido y explorando la aplicación de ebpf en varios campos, como la nube nativa

2. ¿Dónde podemos usar ebpf para insertar pilotes?

Aquí usamos kprobe como ejemplo:

bpftrace -l "kprobe:*"

De esta forma se pueden listar todos los puntos de inserción, por ejemplo si queremos insertar sys_write podemos utilizarlo en el siguiente programa c

SEC("kprobe/__x64_sys_write")

3. Cómo comunicarse entre el estado del usuario y los programas de estado del kernel

1. Comunicación de archivos 

2. Utilice el búfer de anillo

Veo que la comunicación ringBuffer se puede usar en libbpf, e incluso puede admitir la multiplexación de epoll. Lo estudiaré si es necesario en el futuro, porque uso principalmente la comunicación golang y ebpfc en mi trabajo, así que solo lo miro por el momento. ser.

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);

También hay ring_buffer__epoll_fd en el código, cómo usar estas API para realizar la comunicación entre el usuario y el modo kernel en el estudio de seguimiento.

3. Usa mapas

el mapa es similar a la memoria compartida

Código de caso:

Parte del programa 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;
}

Aquí, nuestro programa activa esta función cuando un programa usa una función como escribir, y luego obtenemos la identificación del proceso y la hora del sistema y las ponemos en el mapa.La definición del mapa:

struct{
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 8192);
    __type(key, pid_t);
    __type(value, __u64);
}mapTest SEC(".maps");

Después de ponerlo en el mapa, se escribirá en el archivo.

#define DEBUGFS "/sys/kernel/debug/tracing/trace"

El programa de modo de usuario seguirá leyendo este archivo y recorrerá el mapa después de leer el contenido.

Compile el programa c a bytecode:

/usr/bin/clang-14 -g -O2 -target bpf  -D__TARGET_ARCH_x86_64 -c data.c -o kernel_write.o

Programa de pieza de usuario:

Principalmente carga bytecode y lee el contenido escrito en archivos y mapas por algunos programas del kernel ebpf

#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;
}

Supongo que te gusta

Origin blog.csdn.net/qq_32783703/article/details/127563864
Recomendado
Clasificación