Comment surveiller en continu l'analyse de la stack tout au long de la phase de développement ?

Avec des milliers, voire des millions de lignes de code, les logiciels embarqués deviennent de plus en plus sophistiqués, mais l'objectif global, qui consiste à obtenir un software robuste, correct et performant, reste le même. Un software performant doit gérer de manière optimale les ressources disponibles du CPU et de la mémoire, ce qui représente un défi dans les systèmes embarqués dont la capacité de mémoire, en particulier la mémoire vive (RAM), est limitée. Il est important d'analyser l'utilisation de la RAM en effectuant une analyse de la stack et du heap. L'estimation manuelle de la stack et du heap par les développeurs devient une tâche difficile, même pour les petits programmes. Des estimations incorrectes peuvent entraîner des débordements de stack et des comportements indéfinis. C'est la raison pour laquelle certaines normes de codage imposent des bonnes pratiques sur l'utilisation de l'allocation de mémoire afin d'éviter des dépassements inutiles. La stack reste toutefois un composant nécessaire de la RAM et doit être utilisée de manière optimale.

Pourquoi une analyse de stack est-elle essentielle pour les systèmes embarqués ?

Un débordement de stack se produit lorsque la stack disponible est plus petite que ce que demande le code. D'un autre côté, de la mémoire est gaspillée lorsque l'environnement est configuré avec une stack plus importante que nécessaire. Dans les applications critiques pour la sécurité, il est impératif que les développeurs évaluent de manière continue et cohérente l'utilisation de la stack dans la configuration la plus exigeante, afin d'éviter que le software ne soit à court de RAM.

Risque d'estimation incorrecte de la stack.

Risque d'estimation incorrecte de la stack.

Comment réaliser une estimation sur l'analyse de la stack ?

Estimation manuelle de la stack

L'estimation manuelle de l'analyse de la stack est parfois utile, mais peut s'avérer difficile pour les systèmes plus complexes. Elle exige une compréhension approfondie de la profondeur des appels de fonction, des détails sur toutes les variables locales, ainsi que la taille des cadres d'interruptions qui se produisent à tout moment au cours de l'exécution, parmi d’autres éléments. Cette procédure prend beaucoup de temps et est sujette aux erreurs. Des outils d'analyse statique du code pouvant rapidement calculer cette valeur, il n'est pas nécessaire de le faire manuellement.

Utilisation d'analyseurs de code statique

Les développeurs peuvent prédire l'utilisation de la stack grâce à un analyseur de code statique. Les outils d'analyse peuvent analyser la profondeur des appels de fonction, les estimations de la stack sur les variables locales et les paramètres de retour, les interruptions imbriquées ainsi que la taille des interruptions survenant au cours de l'exécution. L'avantage d'utiliser un analyseur de code statique réside dans sa capacité à traiter les infractions aux règles de codage, les défauts d’exécution, la complexité du codage et l'estimation de l'analyse de la stack. L'analyse est réalisée en quelques minutes, ce qui permet au développeur d’économiser le temps qu'il aurait fallu pour calculer manuellement la consommation de la stack.

Test et mesures sur la cible

Pendant la phase de développement, les analyseurs statiques peuvent estimer la consommation de la stack. Il est toutefois préférable d'obtenir des résultats concernant la consommation réelle de la stack sur le vrai hardware. De nombreux environnements de développement sont dotés de capacités d'émulation au niveau hardware et offrent la possibilité d'effectuer une analyse temps réel de la stack. Il est important de procéder à l'analyse de la stack sur du vrai hardware et de créer des scénarios de débordement pour tester les routines de sécurité. La grande question est désormais de savoir quand effectuer l'analyse de la stack avec des outils d'analyse statique et quand l'effectuer sur la cible réelle.

Quand faut-il effectuer une analyse de la stack ?

L'analyse de la stack est un processus continu dans le cycle de développement logiciel. Estimer l'utilisation de la stack uniquement à la fin du cycle de développement logiciel par une équipe d'évaluation de la qualité distincte pourrait mettre en péril l'ensemble de l'effort de développement. En outre, résoudre les problèmes tardivement dans le cycle de développement pourrait être inefficace et chronophage, et cela pourrait causer de la confusion lorsqu'il s'agit de déterminer de potentielles modifications de design sur le plan hardware ou sur le plan software. Les étapes les plus importantes pour effectuer une analyse de la stack sont les suivantes :

  • Lorsque de nouvelles fonctionnalités sont ajoutées

    Chaque nouvelle fonctionnalité ajoutée au software entraîne une augmentation de l'utilisation de la stack. Les développeurs doivent garder un œil sur l'utilisation de la stack de la nouvelle fonctionnalité.
    1. Effectuer une analyse de la stack, débugger et corriger du code complexe : après chaque implémentation d'une fonctionnalité majeure, les développeurs peuvent utiliser un analyseur statique sur un composant logiciel spécifique ou un module logiciel local pour évaluer l'augmentation de la consommation de la stack entre le software de base et le software implémenté.
    2. Contrôler l'analyse de la stack tout au long du processus de développement : l'équipe d'assurance qualité et les responsables de produits peuvent utiliser un analyseur statique pour faire des estimations de la stack sur le pipeline d'intégration continue (CI) et afficher les résultats sur des tableaux de bord. Ce processus permet de suivre l'analyse de la stack pendant le cycle de développement logiciel.
    3. Faire respecter les bonnes pratiques visant à réduire au minimum l'utilisation de la stack : des portails qualité peuvent aider à éviter les violations des directives de codage MISRA™ et AUTOSAR qui imposent l'utilisation conditionnelle d'allocations dynamiques de mémoire.
  • Avant la publication du software

    Des estimations de la stack par l'analyseur statique montrent clairement quand la consommation de la stack est maîtrisée. Avant chaque mise à jour du software, effectuez une analyse de la stack sur des cibles réelles sous des charges opérationnelles standard, des charges minimales et des charges maximales afin d'acquérir une compréhension complète de l'utilisation de la stack. Il est également essentiel de vérifier les procédures de sécurité en cas de dépassement ou de sous-utilisation de la stack.

Que fait Polyspace pour l'estimation de la stack ?

Polyspace Code Prover™ réalise des estimations conservatrices et optimistes sur les tailles supérieures et inférieures des variables locales dans chaque fonction afin d'obtenir une utilisation maximale et minimale de la stack, aussi bien au niveau fonctionnel qu’au niveau du programme. L'analyse tient compte de la taille des valeurs de retour de fonction, de la taille des paramètres de fonction, de la taille des variables locales et du remplissage supplémentaire introduit pour l'alignement de la mémoire.

Métriques de code d'analyse de stack sur le bureau Polyspace.

Métriques de code d'analyse de stack sur le bureau Polyspace.

Pour comprendre et débugger le dépassement de l'utilisation de la stack, les développeurs peuvent exécuter Polyspace® localement et parcourir les profondeurs des appels de fonction pour identifier la cause exacte du dépassement de la stack et réduire l'utilisation de la stack en exploitant les ressources disponibles de manière optimale.

Arbre d'appel et estimation de la stack supérieure pour la fonction table_loop().

Arbre d'appel et estimation de la stack supérieure pour la fonction table_loop().

Contrôler l'analyse de la stack tout au long du processus de développement

Polyspace Access™ est un serveur de base de données de résultats qui restitue une interface utilisateur graphique sur les navigateurs web. Les processus CI peuvent déclencher une analyse de la stack sur Polyspace Server™ pour générer une estimation de l'utilisation de la stack. Ce résultat peut être téléchargé dans la base de données de résultats. Les équipes d'assurance qualité et les responsables produits peuvent surveiller en permanence l'utilisation de la stack sur l'interface graphique et prendre les mesures nécessaires en cas de surutilisation des ressources stack disponibles.

Estimation de la stack au niveau du projet sur Polyspace Access.

Estimation de la stack au niveau du projet sur Polyspace Access.

L'étape suivante consiste à examiner les fonctions qui utilisent le plus de stack et à les faire analyser par des développeurs pour qu'ils les examinent plus en détail et les débuggent. Polyspace permet d’attribuer un statut, une sévérité et des commentaires aux résultats d'analyse avant qu'ils ne soient assignés aux développeurs sur des outils de suivi de bugs tels que Jira. 

Tableau de bord de l'estimation de la stack au niveau des fonctions et de l'examen des résultats sur Polyspace Access.

Tableau de bord de l'estimation de la stack au niveau des fonctions et de l'examen des résultats sur Polyspace Access.

Faire respecter les bonnes pratiques pour réduire au minimum l'utilisation de la stack

Pour le code de production, il est obligatoire de ne pas enfreindre les normes de codage telles que MISRA C™, MISRA C++, AUTOSAR C++, et autres. Ces normes de codage interdisent l'allocation dynamique de la mémoire et recommandent des cas d'utilisation spécifiques pour optimiser l'allocation statique de la mémoire. Polyspace Bug Finder™ peut identifier toute violation des bonnes pratiques, que les développeurs peuvent surveiller localement et les propriétaires de produits via Polyspace Access. Les règles de codage ci-dessous spécifient les bonnes pratiques pour l'allocation statique de la mémoire, qui peuvent être analysées avec Polyspace Bug Finder.

Directives de codage

Règle

Description

MISRA C : 2004

20.4

L'allocation dynamique de la mémoire du heap ne doit pas être utilisée.

MISRA C : 2012

21.3

Les fonctions d'allocation et de désallocation de mémoire de <stdlib.h> ne doivent pas être utilisées.

MISRA C++ : 2008

18-4-1

L'allocation dynamique de la mémoire du heap ne doit pas être utilisée.

AUTOSAR C++14

A18-5-1

Les fonctions malloc, calloc, realloc et free ne doivent pas être utilisées.

AUTOSAR C++14

A18-5-2

Les expressions de non-placement new ou delete ne doivent pas être utilisées.

AUTOSAR C++14

A18-5-3

La forme de l'expression delete doit correspondre à la forme de l’expression new utilisée pour allouer la mémoire.

AUTOSAR C++14

A18-5-4

Si un projet possède une version réduite ou non réduite de l'opérateur « delete » définie globalement, les deux versions, réduite et non réduite, doivent être définies.

AUTOSAR C++14

A18-5-5

Les fonctions de gestion de la mémoire doivent garantir les aspects suivants : (a) assurer un comportement déterministe résultant de l'existence d'un temps d'exécution maximal prévisible, (b) éviter la fragmentation de la mémoire, (c) éviter de manquer de mémoire, (d) éviter les allocations ou désallocations incompatibles, et (e) ne pas dépendre d'appels non déterministes au noyau.

AUTOSAR C++14

A18-5-7

Si le projet utilise une implémentation non temps réel des fonctions de gestion dynamique de la mémoire, la mémoire ne doit être allouée et désallouée que pendant les phases du programme non temps réel.

AUTOSAR C++14

A18-5-8

Les objets qui ne survivent pas à une fonction ont une durée de stockage automatique.

AUTOSAR C++14

A18-5-9

Les implémentations personnalisées des fonctions d'allocation et de désallocation de mémoire dynamique doivent satisfaire aux exigences sémantiques spécifiées dans la clause correspondante « Required behavior » du standard C++.

AUTOSAR C++14

A18-5-10

L’expression de placement new ne doit être utilisé qu'avec des indications correctement alignées sur une capacité de stockage suffisante.

AUTOSAR C++14

A18-5-11

« operator new » et « operator delete » doivent être définis ensemble.

L'utilisation de la stack augmente notamment avec la complexité cyclomatique croissante du code, le nombre d'appels de fonctions imbriquées, le nombre de variables dans la fonction et d’autres facteurs. Polyspace permet de contrôler de nombreuses variables qui influencent l'utilisation de la stack et de fixer un seuil de complexité du code.

Fixer des seuils de complexité du code.

Fixer des seuils de complexité du code.

Polyspace Bug Finder offre de nombreux contrôles au niveau runtime pour les allocations de mémoire statique et dynamique. La résolution de tous les défauts de haute, moyenne et faible priorité permet de réduire les risques liés à l'allocation de mémoire.

Contrôles statiques et dynamiques de la mémoire en runtime.

Contrôles statiques et dynamiques de la mémoire en runtime.

Il est conseillé de surdimensionner légèrement la stack, quelle que soit la méthode utilisée pour calculer son utilisation. Cette approche permet d'éviter les vulnérabilités système dues à un débordement de la stack qui aurait pu ne pas être détecté lors des tests.

La vulnérabilité du débordement de la stack est l'une des principales raisons pour lesquelles de nombreuses applications embarquées présentent un comportement imprévisible sur le terrain. Le fait d'utiliser le bon outil au bon moment et de respecter les bonnes pratiques peut améliorer la résistance du software par rapport aux débordements de la stack.