PVS-Studio prend en charge la chaîne d'outils intégrée GNU Arm

Chaîne d'outils intégrée GNU Arm + PVS-Studio

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.

Mbed OS + PVS-Studio


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-os

Le 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; // <= } .... free(read_buf); 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; // <= } *interface_out = interface; return NSAPI_ERROR_OK; } 

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) { .... //Clear the error and context capturing buffer memset(&last_error_ctx, sizeof(mbed_error_ctx), 0); //reset error count to 0 error_count = 0; .... } 

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) { // Go through all services if (curService->S_id != S_id) { continue; } ns_list_foreach(thread_network_data_service_server_entry_t, curServiceServer, &curService->server_list) { *rlocAddress = curServiceServer->router_id; return true; // <= } } return false; } 

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); /* already listening? */ if (pcb->state == LISTEN) { // <= lpcb = (struct tcp_pcb_listen*)pcb; res = ERR_ALREADY; goto done; } .... } 

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) // <= { memcpy(ptr, entry->linkId, 8); *ptr ^= 2; } else if (entry->linkType == DHCPV6_DUID_HARDWARE_EUI64_TYPE)// <= { *ptr++ = entry->linkId[0] ^ 2; *ptr++ = entry->linkId[1]; .... } } 

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) .

Attends quoi


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 .

Source: https://habr.com/ru/post/fr427447/


All Articles