Aller au contenu
Administration Linux medium

CO-RE et BTF : rendre un programme eBPF portable

8 min de lecture

CO-RE permet de compiler un programme eBPF une seule fois et de le charger sur des noyaux différents, sans le recompiler sur chaque machine. C'est ce qui a fait passer eBPF du bricolage fragile à l'outil de production. Cette page explique le problème que CO-RE résout, le rôle de BTF (l'information de types du noyau qui rend tout cela possible), et le montre sur une machine réelle : le BTF présent, les types du noyau exposés, un vmlinux.h généré. Public : développeur eBPF visant un déploiement sur un parc hétérogène. Sorties capturées sur une VM Ubuntu 24.04 (noyau 6.8).

  • Comprendre pourquoi un programme eBPF cassait en changeant de noyau
  • Définir BTF et son rôle dans la portabilité
  • Expliquer le principe de CO-RE : compiler une fois, adapter au chargement
  • Vérifier que votre noyau expose le BTF nécessaire
  • Situer CO-RE face à l'ancienne approche par recompilation

Un programme eBPF lit souvent des structures de données du noyau : une tâche, un fichier ouvert, un paquet réseau. Or l'agencement de ces structures en mémoire, l'offset (la position, en octets) de chaque champ, change d'une version du noyau à l'autre. Un champ qui se trouve à l'octet 40 sur un noyau peut être à l'octet 48 sur le suivant.

Un programme compilé pour un noyau précis lit donc les champs aux mauvais endroits sur un autre noyau : au mieux des valeurs absurdes, au pire un refus de chargement. Le programme est collé à son noyau de compilation.

L'ancienne réponse, celle de BCC, était de recompiler sur chaque machine, contre les en-têtes du noyau local. Cela fonctionne, mais impose un compilateur et les en-têtes du noyau sur toutes les cibles, alourdit les images, et coûte du temps et de la mémoire à chaque démarrage. Sur un parc de serveurs, c'est une contrainte lourde.

La solution repose sur BTF (BPF Type Format), une description compacte de tous les types du noyau : chaque structure, chaque champ, chaque taille. Les noyaux récents intègrent cette carte et l'exposent en un point unique. Sur la VM de test, elle est bien là :

Fenêtre de terminal
ls -l /sys/kernel/btf/vmlinux
-r--r--r-- 1 root root 6096599 Jul 14 11:47 /sys/kernel/btf/vmlinux

Ce fichier de 6 Mo décrit l'intégralité des types du noyau en cours d'exécution. On peut le lire avec bpftool, qui le restitue en C :

Fenêtre de terminal
sudo bpftool btf dump file /sys/kernel/btf/vmlinux format c | head -20
#ifndef __VMLINUX_H__
#define __VMLINUX_H__
...
typedef unsigned char __u8;
typedef short unsigned int __u16;
typedef unsigned int __u32;
typedef long long unsigned int __u64;

vmlinux.h : les structures du noyau sans ses en-têtes

Section intitulée « vmlinux.h : les structures du noyau sans ses en-têtes »

De ce BTF, on génère un unique fichier d'en-tête, vmlinux.h, qui contient toutes les définitions de types du noyau courant. Le programme eBPF l'inclut, et n'a plus besoin des en-têtes du noyau installés sur la machine de compilation.

Fenêtre de terminal
sudo bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
wc -l vmlinux.h
150631 vmlinux.h

Plus de 150 000 lignes décrivant le noyau, produites depuis la machine elle-même. C'est la première brique de la portabilité : compiler contre une description exacte du noyau, sans dépendre de paquets d'en-têtes.

CO-RE : compiler une fois, s'adapter au chargement

Section intitulée « CO-RE : compiler une fois, s'adapter au chargement »

BTF résout la description ; CO-RE (Compile Once Run Everywhere) résout l'adaptation. L'idée est élégante.

  1. À la compilation, le programme n'écrit pas « lis le champ à l'octet 40 ». Grâce à l'attribut preserve_access_index (visible dans l'en-tête généré plus haut), il note « lis le champ tel-champ de telle structure », en laissant l'offset à déterminer plus tard. Ces notes sont des relocations.

  2. Le binaire est distribué tel quel, une seule fois compilé, embarquant ses relocations.

  3. Au chargement, sur la machine cible, la bibliothèque libbpf lit le BTF du noyau local, retrouve l'offset réel de chaque champ demandé, et réécrit les accès en conséquence avant de passer le programme au vérificateur.

Résultat : le même binaire lit les bons champs sur un noyau 5.15 comme sur un noyau 6.8, parce que les offsets sont résolus au dernier moment, machine par machine. Plus de compilateur sur les cibles, plus de recompilation au démarrage.

BCC (recompilation)libbpf + CO-RE
Compilationsur chaque machine cibleune seule fois
Dépendances sur la cibleclang + en-têtes du noyauaucune (juste le BTF du noyau)
Démarragelent (compile à chaque fois)rapide (binaire prêt)
Taille de l'outillourdelégère
Portabilitépar recompilationpar relocation au chargement

Cela ne condamne pas BCC : il reste excellent pour apprendre et prototyper, parce qu'écrire un programme dans une chaîne Python et le lancer est immédiat. Mais pour distribuer un outil eBPF à un parc de machines, CO-RE est la voie. Le guide BCC et libbpf compare les deux en pratique.

  • Pas de BTF, pas de CO-RE. Sur un vieux noyau sans /sys/kernel/btf/vmlinux, il faut se rabattre sur la recompilation, ou fournir un BTF externe (des projets distribuent des BTF pour les noyaux qui n'en embarquent pas).
  • CO-RE relocalise les champs, pas la logique. Si un champ disparaît ou change de sens entre deux noyaux, CO-RE ne l'invente pas : le programme doit gérer cette variabilité explicitement (libbpf offre des tests de présence de champ).
  • vmlinux.h reflète le noyau qui l'a généré. Le régénérer sur un noyau très différent donne un fichier différent ; c'est normal, il décrit un noyau donné. La portabilité vient de la relocation au chargement, pas du vmlinux.h de compilation.
  • Les offsets des champs du noyau changent entre versions : un programme compilé en dur est collé à son noyau.
  • BTF est la description de tous les types du noyau, exposée dans /sys/kernel/btf/vmlinux sur les noyaux récents.
  • vmlinux.h, généré depuis le BTF, remplace les en-têtes du noyau à la compilation.
  • CO-RE compile le programme une fois avec des relocations, que libbpf résout au chargement en lisant le BTF local.
  • Le même binaire tourne alors sur des noyaux différents, sans compilateur ni en-têtes sur les cibles.
  • BCC reste idéal pour apprendre ; libbpf + CO-RE est la voie pour distribuer un outil.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn