Instrumentation uprobe
Dans la section précédente, nous avons vu comment il est possible d’instrumenter le code du noyau Linux pour en extraire des informations. Nous allons maintenant voir comment une variante de cette instrumentation, les uprobes, peut être utilisée pour extraire des informations du code utilisateur : exécutables, bibliothèques…
Utilisation simple des uprobes
Dans l’ensemble, l’utilisation de perf probe
sur un binaire utilisateur est
très similaire à celle sur le noyau Linux. Il suffit de rajouter un paramètre
--exec
, qui s’abbrévie en -x
, pour désigner le binaire cible. Par exemple,
on peut énumérer les fonctions instrumentables avec --funcs
/-F
.
perf probe -x /bin/bash --funcs
Début de la sortie de perf probe -x /bin/bash --funcs
Si le code source de l’exécutable est disponible (ce qui est le cas pour
bash
sur srv-calcul-ambulant
), on peut afficher les lignes instrumentables
d’une fonction avec l’option --line
/-L
:
perf probe -x /bin/bash --line absolute_pathname
Si des symboles de déboguages sont disponibles, on peut aussi toujours afficher
le nom des variables accessibles par l’instrumentation avec --vars
/-V
:
perf probe -x /bin/bash --vars main
Bref, vous l’aurez compris, à un paramètre -x
près, l’utilisation de
perf probe
pour les uprobes est quasiment identique à celle pour les
kprobes. Notez juste les préconditions : pour avoir toutes les fonctionalités,
il faut avoir accès à la fois à des symboles de déboguage et au code source du
binaire, ce qui est rarement le cas par défaut et nécessite un peu de
préparation de l’administrateur.
Maintenant, puisqu’on parle d’administrateur, invitez-le justement à activer
une instrumentation au niveau de la fonction main()
de bash
avec la commande
suivante :
# Nécessite des privilèges administrateur !
sudo perf probe -x /bin/bash main argc argv[0]:string \
&& sudo chown -R root:perf /sys/kernel/tracing/events/probe_bash
Ce qui est l’occasion d’aborder quelques nouveaux points de syntaxe :
- Il est possible à
perf probe
d’accéder aux éléments d’un tableau C, par contre les pointeurschar*
ne sont pas automatiquement traités comme des chaînes de caractère, il faut explicitement le demander avec le suffixe:string
. - Pour limiter les collisions de noms, les sondes sont regroupées en groupes.
Par défaut, les kprobes appartiennent au groupe
probe
, et les uprobes à un groupe appeléprobe_xyz
où xyz est le nom du binaire instrumenté. Il est possible d’utiliser un nom de groupe personnalisé si on le souhaite, ce qui est par exemple utile si plusieurs personnes avec des droits administrateurs veulent utiliserperf probe
sur le même binaire en même temps sans se marcher sur les pieds.
Exercice : En surveillant la sonde probe_bash:main
nouvellement créée avec
perf trace
, essayez de lancer bash
quelques fois en variant le chemin
d’accès utilisé (détection automatique via PATH vs absolu…) et en ajoutant des
paramètres, et notez comment chaque nouvelle exécution de bash
est désormais
suivie par perf trace
.
Considérations spécifiques aux bibliothèques GNU
Nous l’avons vu, l’utilisation de base des uprobes est, dans l’ensemble, très similaire à celle des kprobes. Toutefois, quand on commence à les utiliser en pratique, on remarque qu’on a besoin de prendre des précautions qui ne sont pas souvent nécessaires avec le code noyau.
Pour être plus précis, lorsqu’on utilise des uprobes, il est très tentant
d’instrumenter des bibliothèques de base telles que la libc
, la libstdc++
(bibliothèque standard C++ de GCC), la libm
(implémentation des fonctions
mathématiques comme sin
, cos
…), ou la libpthread
(implémentation des
primitives de synchronisation comme les mutexes). Mais si on essaie, on se
rendra rapidement compte qu’il ne suffit pas forcément d’instrumenter la
fonction ayant le nom le plus évident (ex: memcpy
dans la libc), pour les
raisons suivantes :
- Les compilateurs tendent à inliner une partie de l’implémentation de ces fonctions au sein du programme appelant, et donc la fonction en elle-même n’est presque jamais appelée, seule des sous-fonctions utilitaires internes sont éventuellement appelées.
- Les bibliothèques GNU utilisent des techniques compliquées pour optimiser la
compatibilité binaire des programmes et le compromis portabilité/performance,
et l’analyse des binaires de
perf probe
a du mal à voir à travers ces couches d’abstraction.
La manière la plus simple de contourner ces aléas est d’utiliser un profileur CPU pour repérer le nom de la fonction qui est réellement appelée par le binaire final, et instrumenter celle-là. Mais parfois, on aura aussi la chance de disposer d’instrumentation statique USDT prête à l’emploi, ce que nous allons aborder dans la section suivante.