Aller au contenu
Background Image
  1. Ebpf-Another-Types/

L’observabilité pour tous les développeurs avec les uProbes

·2075 mots·10 mins·
Joseph Ligier
Auteur
Joseph Ligier
CNCF ambassador | Kubestronaut 🐝
Sommaire
Apprenons uProbe avec eBPF et Aya - Cet article fait partie d'une série.
Partie 1: Cet article

Je débute la programmation en eBPF avec Aya. L’idée de cette série d’articles est d’apprendre un nouveau type de programme eBPF et de l’expérimenter avec le framework Rust Aya.

Aujourd’hui, nous allons nous plonger dans les uProbes et les uRetProbes : des programmes eBPF qui sondent les fonctions de l’espace utilisateur sans laisser de trace.

Vous allez voir que cela peut être très intéressant pour du profilage, du debug ou même de la rétro-ingénierie.

Si tu ne connais pas eBPF, je te conseille de lire les deux premières parties de ma série S’initier à eBPF avec Aya. Cela couvre les bases et t’aidera pour la suite de l’article.

Qu’est-ce qu’une u•Ret•Probe ?
#

En anglais, une probe peut se traduire par une sonde pour examiner ou explorer quelque chose. En eBPF, il y en a de plusieurs types : kProbe, kRetProbe, uProbe, uRetProbe et USDT.

kProbe : la sonde pour le kernel
#

Si tu consultes la documentation d’eBPF, il n’y a pas de section consacrée aux programmes de type uProbe ou uRetProbe. Mais il y en a une dédiée à la kProbe :

Kprobe documentation

La kProbe a pour but d’observer des fonctions du kernel Linux. Elle peut être considérée comme la probe parente. Toutes les autres probes sont en fait le même type de programme BPF_PROG_TYPE_KPROBE mais c’est juste le point d’attache qui va déterminer comment le programme est exécuté.

kRetProbe : retour sur la sonde kernel
#

La kRetProbe est simplement dédiée à l’observation du retour des fonctions du kernel Linux. Cette sonde permet ainsi de vérifier si l’appel de la fonction s’est bien terminé.

Nous avons vu brièvement kProbe et kRetProbe qui pourraient faire l’objet d’autres articles. Parlons maintenant des probes qui nous intéressent aujourd’hui : uProbe et uRetProbe.

uProbe : la sonde pour les utilisateurs
#

Contrairement aux kProbes qui sont dédiées à observer les fonctions du kernel Linux, les uProbes sont dédiées aux fonctions de l’espace utilisateur : User-space Probes. Par exemple, on pourrait s’en servir pour compter le nombre d’appels aux fonctions malloc et free dans un programme C.

uProbe permet également de récupérer le contenu des arguments de la fonction observée. Ainsi on pourrait regarder la quantité de mémoire allouée à chaque malloc ou vérifier que free libère réellement des bons pointeurs.

Ainsi les uProbes pourrait s’intégrer à une CI pour automatiser des vérifications de sécurité, faciliter le debogage ou aider au diagnostic mémoire.

Free Medical icons Ça peut paraître paradoxal de vouloir tracer un code utilisateur depuis l’espace noyau. Cependant cela a le mérite d’être non intrusif car il n’y a pas besoin de modifier le programme.

uRetProbe : retour sur la sonde utilisateur
#

De la même manière que la kRetProbe, uRetProbe a pour but d’étudier le retour de la fonction cible de l’espace utilisateur : User-space Return Probe. On peut donc découvrir la valeur que retourne la fonction. Cela permet ainsi de debugger ou d’observer le comportement final de la fonction.

Mais il y a un autre intérêt : en combinant les temps de l’uProbe et de l’uRetProbe, on peut récupérer la durée que met une fonction à s’exécuter assez facilement. Il est ainsi possible de profiler une fonction de son programme.

file_type_sql On pourrait, par exemple, l’utiliser pour des requêtes SQL où on identifierait les requêtes les plus longues.

Les uRetProbe et uProbes peuvent être utilisées pour débugger et comprendre un programme dont tu n’as pas le code source. Ça peut donc être un bel outil de rétro-ingénierie (reverse engineering).

Par contre, elles sont limitées aux programmes dont le langage est compilé : C/C++, Rust, Go, etc. Si on a un programme développé avec un autre langage, USDT pourrait vous convenir. Parlons-en.

USDT : le tracepoint de l’espace utilisateur
#

USDT veut dire User Statically-Defined Tracing. Comme son nom l’indique, elle est également dédiée aux programmes de l’espace utilisateur mais il faut rajouter dans le code des sondes usdt pour les utiliser. USDT est, en fait, dérivée de l’uProbe.

Par contre, elle est beaucoup plus précise que l’uProbe. En effet, la sonde uProbe est cantonnée au début de fonction alors que la sonde usdt peut être mise à n’importe quel endroit dans le code.

Voici un exemple de code Python :

def benchmark_module():
    loop = 0
    for _ in range(100000):
        pyusdt.trace_start_loop(loop)
        calculate_pi(1000)
        pyusdt.trace_stop_loop(loop)
        loop += 1

Avec ce code, on peut avoir la durée pour calculer les 1000 décimales de π.

Au moment de l’écriture, le framework Aya ne gère pas encore les programmes de type USDT.

Nous allons maintenant nous consacrer pour la suite de l’article aux uProbes et uRetProbes. Parlons d’abord un peu de leur histoire.


Origin story
#

uTrace l’ancêtre
#

Vouloir tracer des fonctions de l’espace utilisateur depuis le noyau Linux ne date pas de l’introduction d’eBPF. Par exemple, une (première ?) tentative est apparue en 2007 avec les uTraces :

Introducing utrace

Mais elles n’ont jamais été incluses dans le code principal du fait d’opposition de certains mainteneurs.

Habemus uProbe
#

Il a fallu attendre 2012 pour que le consensus finisse par arriver et les uProbes ont été introduites lors de la version 3.5 du noyau Linux :

Introducing uprobe

À l’époque, les uProbes étaient limités par rapport à celles qu’on connaît aujourd’hui.

Ainsi, elles ont ensuite été améliorées avec la version 3.14 (sortie en 2014, la même année que l’introduction d’eBPF) :

Patching uprobe

Ce patch a permis de récupérer un nombre plus important de données comme la valeur de retour d’une fonction.

Les uProbes sont alors devenues pleinement exploitables alors qu’eBPF n’était pas encore sortie. Voyons quand son intégration s’est faite.

uProbe avec eBPF
#

D’après la documentation d’eBPF, kProbe est apparue en 2015 dans la version 4.1 du noyau Linux. C’est Alexei Starovoitov, l’un des créateurs d’eBPF, qui l’a initié :

Patching kprobe eBPF

Comme une uProbe est une kProbe avec un point d’attache différent, on pouvait commencer à développer des uProbes avec eBPF à partir du 2 avril 2015.

Cependant, il fallait encore attendre que les frameworks eBPF de l’époque puissent le gérer.

Ainsi on pouvait déjà l’utiliser en 2016 avec BCC comme l’atteste le tutoriel de Brendan Gregg :

uprobe bcc eBPF tutorial

On peut voir également son issue sur GitHub datant d’octobre 2015 :

uprobe bcc issue

Pour finir, voici une petite frise chronologique de l’histoire des uProbes :

uProbe history

Free Medical icons Les uRetProbes ont été introduites de façon conjointe aux uProbes.

Maintenant qu’uProbe a plus de 10 ans d’existence, on peut se poser une question bien légitime : est-elle encore utilisée et par quel projet ?


Quels projets utilisent u•Ret•Probe ?
#

Pour défier le lieu commun : “eBPF c’est utilisé que par 3 grosses boîtes”, j’ai fait une petite recherche des outils qui utilisaient réellement les uProbes et que donc vous l’utilisiez peut-être sans le savoir…

Pixie : where is my mind?
#

Le projet Pixie utilise uProbe notamment pour tracer les connexions TLS :

uProbe Pixie

D’ailleurs il y a un article de Douglas Mendez pour capturer le traffic HTTPs avec Aya.

Le Projet Pixie est un outil d’observabilité pour les applications qui sont sur Kubernetes.

Parca : l’hiver vient !
#

Le projet Parca utilise également les uProbes.

uProbe Parca

Le projet Parca est un outil de profilage “en continue” c’est à dire de façon systématique.

Inspektor Gadget : hé là, qui va là?
#

Le projet Inspektor Gadget a créé des outils basés sur des uProbes et sur des sondes USDT depuis 2024 :

uProbe Inspektor Gadget

Le projet Inspektor Gadget fournit des outils (des gadgets) et un framework pour collecter les données et l’inspection du système sur Kubernetes.

Bonus Track
#

Pour finir la présentation, je vous partage quelques liens bien sympathiques que j’ai trouvé lors de mes recherches sur les uProbes :

  • Utilisation des uprobes sans eBPF par Brendan Gregg en 2015 :

Brendan Gregg blog

Julia Evans zine

  • Si les uProbes ne vous conviennent pas, peut-être que les bpftimes d’Eunomia peuvent vous intéresser :

bpftime: Userspace eBPF runtime for Observability, Network & General extensions Framework

Maintenant qu’on a présenté uProbe et uRetProbe, voyons comment débuter son développement avec Aya.


Comment débuter son programme Aya ?
#

Quand on démarre le développement d’un nouveau programme eBPF, la première difficulté est de réussir à le démarrer. Pour cela, il a besoin d’un événement déclencheur (event-driven). Dans cet épisode, cet événement sera donc le passage d’une uProbe ou d’une uRetProbe dans le noyau Linux.

Aya nous facilite la tâche. Quand on lance la commande :

cargo generate https://github.com/aya-rs/aya-template

Tu devras répondre à deux questions importantes qui permettront de définir cet événement :

🤷   Target to attach the (u|uret)probe? (e.g libc):
🤷   Function name to attach the (u|uret)probe? (e.g getaddrinfo):

Voyons comment y répondre.

Cible pour attacher l’u•Ret•Probe
#

La première question demande le nom d’une bibliothèque (comme la libc) ou d’un binaire. La question aurait pu être posée autrement : quel fichier tu veux debugger ou tracer ?

Il faut voir ça comme un filtre :

  • Si tu choisis libc, le programme eBPF ne pourra démarrer que si un programme de la libc est exécuté
  • Si tu choisis un binaire, il ne pourra démarrer que si le binaire est exécuté.

Mais cela n’est pas suffisant pour démarrer le programme eBPF. Il faut être plus précis : donner le nom d’une fonction.

Nom de la fonction pour attacher l’u•Ret•Probe
#

La seconde question demande ainsi la fonction du binaire ou de la bibliothèque que tu veux débugger.

Par exemple :

  • si tu choisis le nom d’une fonction d’un programme C, le programme eBPF sera lancé à chaque fois qu’il passe par cette fonction.
  • si tu choisis une fonction de la libc, il ne sera lancé lorsqu’un programme appellera cette fonction de la libc.

Si cela vous parait un peu trop théorique, nous allons finir le chapitre en parlant d’un outil bien sympathique qui va nous permettre d’illustrer cela.

S’initier à eBPF avec bpftrace
#

Le projet bpftrace permet de créer rapidement la plupart des types de programme eBPF dédiés aux tracings dont notamment uProbe et uRetProbe mais également USDT, kProbe et kRetProbe (Voir la prise en charge ici).

probe summary

N’hésitez pas à l’installer, il est probablement packagé pour votre distribution Linux favorite.

Le projet s’est fortement inspiré de DTrace, un outil de tracing créé à l’origine pour les unix comme Solaris, FreeBSD ou NetBSD au début des années 2000.

Trève de bavardage, prenons un exemple :

sudo bpftrace -e \
'uretprobe:/bin/bash:readline { printf("%s\n", str(retval)); }'

Que veut dire cela ?

  • uretprobe : le type de programme eBPF
  • /bin/bash : le binaire cible
  • readline : le nom de la fonction
  • { printf("%s\n", str(retval)); } : le code du programme bpftrace (il affiche la valeur retour de la fonction)

Cette commande crée ainsi un programme eBPF de type uRetProbe avec comme point d’attache la fonction readline du binaire bash.

Si vous avez vraiment lancé la commande, vous allez voir que cette création est quasi immédiate ! Vérifions qu’il fonctionne bien.

Démarrez un autre terminal et lancez quelques commandes de ton choix. Voici un exemple de ce que vous pourrez voir sur le terminal bpftrace :

Attaching 1 probe...
ls -lrth
hello
man woman

Vous voyez toutes les commandes que vous avez tapé sur le terminal !

bpftrace peut ainsi être un bon moyen de prototyper un programme uProbe ou uRetProbe avant de le générer avec Aya.

Et si je veux observer une autre fonction que readline dans le programme bash ? Comment faire ? Le premier reflexe serait d’aller dans le code de bash et de chercher une autre fonction mais il y a plus simple et plus sûr :

bpftrace -l 'uretprobe:/bin/bash:*'

Cette commande va te lister toutes les fonctions disponibles dans bash.

On peut ainsi voir qu’on peut débugger 1670 fonctions dans bash : bpftrace -l 'uretprobe:/bin/bash:*' | wc -l

Ainsi bpftrace va nous permetre pour la suite de vérifier la faisabilité avant de créer le programme en Rust avec Aya.


Dans cet épisode, on a vu les bases des uProbes et des uRetProbes : à quoi elles servent, leur histoire, qui les utilise et comment trouver le bon point d’accroche. Nous avons également vu bpftrace, un outil qui permet de créer des probes rapidement.

Nous allons maintenant passer à la pratique dans l’épisode suivant : on va créer un petit programme Go et on va le faire réagir avec des programmes eBPF de type uProbe et uRetProbe avec Aya.

Apprenons uProbe avec eBPF et Aya - Cet article fait partie d'une série.
Partie 1: Cet article