Les systèmes Linux enfouis suivis visuellement à la

Transcription

Les systèmes Linux enfouis suivis visuellement à la
A P P L I C A T I O N
Débogage logiciel
Les systèmes Linux enfouis
suivis visuellement à la trace…
logicielle
Dans le cadre du développement de systèmes enfouis, le débogage logiciel s’avère
parfois une étape laborieuse, imprévisible et source de nombreux défis. Une fois
l’exécution d’un programme en errance détectée, plusieurs questions se posent. Comment
l’application a-t-elle pu se retrouver dans cet état ? Par quelle combinaison d’entrées
à un instant donné l’erreur a-t-elle vu le jour, et pourquoi ? A ce niveau, une trace logicielle
peut fournir une réponse adaptée.
L
a technologie de traçage
d’une application embarquée
implique d’enregistrer le
comportement du logiciel
lors de son exécution, ce qui permet
d’analyser ensuite les données de
suivi recueillies. Le traçage est le plus
souvent utilisé lors de la phase de
développement, mais il peut également être activé pour une utilisation
en production, de manière à être
continuellement actif avec enregistrement du comportement de l’application et capture des erreurs en phase
de post-déploiement. Le traçage en
production peut s’avérer aussi une
technique efficace pour détecter des
erreurs qui se manifestent rarement
et qui sont donc difficiles à reproduire avec un débogueur classique.
Il peut s’agir de situations où le système répond par exemple plus lentement que prévu, donne une sortie
incorrecte, se fige ou se bloque complètement.
Le traçage peut être réalisé soit au
niveau matériel (dans le processeur),
soit au niveau logiciel. Le traçage au
niveau matériel génère un historique
détaillé d’exécution des instructions,
sans perturber le système analysé.
L’inconvénient est que le traçage
matériel nécessite un processeur, une
carte et un débogueur prenant explicitement en charge cette fonctionnalité. Ainsi, la prise en charge du traçage matériel doit être envisagée très
tôt, dès la sélection de la plate-forme
matérielle du projet. En outre, la plupart des solutions de traçage matériel
ne permettent pas l’enregistrement
de données, mais font uniquement
34 / L’EMBARQUÉ / N°6 / 2014
AUTEUR
Dr. Johan Kraft,
PDG et
fondateur
de Percepio,
société
suédoise créée
en 2009
(les outils
de Percepio
sont distribués
en France par
Antycip).
du contrôle de flux, autrement dit du
code exécuté.
Le traçage par logiciel, de son côté,
se concentre sur des événements
sélectionnés, tels que les appels du
système d’exploitation, les routines
d’interruption ou les mises à jour de
variables importantes. Cela ne nécessite pas de matériel particulier et peut
même se déployer sur des produits
embarqués, un peu comme un enregistreur de vol de type « boîte noire »
utilisé dans l’aviation. En outre, le
traçage logiciel permet de stocker les
données pertinentes avec les événements, tels que les paramètres des
appels système.
Revers de la médaille, le traçage logiciel impose l’utilisation du processeur et de la mémoire RAM du système cible pour stocker les
événements. Cependant, étant
donné que le stockage de chaque
événement ne prend généralement
que quelques microsecondes, un
système de traçage logiciel s›exécute
généralement à environ 99 % de la
vitesse normale. En outre, le temps
de processeur supplémentaire utilisé
par le traçage est souvent compensé
par une meilleure optimisation du
logiciel.
Un autre problème lié au traçage
logiciel est ce que l’on appelle « l’effet de sonde », i.e. l’impact théorique
sur le comportement du logiciel en
raison de l’impact de synchronisation introduit par le traçage logiciel.
Cependant, ces effets de synchronisation sont limités et d’un impact
comparable à celui des modifications de base couramment apportées
à un logiciel. Il est cependant possible d’éliminer complètement
cet effet intrusif en maintenant l’enregistrement actif en permanence,
c’est-à-dire, même dans le code de
production. De cette façon, l’enregistrement de la trace devient partie
intégrante du système intégré testé.
Cette approche présente l’avantage
que les traces peuvent être automatiquement sauvegardées par le code
de gestion d’erreurs, ce qui peut
grandement faciliter l’analyse
post-mortem.
Le traçage est particulièrement
important pour les systèmes qui
VOIR LES TRACES…
n Tracealyzer est une famille
d’outils de visualisation de traces
développée par Percepio qui
fournit un large éventail d’outils
graphiques pour faciliter l’analyse
de traces logicielles. Rappelons
que le traçage constitue un outil
puissant pour l’analyse des systèmes logiciels multithreads.
Sous Linux, il est activé par
LTTng, une solution open source.
Conçu pour visualiser les données
de trace LTTng au moyen de
nombreuses vues graphiques
interconnectées, Tracealyzer
pour Linux est disponible pour
plusieurs systèmes d’exploitation
enfouis, y compris Linux, VxWorks,
FreeRTOS, SafeRTOS, Micrium
µC/OS-III et RTXC Quadros. Tracealyzer pour Linux est conçu pour
visualiser les données de trace
LTTng et prend en charge
la v2.x LTTng actuelle et l’ancienne
version de LTTng (par exemple,
celle qui figure dans Wind River
Linux 5). La visualisation Tracealyzer est développée sous Microsoft.
NET. La version Tracealyzer v2.7
fonctionne également sous Linux
via Mono (http://mono-project.org),
une mise en œuvre alternative en
open source de .NET.
Débogage logiciel
intègrent un système d’exploitation.
Un élément central des systèmes
d’exploitation est en effet le multithreading qui rend possible l’exécution de plusieurs programmes
(threads) sur un seul cœur de processeur en basculant rapidement d’un
contexte d’exécution à l’autre. Or,
le multithreading est très pratique
pour les logiciels enfouis où plusieurs activités périodiques doivent
fonctionner à des vitesses différentes,
dans un système de contrôle par
exemple, ou lorsque des fonctions
critiques temps réel doivent être activées sur certains événements, prenant le pas sur d’autres activités
moins urgentes. Mais cette technologie de multithreading rend le comportement des logiciels plus complexe, et fait que le développeur
contrôle moins bien le comportement lors de l’exécution de l’application, cette dernière étant préemptée par le système d’exploitation.
Traçage des systèmes
Linux : l’aide de LTTng
LTTng (Linux Trace Toolkit Next
Generation, http://lttng.org) est
actuellement la solution de pointe
pour le traçage logiciel sous Linux.
LTTng est un programme open
source pris en charge par la plupart
des distributions Linux et par Yocto,
le système de mise au point (build)
très couramment employé pour
Linux embarqué. Très efficace, LTTng
a fait ses preuves à l’utilisation et est
compatible avec les noyaux Linux
dès la version 2.6.32. Les noyaux à
partir de la v2.6.38 sont en outre pris
en charge sans aucune modification
du noyau.
LTTng est basé sur les « tracepoints »,
espaces réservés aux appels de fonction qui sont inactifs par défaut. Un
tracepoint inactif a un impact minime
sur les performances, de quelques
cycles d’horloge seulement. Lorsque
LTTng est activé, il relie les tracepoints à une fonction LTTng interne
qui stocke l’événement dans une
mémoire Ram tampon.
Les données de trace dans la
mémoire Ram peuvent être envoyées
en permanence vers le disque ou
déchargées vers un autre système via
une connexion réseau. Le déchargement est géré par des threads qui
s’exécutent dans l’espace utilisateur.
Une autre possibilité est de conserver les données de trace dans la
A P P L I C A T I O N
mémoire Ram en utilisant une
mémoire tampon circulaire, à savoir,
en écrasant les événements antérieurs lorsque la mémoire tampon est
pleine. Dans ce mode, un instantané
contenant les derniers événements
est enregistré à la demande.
LTTng fournit deux enregistreurs de
trace. Le traceur de noyau enregistre
la planification des threads, les
appels système, les IRQ, la gestion
de la mémoire, ainsi que d’autres
activités au niveau du noyau, en utilisant les tracepoints existants dans
le noyau Linux. Le traceur de l’espace utilisateur (LTTng-UST) permet
de générer des événements personnalisés à partir du code de l’espace
utilisateur, c’est-à-dire par l’ajout de
nouveaux tracepoints.
Bien que LTTng soit basé sur la
notion d’instrumentation logicielle,
conséquences sur les liaisons dynamiques et impose que l’application
appelle la fonction de wrapper au
lieu de la fonction d’origine. La fonction de wrapper enregistre l’événement en utilisant le ou les tracepoint(s), puis appelle la fonction
d’origine. Ainsi, le wrapping de fonction est complètement transparent
pour le code d’application, sans
aucun besoin de recompilation.
Lors du premier appel d’une fonction
wrapper, celle-ci recherche l’adresse
de la fonction d’origine et la stocke
dans un pointeur de fonction pour
utilisation dans les appels ultérieurs.
Un outil pour visualiser
et analyser les traces LTTng
LTTng renvoie les enregistrements de
traces dans un format ouvert appelé
Common Trace Format. S’agissant
1 TRACE DES APPELS DE FONCTIONS
On voit ici la trace des appels de fonctions dans un Linux embarqué à l'aide des fonctions
wrapper et LD_PRELOAD.
il ne nécessite pas de recompilation
du code source pour la cible. Le
noyau contient déjà des tracepoints
aux endroits stratégiques, et en utilisant une autre caractéristique de
Linux, il est possible de tracer les
appels de fonctions de l’espace utilisateur sélectionné sans modifier le
code source. Cela se fait par la création d’un fichier d’objet partagé avec
fonctions d’encapsulation (figure 1)
contenant des tracepoints. Le fichier
d’objet partagé est ensuite spécifié
dans LD_PRELOAD lors du lancement de l’application. Cela a des
d’un format binaire, un outil est
nécessaire pour l’analyse. L’outil
Babeltrace de LTTng peut convertir
les données de trace vers des fichiers
texte, mais il est difficile d’avoir une
vision d’ensemble à partir de grandes
quantités de données de trace au format texte. Un outil de visualisation
facilite grandement l’analyse puisque
le cerveau humain est beaucoup plus
doué pour repérer les tendances
dans des images que dans des données au format texte. C’est l’objet de
l’outil Tracealyzer, développé par
Percepio. La vue de trace principale
L’EMBARQUÉ / N°6 / 2014 /
35
A P P L I C A T I O N
2 VUE PRINCIPALE DE TRACEALYZER
Dans cet écran, on visualise l’exécution de threads et les appels système.
dans Tracealyzer montre l’exécution
des threads le long d’une ligne de
temps verticale, avec différents événements (par exemple, des appels
système) présentés au moyen d’étiquettes avec des codes de couleur
(figure 2). Les étiquettes peuvent être
filtrées de plusieurs manières et leur
placement est automatiquement
ajusté pour éviter les chevauchements. La couleur de fond des étiquettes indique l’état et le type
d’opération. Les étiquettes rouges par
exemple montrent les appels système
qui bloquent le thread appelant, tandis que les étiquettes vertes indiquent
où les appels système bloquants
retournent à l’appelant. Des événements d’application personnalisés du
traceur de l’espace utilisateur (LTTngUST) peuvent être configurés pour
apparaître soit comme des appels de
service (par exemple, malloc), soit
comme des « événements utilisateur », c’est-à-dire des messages de
débogage génériques (étiquettes
jaunes).
Tracealyzer va dans le même temps
au-delà d’un afficheur de base. Il
comprend et met en évidence les
dépendances entre des événements
36 / L’EMBARQUÉ / N°6 / 2014
connexes dans les données de trace,
par exemple en envoyant et en recevant un signal de sémaphore. Ce qui
facilite la compréhension du comportement du système d’exploitation,
Débogage logiciel
et explicite notamment pourquoi
certains threads sont bloqués et
d’autres déclenchés.
Un exemple est montré dans la
figure 2, où un appel « en écriture »
bloquant est mis en évidence. Cet
appel génère deux événements
LTTng, lorsque l’appel commence
(événement d’entrée) et lorsque l’appel retourne (événement de sortie).
Puisque l’appel a bloqué le thread, les
deux événements sont séparés par les
commutateurs de contexte et d’autres
événements. Tracealyzer comprend
que ces événements sont liés et met
en évidence les deux événements
(contour bleu) lorsque l’un est sélectionné. L’événement d’entrée
(« write(FD-1) blocks ») indique que le
blocage du thread appelant « demo.
out: 5133 » a été causé par une opération d’écriture sur FD-1, c’est-à-dire
le Descripteur de Fichier 1, qui est la
Sortie Standard. Le thread est devenu
prêt à s’exécuter presque immédiatement (« Actor Ready: demo.out:
5133 »), mais l’exécution n’a pas
repris jusqu’à 69 µs plus tard
(« write(FD-1) returns after 69 µs »).
La vue principale est prise en charge
par plus de 20 autres vues graphiques
avec d’autres perspectives de la
trace, comme par exemple un graphique d’utilisation du CPU montrant la charge système totale et
chaque thread au fil du temps.
D’autres vues affichent aussi les sta-
3 DÉPENDANCES DE COMMUNICATION ENTRE LES THREADS
Ce flot montre les dépendances de communication entre les threads à travers
les objets du noyau.
A P P L I C A T I O N
Débogage logiciel
tistiques de synchronisation des
threads, le blocage du noyau, l’intensité de la planification, la communication interprocessus et les dépendances de communication entre les
threads (figure 3). Etant donné
qu’une trace contient souvent des
quantités importantes de scénarios
répétitifs présentant un intérêt
moindre, les nombreuses vues fournies par Tracealyzer offrent différentes perspectives, différents points
de vue, ce qui facilite le repérage de
parties intéressantes, par exemple
lorsqu’un appel système échoue ou
lorsqu’un thread prend plus de temps
que la normale pour se terminer.
Les événements de l’application sont
présentés sous forme d’étiquettes
jaunes dans la vue principale (événements utilisateur), mais peuvent
également s’afficher dans une fenêtre
de journal distincte qui donne un
aperçu du comportement général de
l’application comme, par exemple,
les mises à jour des variables d’état
importantes. Si des données numériques sont incluses dans l’enregistrement de l’application (utilisation
de la mémoire tampon, signaux de
commande ou entrées de capteur,
tout élément qui peut être tracé…),
Tracealyzer peut être considéré
comme un analyseur logique logiciel, utile dans de nombreux types
de développement. En outre, les
points de données dans les traces
sont liés à la vue principale ; par
conséquent, un double clic sur n’importe quel point de données permet
de synchroniser la vue principale et
de lui faire afficher l’événement correspondant.
réactif. Si un thread de priorité élevée
utilise trop de temps CPU, cela est
illustré par le graphique de la charge
CPU et par le tracé des temps de
réponse (figure 4). En outre, le rapport de statistiques fournit également
un aperçu des priorités des threads,
de l’utilisation du CPU et des statistiques de synchronisation, qui permet d’étudier et de revoir les priorités
des threads en général.
Tracealyzer propose aussi plusieurs
vues montrant les propriétés de synchronisation des threads dans des
tracés chronologiques, où chaque
point de données représente une instance (exécution) du thread. L’axe
des Y indique une propriété de syn-
risquent d’entrer en concurrence
fréquemment par rapport à la synchronisation, autrement dit, de commencer en même temps et de se
disputer l’utilisation du temps CPU,
même si le système dispose par ailleurs de beaucoup de temps processeur inutilisé. Cela provoque des
basculements de contexte inutiles et
retarde l’achèvement de l’ensemble
des threads en concurrence. Ces cas
sont des « fruits mûrs » pour l’optimisation, pour lesquels de petits changements de synchronisation peuvent
permettre d’améliorer considérablement les performances. Tracealyzer
facilite le repérage de ces opportunités, par exemple en inspectant le
4 INDICATION D’UNE CHARGE CPU SUR UNE FRISE CHRONOLOGIQUE
Sur cette vue, charge CPU et temps de réponse sont indiqués sur la même frise
chronologique.
Une approche multivue
La plupart des vues sont reliées entre
elles d’une manière similaire. La vue
principale est liée aux vues qui la
prennent en charge, et celles-ci sont
liées aux autres vues qui les prennent
en charge, ainsi qu’à la vue principale.
Une approche qui facilite le basculement entre les différentes perspectives
des données de trace lors de l’analyse
d’une situation particulière.
Certains systèmes Linux enfouis utilisent des priorités de planification
fixes (en temps réel) pour les threads
critiques temps réel, afin d’éviter les
interférences avec des threads moins
critiques. La bonne définition des
priorités est cependant cruciale pour
garantir un fonctionnement fiable et
chronisation spécifique telle que le
temps d’exécution, le temps de
réponse ou la périodicité (temps
entre deux activations). Cette dernière est particulièrement utile pour
analyser les activités périodiques. Si
l’exécution de thread périodique est
retardée à un moment donné, cela
est révélé par le tracé de la périodicité. Et, tout comme dans d’autres
vues similaires, le fait de double-cliquer sur le point de données dans le
tracé de périodicité permet de synchroniser la vue de trace principale
afin de permettre l’analyse de la
cause du retard.
Les threads périodiques qui s’exécutent à des fréquences semblables
graphique des « interférences des
réponses ». Ce dernier montre le
temps de réponse normalisé par rapport au temps d’exécution. Par
exemple, si un thread prend 300 µs
à s’exécuter, mais n’utilise que
100 µs de temps CPU, l’interférence
des réponses est de 200 %. Si plusieurs threads ont souvent des pics
d’interférence des réponses aux
mêmes moments, cela mérite probablement qu’on s’y intéresse d’un peu
plus près. Si l’exécution de threads
périodiques entrant en collision peut
être déplacée, le nombre de collisions peut ainsi être réduit et les performances peuvent s›en trouver augmentées.
n
L’EMBARQUÉ / N°6 / 2014 /
37