Aller au contenu
Administration Linux medium

bpftrace : tracer le noyau Linux en une ligne

11 min de lecture

bpftrace répond en une ligne à la question « qu'est-ce que ma machine est en train de faire, là, maintenant ? ». C'est l'outil eBPF le plus accessible : vous décrivez ce que vous voulez observer dans un langage court, il produit le programme eBPF, le fait vérifier, le charge et affiche le résultat. Cette page couvre la structure du langage, les types de sondes, l'agrégation de données (compteurs, histogrammes) et les pièges, avec des sorties réelles capturées sur une VM Ubuntu 24.04 (noyau 6.8, bpftrace v0.20.2). Pour l'installation et le tout premier contact, voir écrire son premier programme eBPF.

  • Décomposer un programme bpftrace : sonde, filtre, action
  • Choisir le bon type de sonde parmi les milliers disponibles
  • Agréger des événements en compteurs et en histogrammes
  • Écrire les one-liners qui répondent aux questions de terrain
  • Savoir quand bpftrace suffit et quand il faut un autre outil

Tout programme bpftrace, du one-liner au script, suit le même schéma en trois parties :

sonde /filtre optionnel/ { action }
  • La sonde (probe) est le point de déclenchement : « quand cet événement se produit ».
  • Le filtre (entre /.../) est une condition : n'agir que si elle est vraie.
  • L'action (entre {...}) est ce qu'on exécute à chaque déclenchement.

Exemple minimal, déjà vu : afficher chaque programme lancé sur la machine. En Linux, démarrer un programme passe par l'appel système execve.

Fenêtre de terminal
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%-16s -> %s\n", comm, str(args->filename)); }'

Sortie réelle :

bash -> /usr/bin/uname
bash -> /usr/bin/id
bash -> /usr/bin/sleep

Ici la sonde est tracepoint:syscalls:sys_enter_execve, il n'y a pas de filtre, et l'action affiche deux informations. Les variables comm (nom du processus) et args->filename (argument de l'appel) sont fournies par bpftrace.

bpftrace expose des variables prêtes à l'emploi dans chaque action :

VariableContenu
commNom du processus courant (16 caractères)
pid / tidNuméro du processus / du fil d'exécution
uidIdentifiant de l'utilisateur
argsLes arguments de la sonde (ex. args->filename)
retvalLa valeur de retour (sur les sondes de sortie)
nsecsHorodatage en nanosecondes

Une sonde désigne et quand votre action se déclenche. bpftrace en expose un nombre considérable : sur la VM de test, sudo bpftrace -l en dénombre 120 223. Les grandes familles :

  • tracepoint : un point d'observation stable, garanti par les développeurs du noyau. À préférer quand il existe, car il ne change pas d'une version à l'autre. Exemple : tracepoint:syscalls:sys_enter_openat.
  • kprobe / kretprobe : l'entrée / la sortie de n'importe quelle fonction du noyau. Très puissant, mais lié aux noms internes du noyau, qui peuvent changer. Exemple : kretprobe:vfs_read.
  • uprobe / uretprobe : l'entrée / la sortie d'une fonction d'un programme en espace utilisateur, pas seulement du noyau.
  • interval / profile : un déclenchement périodique (toutes les N secondes, ou à intervalle d'échantillonnage), utile pour les résumés et le profilage.

Pour découvrir les sondes d'un domaine, filtrez la liste :

Fenêtre de terminal
sudo bpftrace -l 'tracepoint:syscalls:sys_enter_exec*'
tracepoint:syscalls:sys_enter_execve
tracepoint:syscalls:sys_enter_execveat

Afficher chaque événement devient vite illisible sous charge. La force de bpftrace, c'est d'agréger dans des maps, des variables spéciales préfixées par @. Le noyau accumule, et bpftrace n'affiche le résumé qu'à la fin.

Combien d'appels système chaque programme passe-t-il ? On indexe une map par comm et on incrémente un compteur. La sonde interval:s:5 { exit(); } arrête la mesure au bout de cinq secondes.

Fenêtre de terminal
sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); } interval:s:5 { exit(); }'

Sortie réelle (triée par bpftrace, du moins au plus fréquent) :

@[systemd-journal]: 5
@[gmain]: 6
@[multipathd]: 31
@[bpftrace]: 115
@[bash]: 3424
@[sleep]: 3584
@[cat]: 3872
@[ls]: 4736
@[id]: 7040

En une commande, vous savez quels processus sollicitent le plus le noyau. count() est une fonction d'agrégation ; le noyau maintient un compteur par clé, sans renvoyer chaque événement à l'espace utilisateur.

Pour une grandeur numérique, un histogramme vaut mille lignes. Quelle est la taille des lectures de fichiers ? On accroche la sortie de vfs_read (la fonction noyau derrière les lectures) et on range retval (le nombre d'octets lus) dans un histogramme avec hist().

Fenêtre de terminal
sudo bpftrace -e 'kretprobe:vfs_read /retval>0/ { @octets = hist(retval); } interval:s:5 { exit(); }'

Sortie réelle :

@octets:
[4, 8) 42 |@@@@ |
[8, 16) 1 | |
[16, 32) 21 |@@ |
[256, 512) 42 |@@@@ |
[512, 1K) 504 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[1K, 2K) 21 |@@ |
[2K, 4K) 84 |@@@@@@@@ |

Chaque ligne est un intervalle de tailles ([512, 1K) = de 512 à 1024 octets), suivi du nombre de lectures et d'une barre proportionnelle. On lit d'un coup d'œil que l'écrasante majorité des lectures fait entre 512 et 1024 octets. Le filtre /retval>0/ écarte les lectures vides ou en erreur.

Les deux briques (sonde + action) répondent à une question courante en diagnostic : quel processus touche à quels fichiers ? On trace l'ouverture de fichiers via openat.

Fenêtre de terminal
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%-14s %s\n", comm, str(args->filename)); }'

Sortie réelle :

bash /dev/null
cat /etc/ld.so.cache
cat /lib/x86_64-linux-gnu/libc.so.6
cat /usr/share/locale/locale.alias
cat /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache

Sans installer d'agent ni modifier cat, vous voyez chaque bibliothèque et chaque fichier qu'il ouvre. C'est le genre d'observation qui remplace des heures de strace ciblé, à l'échelle de toute la machine.

Tracer une fonction en espace utilisateur (uprobe)

Section intitulée « Tracer une fonction en espace utilisateur (uprobe) »

Jusqu'ici on a observé le noyau. Une uprobe (user probe) fait la même chose sur une fonction d'un programme en espace utilisateur : on s'accroche à une fonction précise d'un binaire, sans en avoir le code source ni le modifier. Utile pour comprendre le comportement d'une application ou d'une bibliothèque.

Prenons un petit programme qui appelle une fonction calcule() en boucle. On trace cette fonction et son premier argument (arg0) :

Fenêtre de terminal
sudo bpftrace -e 'uprobe:/tmp/prog:calcule { printf("calcule() appelee avec arg=%d\n", arg0); }'

Sortie réelle capturée sur la VM :

calcule() appelee avec arg=6
calcule() appelee avec arg=7
calcule() appelee avec arg=8
calcule() appelee avec arg=9

La sonde uprobe:/tmp/prog:calcule désigne le binaire puis la fonction. On peut aussi cibler une bibliothèque partagée (par exemple une fonction de la libc) pour observer toutes les applications qui l'utilisent. C'est le pendant, côté espace utilisateur, des kprobes du noyau.

Vous n'êtes pas obligé de tout écrire. La communauté maintient une large collection de scripts bpftrace (.bt) et l'écosystème BCC livre des dizaines d'outils packagés (execsnoop, opensnoop, biolatency, tcplife...), installés avec le paquet bpfcc-tools. Ils sont écrits par les mêmes personnes que bpftrace et couvrent la plupart des besoins courants. Reportez-vous à BCC et libbpf pour les utiliser.

bpftrace excelle en investigation ponctuelle : une question, une commande, une réponse, puis on arrête. Il n'est pas conçu pour tourner en permanence comme un service de supervision.

  • Pour du profilage continu (voir en permanence où le CPU est consommé, avec un historique), passez à un collecteur dédié comme Parca ou Pyroscope.
  • Pour corréler les événements à Kubernetes (quel pod, quel conteneur), un outil comme Inspektor Gadget est plus adapté : voir Inspektor Gadget.
  • Opération privilégiée. bpftrace charge des programmes eBPF : il exige root ou les capacités CAP_BPF et CAP_PERFMON. Réservez son usage aux administrateurs.
  • Observation, pas modification. Les sondes de traçage lisent l'état du système ; elles ne le modifient pas. Le risque n'est pas la casse, mais la fuite d'information : une sonde peut révéler des chemins de fichiers, des arguments de commande, des données sensibles. Traitez ses sorties comme des journaux sensibles.
  • Coût sous forte charge. Une sonde sur un événement très fréquent (chaque appel système) ajoute un surcoût. En production, filtrez tôt et préférez l'agrégation à l'affichage ligne à ligne.
  • kprobe n'est pas stable. Un script basé sur kprobe:vfs_read peut cesser de fonctionner si le noyau renomme la fonction. Préférez un tracepoint quand il existe.
SymptômeCause probableSolution
ERROR: Could not resolve symbolSonde kprobe sur une fonction absente du noyauVérifier le nom avec bpftrace -l, ou utiliser un tracepoint
Aucune sortieAucun événement, ou filtre trop restrictifProvoquer l'activité, relâcher le filtre
Permission deniedLancé sans privilègesudo, ou CAP_BPF + CAP_PERFMON
Sortie tronquée / caractères manquantsChaîne plus longue que le tamponAugmenter avec str(ptr, taille)
Surcoût visible sur la machineSonde sur un événement trop fréquentFiltrer, agréger, cibler un PID
  • Un programme bpftrace, c'est une sonde, un filtre optionnel, une action.
  • Les tracepoints sont stables et à préférer ; les kprobes sont plus puissantes mais liées aux noms internes du noyau.
  • L'agrégation dans des maps @ (avec count(), hist()) évite de noyer l'espace utilisateur sous les événements.
  • Un histogramme répond en une commande à « quelle est la distribution de... ».
  • bpftrace est fait pour l'investigation ponctuelle, pas pour tourner en service permanent.
  • C'est une opération privilégiée dont les sorties peuvent être sensibles.

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