eBPF, écriture et exécution d'un programme simple

Par mboudemagh, 1 mars, 2025
Logo de la technologie eBPF

Introduction

Pour ce cinquième article nous allons nous attaquer à l'aspect technique d'eBPF, nous allons donc écrire notre premier programme eBPF! À première vue, cela peut sembler un peu intimidant donc nous allons simplement afficher le message "Hello world" dans notre console. Rien de plus simple!

Prérequis

Étant donné qu'initialement les programmes eBPF ont été conçus pour les systèmes Linux, nous allons exécuter notre programme dans un environnement Linux pour des raisons de simplicité, cependant sachez qu'il est aussi possible d'utiliser eBPF sur windows. Personnellement je vais travailler sur un serveur debian où j'ai des accès privilégiés, vous pouvez faire comme moi ou simplement ouvrir une machine virtuelle ou un conteneur. Tout d'abord, nous allons installer les outils dont nous avons besoin pour compiler et exécuter notre programme avec la commande suivante:

sudo apt install clang llvm libbpf-dev bpftool

Clang est le compilateur que nous allons utiliser pour compiler notre programme eBPF. LLVM quant à lui est un framework de compilation qui nous permettra de générer du bytecode eBPF. Finalement, libbpf-dev est une librairie qui nous permettra de charger notre bytecode en tant que programme eBPF dans le noyau. Comme IDE je vais simplement utiliser vscode mais vous pouvez utiliser l'éditeur de code de votre choix. La seule extension que j'utilise est C/C++ Extension pack. Bpftool quant à lui est l'outil qui nous permettra de gérer les objets ebpf et nous servira surtout lors du chargement et de l'exécution de notre programme.

Écriture du code

Tout d'abord, comme tout programme nous devons importer les librairies qui nous serons utiles.

#include <linux/bpf.h> #include <bpf/bpf_helpers.h>

#include en C est l'équivalent de import en python ou en java. Ces 2 librairies sont essentielles pour le développement de notre programme. Elles nous permettent d'utiliser des fonctions pré-fabriquées et de nous faciliter la tâche dans notre développement. Nous allons ensuite ajouter l'instruction SEC("tracepoint/syscalls/sys_enter_write")

Cette instruction sert à préciser au système que nous voulons attacher notre programme au tracepoint d'écriture standard. Ensuite, nous allons écrire notre fonction principale.

void hello_world(void *ctx) { }

Si vous n'avez jamais fait de c auparavant, il y'a de très grande chance que vous ne compreniez pas pourquoi il y'a un astérisque devant le paramètre ctx. C'est simplement un pointeur qui renvoie vers l'adresse mémoire du contexte de l'exécution. Dans ce cas je l'ai déclaré vide car nous n'en n'aurons pas besoin pour notre programme. Ensuite, nous allons y insérer l'instruction qui nous permettra d'afficher le message «Hello World!».

bpf_printk("Hello world!\n");

Cette fonction sert généralement à afficher des messages de débogages dans le noyau. Ensuite, nous ajouterons à l'extérieur de la fonction cette ligne:

char LICENSE[] SEC("license") = "GPL";

Ça nous permet de spécifier que le programme est sous licence GPL, sans ça on ne peut pas l'utiliser avec eBPF, j'ai déjà essayé pour votre information. Le code complet nous donne:

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

SEC("tracepoint/syscalls/sys_enter_write")
void hello_world(void *ctx)
{
    bpf_printk("Hello world!\n");
}
char LICENSE[] SEC("license") = "GPL";

Compilation

Ensuite, nous allons le compiler avec clang grâce à la longue commande:

sudo clang -I/usr/include/x86_64-linux-gnu/ -O2 -g -target bpf -c ebpf.c -o ebpf.o

L'option -I spécifie le répertoire d'inclusion pour les librairies que nous utilisons, cela peut varier en fonction de la version du système que vous avez. L'option -02 quant à elle spécifie le niveau d'optimisation de la compilation. Sans trop détailler le niveau 2 est le niveau le plus équilibré. L'option -g sert à générer des informations de déboggage dans le programme de sortie si nous avons besoin de les consulter ensuite avec d'autres outils. -target spécifie que nous voullons compiler un programme ebpf, -c spécifie le fichier c que nous voulons compiler et -o est le fichier de sortie (en format fichier objet)

Chargement dans le noyau

Nous allons ensuite charger le programme eBPF dans le noyau et l'attacher à un chemin spécifique grâce à la commande

sudo bpftool prog load ebpf.o /sys/fs/bpf/programme_ebpf type raw_tracepoint

Cela permet aussi de stocker le programme eBPF sur notre machine. L'option raw tracepoint sert à attacher le programme aux tracepoints du noyau sans passer par une interface spécifique.

Exécution

Afin d'exécuter notre programme, nous allons utiliser la commande:

sudo bpftool prog run pinned /sys/fs/bpf/programme_ebpf repeat 0

L'option pinned permet de spécifier que nous voulons exécuter un programme déjà présent dans le système de fichier bpf. Repeat 0 spécifie que nous ne voulons exécuter le programme qu'une seule fois. Vous pouvez bien évidemment l'incrémenter si vous le voulez.

Résultat final

Nous allons lancer la commande

sudo cat /sys/kernel/debug/tracing/trace

Elle permet de lire les informations de deboggage du noyau système. Et voilà le résultat! Alt Text

Sources

Whirl Offload: «eBPF asssembly with LLVM» - https://qmonnet.github.io/whirl-offload/2020/04/12/llvm-ebpf-asm/ Medium «BPF Development: Starting with Hello World | by MatrixOrigin | Medium» - https://medium.com/@matrixorigin-database/bpf-development-starting-with-hello-world-c309941d6b3f

Commentaires