Les systèmes embarqués sont entrés depuis longtemps et fermement dans nos vies. Les exigences de stabilité et de fiabilité sont très élevées et la correction d'erreurs est coûteuse. Par conséquent, il est particulièrement important que les développeurs intégrés utilisent régulièrement des outils spécialisés pour garantir la qualité du code source. Cet article parlera de l'apparence de la prise en charge de la chaîne d'outils intégrée GNU Arm dans l'analyseur PVS-Studio et des défauts de code trouvés dans le projet Mbed OS.
Présentation
L'analyseur PVS-Studio prend déjà en charge plusieurs compilateurs commerciaux pour les systèmes embarqués, par exemple:
Maintenant, un autre outil de développeur a été ajouté pour prendre en charge - la chaîne d'outils intégrée GNU.
La chaîne d'outils intégrée GNU est une collection de compilateurs d'Arm basée sur la collection de compilateurs GNU. La première version officielle a eu lieu en 2012 et depuis lors, le projet se développe avec le CCG.
Le but principal de GNU Embedded Toolchain est de générer du code qui s'exécute sur du métal nu, c'est-à-dire directement sur le processeur sans intercouche sous la forme d'un système d'exploitation. Le package comprend des compilateurs pour C et C ++, un assembleur, un ensemble d'utilitaires GNU Binutils et la bibliothèque
Newlib . Le code source de tous les composants est entièrement ouvert et sous licence GNU GPL. Depuis le site officiel, vous pouvez télécharger des versions pour Windows, Linux et macOS.
Mbed OS
Pour tester l'analyseur, vous avez besoin d'autant de code source que possible. Habituellement, cela ne pose aucun problème, mais lorsque nous traitons de développement intégré visant principalement les appareils inclus dans l'IoT, il peut être difficile de trouver un nombre suffisant de grands projets. Heureusement, ce problème a été résolu par des systèmes d'exploitation spécialisés, dont le code source est dans la plupart des cas ouvert. Plus loin, nous parlerons de l'un d'eux.
Bien que l'objectif principal de cet article soit de parler de la prise en charge de la chaîne d'outils intégrée GNU, il est difficile d'écrire beaucoup à ce sujet. De plus, les lecteurs de nos articles attendent probablement une description de quelques erreurs intéressantes. Eh bien, ne trompons pas leurs attentes et exécutons l'analyseur sur le projet Mbed OS. Il s'agit d'un système d'exploitation open source développé avec l'aide d'Arm.
Site officiel:
https://www.mbed.com/Code source:
https://github.com/ARMmbed/mbed-osLe choix sur Mbed OS n'est pas tombé par hasard, voici comment les auteurs décrivent le projet:
Arm Mbed OS est un système d'exploitation embarqué open source spécialement conçu pour les «choses» de l'Internet des objets. Il comprend toutes les fonctionnalités dont vous avez besoin pour développer un produit connecté basé sur un microcontrôleur Arm Cortex-M, y compris la sécurité, la connectivité, un RTOS et des pilotes pour les capteurs et les périphériques d'E / S.
Il s'agit d'un projet de construction idéal utilisant la chaîne d'outils intégrée GNU, en particulier compte tenu de l'implication d'Arm dans son développement. Je vais faire une réservation tout de suite que je n'ai pas eu pour objectif de trouver et d'afficher autant d'erreurs que possible dans un projet spécifique. Les résultats de l'examen sont donc brièvement examinés.
Erreurs
Lors de la vérification du code Mbed OS, l'analyseur PVS-Studio a généré 693 avertissements, 86 d'entre eux avec une priorité élevée. Je ne les examinerai pas tous en détail, d'autant plus que nombre d'entre eux se répètent ou ne présentent pas un intérêt particulier. Par exemple, l'analyseur a
généré de nombreux avertissements
V547 (l'expression est toujours vraie / fausse) liés aux mêmes fragments de code. L'analyseur peut être configuré pour réduire considérablement le nombre de réponses fausses et sans intérêt, mais cette tâche n'a pas été définie lors de la rédaction d'un article. Ceux qui le souhaitent peuvent voir un exemple d'une telle configuration décrite dans l'article "
Spécifications de l'analyseur PVS-Studio en utilisant l'exemple EFL Core Libraries, 10-15% de faux positifs ".
Pour l'article, j'ai sélectionné quelques erreurs intéressantes pour démontrer le fonctionnement de l'analyseur.
Fuites de mémoire
Commençons par la classe commune des erreurs en C et C ++ - les fuites de mémoire.
Avertissement de l'analyseur:
V773 CWE-401 La fonction a été quittée sans libérer le pointeur 'read_buf'. Une fuite de mémoire est possible. cfstore_test.c 565
int32_t cfstore_test_init_1(void) { .... read_buf = (char*) malloc(max_len); if(read_buf == NULL) { CFSTORE_ERRLOG(....); return ret; } .... while(node->key_name != NULL) { .... ret = drv->Create(....); if(ret < ARM_DRIVER_OK){ CFSTORE_ERRLOG(....); return ret;
La situation classique lors de l'utilisation de la mémoire dynamique. Le tampon alloué par
malloc est utilisé uniquement à l'intérieur de la fonction et libéré avant de quitter. Le problème est que cela ne se produit pas si la fonction cesse de fonctionner plus tôt. Notez le même code dans les blocs
if . Très probablement, l'auteur a copié le fragment supérieur et a oublié d'ajouter un appel
gratuit .
Un autre exemple similaire au précédent.
Avertissement de l'analyseur:
V773 CWE-401 La fonction a été quittée sans libérer le pointeur «interface». Une fuite de mémoire est possible. nanostackemacinterface.cpp 204
nsapi_error_t Nanostack::add_ethernet_interface( EMAC &emac, bool default_if, Nanostack::EthernetInterface **interface_out, const uint8_t *mac_addr) { .... Nanostack::EthernetInterface *interface; interface = new (nothrow) Nanostack::EthernetInterface(*single_phy); if (!interface) { return NSAPI_ERROR_NO_MEMORY; } nsapi_error_t err = interface->initialize(); if (err) { return err;
Le pointeur vers la mémoire allouée est renvoyé via le paramètre de sortie, mais uniquement si l'appel d'
initialisation a réussi, et en cas d'erreur, une fuite se produit car la variable d'
interface locale sort du domaine et le pointeur est simplement perdu. Ici, il faut soit appeler
delete , soit au moins donner l'adresse stockée dans la variable d'
interface à l'extérieur dans tous les cas, afin que le code appelant puisse s'en occuper.
Memset
L'utilisation de la fonction
memset entraîne souvent des erreurs, des exemples des problèmes qui lui sont associés peuvent être trouvés dans l'article "
La fonction la plus dangereuse du monde C / C ++ ".
Tenez compte de l'avertissement de l'analyseur suivant:
V575 CWE-628 La fonction 'memset' traite les éléments '0'. Inspectez le troisième argument. mbed_error.c 282
mbed_error_status_t mbed_clear_all_errors(void) { ....
Le programmeur avait l'intention de réinitialiser la mémoire occupée par la structure
last_error_ctx , mais a mélangé les deuxième et troisième arguments. Par conséquent,
0 octet est rempli avec la valeur
sizeof (mbed_error_ctx) .
Exactement la même erreur est présente une centaine de lignes plus haut:
V575 CWE-628 La fonction 'memset' traite les éléments '0'. Inspectez le troisième argument. mbed_error.c 123
Instruction 'return' inconditionnelle dans une boucle
Avertissement de l'analyseur:
V612 CWE-670 Un «retour» inconditionnel dans une boucle. thread_network_data_storage.c 2348
bool thread_nd_service_anycast_address_mapping_from_network_data ( thread_network_data_cache_entry_t *networkDataList, uint16_t *rlocAddress, uint8_t S_id) { ns_list_foreach(thread_network_data_service_cache_entry_t, curService, &networkDataList->service_list) {
Dans cet extrait,
ns_list_foreach est la macro qui se développe dans l'instruction
for . La boucle interne n'effectue pas plus d'une itération en raison de l'appel à
retourner immédiatement après la ligne dans laquelle le paramètre de sortie de la fonction est initialisé. Peut-être que ce code fonctionne comme prévu, mais l'utilisation de la boucle interne semble plutôt étrange dans ce contexte. Très probablement, l'initialisation
rlocAddress et la sortie de la fonction doivent être effectuées par condition,
sinon vous pouvez vous débarrasser de la boucle interne.
Erreurs dans les conditions
Comme je l'ai dit ci-dessus, l'analyseur a
généré un assez grand nombre d'avertissements
V547 sans
intérêt , donc je les ai étudiés couramment et n'ai écrit que deux cas pour l'article.
V547 CWE-570 L'expression 'pcb-> state == LISTEN' est toujours fausse. lwip_tcp.c 689
enum tcp_state { CLOSED = 0, LISTEN = 1, .... }; struct tcp_pcb * tcp_listen_with_backlog_and_err(struct tcp_pcb *pcb, u8_t backlog, err_t *err) { .... LWIP_ERROR("tcp_listen: pcb already connected", pcb->state == CLOSED, res = ERR_CLSD; goto done); if (pcb->state == LISTEN) {
L'analyseur considère que la condition
pcb-> state == LISTEN est toujours fausse,
voyons pourquoi.
Avant l'
instruction if , la macro
LWIP_ERROR est
utilisée , ce qui, par sa logique de fonctionnement, ressemble à
assert . Son annonce ressemble à ceci:
#define LWIP_ERROR(message, expression, handler) do { if (!(expression)) { \ LWIP_PLATFORM_ERROR(message); handler;}} while(0)
Si la condition est fausse, la macro signale une erreur et exécute le code transmis via le paramètre du
gestionnaire , dans ce fragment de code il y a un saut inconditionnel à l'aide de
goto .
Dans cet exemple, la condition 'pcb-> state == CLOSED' est vérifiée, c'est-à-dire que la transition vers l'étiquette
done se produit lorsque
pcb-> state a une autre valeur. L'
instruction if suivant l'appel à
LWIP_ERROR vérifie l'
état pcb-> pour
LISTEN , mais cette condition n'est jamais remplie, car l'
état sur cette ligne ne peut contenir que la valeur
CLOSED .
Considérez un autre avertissement lié aux conditions:
V517 CWE-570 L'utilisation du modèle 'if (A) {...} else if (A) {...}' a été détectée. Il y a une probabilité de présence d'erreur logique. Vérifiez les lignes: 62, 65. libdhcpv6_server.c 62
static void libdhcpv6_address_generate(....) { .... if (entry->linkType == DHCPV6_DUID_HARDWARE_EUI64_TYPE)
Ici,
if et
else if vérifient la même condition, à la suite de quoi le code dans le corps
else if n'est jamais exécuté. Ces erreurs se produisent souvent lors de l'écriture de code à l'aide de la méthode
copier-coller .
Expression sans propriétaire
Jetons un coup d'œil à un morceau de code amusant.
Avertissement de l'analyseur: expression sans
propriétaire V607 '& Discover_response_tlv'. thread_discovery.c 562
static int thread_discovery_response_send( thread_discovery_class_t *class, thread_discovery_response_msg_t *msg_buffers) { .... thread_extension_discover_response_tlv_write( &discover_response_tlv, class->version, linkConfiguration->securityPolicy); .... }
Jetons maintenant un œil à la
déclaration de macro
thread_extension_discover_response_tlv_write :
#define thread_extension_discover_response_tlv_write \ ( data, version, extension_bit)\ (data)
La macro est développée dans l'argument de données, c'est-à-dire son appel à l'intérieur de la fonction
thread_discovery_response_send après que le prétraitement se transforme en expression
(& discovery_response_tlv) .
Je n'ai aucun commentaire. Ce n'est probablement pas une erreur, mais un tel code me met toujours dans un état similaire à l'image de l'image :).
Conclusion
La liste des compilateurs pris en charge dans PVS-Studio a été étendue. Si vous avez un projet destiné à être assemblé à l'aide de la chaîne d'outils intégrée GNU Arm, je suggère d'essayer de le tester à l'aide de notre analyseur. Téléchargez la démo
ici . Faites également attention à l'option de
licence gratuite , qui convient à certains petits projets.

Si vous souhaitez partager cet article avec un public anglophone, veuillez utiliser le lien vers la traduction: Yuri Minaev.
PVS-Studio prend désormais en charge la chaîne d'outils intégrée GNU Arm .