Machine d’état sans pointeur de fonction

J’ai implémenté une machine à états complexe avec de nombreuses transitions d’état pour un système SIL 4 de sécurité. L’ossature de cette mise en œuvre a été réalisée à l’aide de pointeurs de fonction. Lorsque tout se passait bien, le V & V s’est opposé à l’utilisation de pointeurs de fonction dans un système SIL 4. Référence – Règle 9 NASA .Misra C 2004 ne dit cependant pas que les pointeurs de fonction ne peuvent pas être utilisés.

Existe-t-il un autre moyen d’implémenter des machines d’état complexes sans pointeur de fonction?

Tout d’abord, ce document de la NASA n’est pas canon. Commencez par demander quelle loi / directive / norme / exigence / document vous oblige à suivre le document de la NASA. S’il n’est appliqué nulle part (ce qui semble très probable, même à la NASA), vous n’êtes pas obligé de le suivre et vous pouvez tout ignorer.

En omettant de considérer le non-sens comme un non-sens, vous pouvez utiliser la procédure habituelle pour bash un mur avec des normes de sécurité: la solution consiste toujours à documenter en détail comment la règle énoncée n’a pas de sens et à les gifler avec leur propre méthodologie. .

Par conséquent, plutôt que d’abandonner les pointeurs de fonction, assurez-vous qu’ils sont utilisés de manière sécurisée, en appliquant les méthodes décrites ci-dessous.


Comme toute conception liée à la sécurité se résume à une évaluation des risques, vous aurez toujours:

Erreur -> Cause -> Danger -> Mesure de sécurité

Avec la justification (médiocre) donnée dans le document de la NASA, vous justifieriez la mesure de sécurité “éviter les pointeurs de fonction” avec quelque chose comme:

  1. Mauvais code exécuté -> Pointeur de fonction corrompu -> Code de fuite / Code d’opération non conforme

  2. Dépassement de capacité de stack -> Récursion de pointeur de fonction -> Corruption de mémoire

  3. Programmeur confus -> Syntaxe du pointeur de fonction -> Fonctionnalité de programme non souhaitée

C’est une évaluation des risques plutôt vague et discutable, mais c’est ce que résume le document de la NASA.

Au lieu de “éviter les indicateurs de fonction” pour les 3 dangers énumérés ci-dessus, je suggérerais plutôt d’utiliser les mesures de sécurité suivantes:

  1. Programmation défensive et assertions.
  2. Programmation défensive et assertions. Éduquer les programmeurs.
  3. Utilisez typedef. Éduquer les programmeurs.

Programmation défensive et assertions

  • Pour la machine à états, assurez-vous que le nombre d’états (éléments de l’énumération) correspond au nombre de gestionnaires (éléments du tableau de pointeurs de fonctions). STATES_N simplement le dernier élément de l’énumération (appelé STATES_N ou autre) contre sizeof(func_pointer_array)/sizeof(*func_pointer_array) .
  • Le tableau de pointeurs de fonction doit être alloué dans la ROM avec détection d’erreur / CRC. D’autres exigences en matière de sécurité feront ressortir ce besoin. Le moyen le plus simple à résoudre consiste à utiliser un microcontrôleur avec “flash ECC”. À l’époque d’avant l’ECC, vous auriez plutôt à calculer un CRC sur l’ensemble de la ROM, ce qui est complexe et fastidieux, mais peut également être effectué. Vous pouvez également créer une image miroir ROM identique de la table pour vous protéger contre la corruption flash.
  • Le seul pointeur de fonction que votre code de machine d’état est autorisé à appeler est celui obtenu à partir de ce tableau de pointeurs de fonction sûr. Étant donné que votre appel à la machine d’état ressemble à STATE_MACHINE[i]();STATE_MACHINE est le tableau de pointeurs de fonction, ajoutez simplement une vérification au moment de l’exécution de i pour vous assurer qu’il est toujours valide.
  • Pour les autres pointeurs de fonction de votre programme, tels que les fonctions de rappel, assurez-vous qu’ils sont initialisés pour pointer sur des fonctions valides dans la ROM. Utilisez des pointeurs const si possible (le pointeur lui-même est en lecture seule). Si vous devez les réaffecter au moment de l’exécution, assurez-vous qu’ils pointent vers une fonction valide avant de les appeler.

Le type de machine à états ci-dessus est idiomatique et extrêmement sûr, probablement bien plus sûr que les appels de fonctions ordinaires ailleurs dans votre code. Vous devrez bien sûr vous assurer que les transits d’état se font de manière sûre et raisonnable, mais cela ne concerne pas les indicateurs de fonction.

Eviter la récursion

Il s’agit principalement d’éduquer les programmeurs à ne pas l’utiliser, des pointeurs de fonction ou aucun sharepoint fonction (semble que cela aurait empêché le bug Toyota).

Il n’est ni difficile de repérer ni d’éviter la récursivité, aussi des formalités de révision de code à peu près correctes devraient suffire à l’empêcher. Aucun ancien programmeur de systèmes embarqués, quelle que soit l’expérience des systèmes critiques pour la sécurité, n’approuvera la récursion contenant du code.

Vous pouvez / devriez définir une règle de conception interne stipulant que tout code relatif à la sécurité doit être revu et approuvé par un programmeur expérimenté C possédant n années d’expérience dans la conception de programmes critiques pour la sécurité.

En outre, vous devez également vérifier la récursivité à l’aide des outils de l’parsingur statique (même s’ils ne sont pas en mesure de détecter la récursivité via des pointeurs de fonction). Si vous disposez d’un parsingur statique conforme à n’importe quelle version de MISRA-C, cela est inclus.

En ce qui concerne la récursion involontaire, elle est évitée avec les méthodes de programmation défensive mentionnées ci-dessus.

Syntaxe du pointeur de la fonction Confusig

La syntaxe du pointeur de fonction en C peut certes être très déroutante, il suffit de regarder

 int (*(*func)[5])(void); 

ou un autre exemple ridicule. On peut résoudre ce problème en imposant toujours un typedef pour les types de pointeurs de fonction.

(Référence: Les Hatton, Safer C , p. 184 “Pour ce qui est de la sécurité, la réponse simple est qu’ils ne devraient jamais être autorisés en dehors du mécanisme de typedef.”)

Il y a deux manières différentes de les taper, je préfère ceci:

 typedef int func_t (void); func_t* fptr; 

Parce que cela ne cache pas le pointeur derrière un typedef, ce qui est généralement une mauvaise pratique. Mais si vous vous sentez plus à l’aise avec l’alternative

 typedef int (*func_t) (void); func_t fptr; 

alors c’est bien la pratique aussi.