PVS-Studio unterstützt die GNU Arm Embedded Toolchain

GNU Arm Embedded Toolchain + PVS-Studio

Eingebettete Systeme haben unser Leben lange und fest geprägt. Die Anforderungen an ihre Stabilität und Zuverlässigkeit sind sehr hoch und eine Fehlerkorrektur ist teuer. Daher ist es für Embedded-Entwickler besonders wichtig, regelmäßig spezielle Tools zu verwenden, um die Qualität des Quellcodes sicherzustellen. In diesem Artikel wird über die Unterstützung der GNU Arm Embedded Toolchain im PVS-Studio-Analysegerät und über Codefehler im Mbed OS-Projekt gesprochen.

Einführung


Der PVS-Studio Analyzer unterstützt bereits mehrere kommerzielle Compiler für eingebettete Systeme, zum Beispiel:


Jetzt wurde ein weiteres Entwicklertool zur Unterstützung hinzugefügt - die GNU Embedded Toolchain.

Die GNU Embedded Toolchain ist eine Sammlung von Compilern von Arm, die auf der GNU Compiler Collection basieren. Die erste offizielle Veröffentlichung fand 2012 statt und seitdem wurde das Projekt zusammen mit dem GCC entwickelt.

Der Hauptzweck der GNU Embedded Toolchain besteht darin, Code zu generieren, der auf Bare Metal ausgeführt wird, dh direkt auf dem Prozessor ohne Zwischenschicht in Form eines Betriebssystems. Das Paket enthält Compiler für C und C ++, Assembler, eine Reihe von GNU Binutils-Dienstprogrammen und die Newlib- Bibliothek. Der Quellcode für alle Komponenten ist vollständig geöffnet und unter der GNU GPL lizenziert. Von der offiziellen Website können Sie Versionen für Windows, Linux und macOS herunterladen.

Mbed OS


Zum Testen des Analysators benötigen Sie so viel Quellcode wie möglich. Normalerweise gibt es damit kein Problem, aber wenn wir uns mit eingebetteter Entwicklung befassen, die hauptsächlich auf Geräte abzielt, die in IoT enthalten sind, kann es schwierig sein, eine ausreichende Anzahl großer Projekte zu finden. Glücklicherweise wurde dieses Problem durch spezialisierte Betriebssysteme gelöst, deren Quellcode in den meisten Fällen offen ist. Weiter werden wir über einen von ihnen sprechen.

Mbed OS + PVS-Studio


Obwohl der Hauptzweck dieses Artikels darin besteht, über die Unterstützung der GNU Embedded Toolchain zu sprechen, ist es schwierig, viel darüber zu schreiben. Darüber hinaus warten die Leser unserer Artikel wahrscheinlich auf eine Beschreibung einiger interessanter Fehler. Lassen Sie uns ihre Erwartungen nicht täuschen und den Analysator für das Mbed OS-Projekt ausführen. Dies ist ein Open Source-Betriebssystem, das mit Unterstützung von Arm entwickelt wurde.

Offizielle Website: https://www.mbed.com/

Quellcode: https://github.com/ARMmbed/mbed-os

Die Wahl für Mbed OS fiel nicht zufällig, so beschreiben die Autoren das Projekt:

Arm Mbed OS ist ein Open Source Embedded-Betriebssystem, das speziell für die "Dinge" im Internet der Dinge entwickelt wurde. Es enthält alle Funktionen, die Sie zur Entwicklung eines verbundenen Produkts auf der Basis eines Arm Cortex-M-Mikrocontrollers benötigen, einschließlich Sicherheit, Konnektivität, RTOS und Treiber für Sensoren und E / A-Geräte.

Dies ist ein ideales Build-Projekt mit der GNU Embedded Toolchain, insbesondere angesichts der Beteiligung von Arm an seiner Entwicklung. Ich werde sofort reservieren, dass ich nicht das Ziel hatte, so viele Fehler wie möglich in einem bestimmten Projekt zu finden und anzuzeigen. Daher werden die Ergebnisse der Überprüfung kurz überprüft.

Fehler


Während der Überprüfung des Mbed-Betriebssystemcodes generierte der PVS-Studio-Analysator 693 Warnungen, davon 86 mit hoher Priorität. Ich werde sie nicht alle im Detail betrachten, zumal viele von ihnen wiederholt werden oder nicht von besonderem Interesse sind. Beispielsweise hat der Analysator viele Warnungen V547 (Ausdruck ist immer wahr / falsch) generiert , die sich auf dieselben Codefragmente beziehen. Der Analysator kann so konfiguriert werden, dass die Anzahl falscher und uninteressanter Antworten erheblich reduziert wird. Diese Aufgabe wurde jedoch beim Schreiben eines Artikels nicht gestellt. Diejenigen, die dies wünschen, können ein Beispiel für eine solche Konfiguration sehen, die im Artikel " PVS-Studio-Analysatorspezifikationen am Beispiel der EFL-Kernbibliotheken, 10-15% der falsch positiven Ergebnisse " beschrieben ist.

Für den Artikel habe ich einige interessante Fehler ausgewählt, um die Funktionsweise des Analysators zu demonstrieren.

Speicherlecks


Beginnen wir mit der allgemeinen Fehlerklasse in C und C ++ - Speicherlecks.

Analyzer Warnung: V773 CWE-401 Die Funktion wurde beendet, ohne den Zeiger 'read_buf' loszulassen. Ein Speicherverlust ist möglich. 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; } 

Die klassische Situation beim Arbeiten mit dynamischem Speicher. Der von malloc zugewiesene Puffer wird nur innerhalb der Funktion verwendet und vor dem Beenden freigegeben. Das Problem ist, dass dies nicht passiert, wenn die Funktion vorzeitig nicht mehr funktioniert. Beachten Sie den gleichen Code in den if- Blöcken. Höchstwahrscheinlich hat der Autor das oberste Fragment kopiert und vergessen, einen kostenlosen Anruf hinzuzufügen.

Ein weiteres Beispiel ähnlich dem vorherigen.

Analysator Warnung: V773 CWE-401 Die Funktion wurde beendet, ohne den Zeiger 'Schnittstelle' loszulassen. Ein Speicherverlust ist möglich. 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; } 

Der Zeiger auf den zugewiesenen Speicher wird über den Ausgabeparameter zurückgegeben, jedoch nur, wenn der Initialisierungsaufruf erfolgreich war. Im Fehlerfall tritt ein Leck auf, da die lokale Schnittstellenvariable den Gültigkeitsbereich verlässt und der Zeiger einfach verloren geht. Hier sollte man entweder delete aufrufen oder zumindest die in der Schnittstellenvariablen gespeicherte Adresse nach außen geben, damit sich der aufrufende Code darum kümmern kann.

Memset


Die Verwendung der Memset- Funktion führt häufig zu Fehlern. Beispiele für die damit verbundenen Probleme finden Sie im Artikel " Die gefährlichste Funktion in der C / C ++ - Welt ".

Beachten Sie die folgende Warnung des Analysators:

V575 CWE-628 Die Funktion 'memset' verarbeitet '0'-Elemente. Untersuchen Sie das dritte 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; .... } 

Der Programmierer wollte den von der last_error_ctx- Struktur belegten Speicher zurücksetzen, verwechselte jedoch das zweite und dritte Argument. Infolgedessen werden 0 Bytes mit dem Wert sizeof (mbed_error_ctx) gefüllt.

Genau der gleiche Fehler ist hundert Zeilen weiter oben vorhanden:

V575 CWE-628 Die Funktion 'memset' verarbeitet '0'-Elemente. Untersuchen Sie das dritte Argument. mbed_error.c 123

Unbedingte 'return'-Anweisung in einer Schleife


Analyzer-Warnung: V612 CWE-670 Eine bedingungslose 'Rückgabe' innerhalb einer Schleife. 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; } 

In diesem Snippet ist ns_list_foreach das Makro, das in die for- Anweisung erweitert wird. Die innere Schleife führt aufgrund des Aufrufs, unmittelbar nach der Zeile zurückzukehren, in der der Ausgabeparameter der Funktion initialisiert wird, nicht mehr als eine Iteration durch. Vielleicht funktioniert dieser Code wie beabsichtigt, aber die Verwendung der inneren Schleife sieht in diesem Zusammenhang ziemlich seltsam aus. Höchstwahrscheinlich muss die Initialisierung von rlocAddress und das Verlassen der Funktion je nach Bedingung ausgeführt werden, sonst können Sie die innere Schleife entfernen .

Fehler in den Bedingungen


Wie ich oben sagte, hat der Analysator eine ziemlich große Anzahl uninteressanter V547- Warnungen generiert , daher habe ich sie fließend studiert und nur zwei Fälle für den Artikel geschrieben.

V547 CWE-570 Der Ausdruck 'pcb-> state == LISTEN' ist immer falsch. 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; } .... } 

Der Analysator ist der Ansicht, dass die Bedingung pcb-> state == LISTEN immer falsch ist. Mal sehen, warum.

Vor der if-Anweisung wird das Makro LWIP_ERROR verwendet , das gemäß der Logik seiner Operation dem Assert ähnelt. Seine Anzeige sieht folgendermaßen aus:

 #define LWIP_ERROR(message, expression, handler) do { if (!(expression)) { \ LWIP_PLATFORM_ERROR(message); handler;}} while(0) 

Wenn die Bedingung falsch ist, meldet das Makro einen Fehler und führt den durch den Handler- Parameter übergebenen Code aus. In diesem Codefragment gibt es einen bedingungslosen Sprung mit goto .

In diesem Beispiel wird die Bedingung 'pcb-> state == CLOSED' aktiviert, dh der Übergang zum erledigten Label erfolgt, wenn pcb-> state einen anderen Wert hat. Die if-Anweisung nach dem Aufruf von LWIP_ERROR überprüft den Status pcb-> auf LISTEN . Diese Bedingung wird jedoch nie erfüllt, da der Status in dieser Zeile nur den Wert CLOSED enthalten kann.

Betrachten Sie eine weitere Warnung in Bezug auf die Bedingungen: V517 CWE-570 Die Verwendung des Musters 'if (A) {...} else if (A) {...}' wurde erkannt. Es besteht die Wahrscheinlichkeit eines logischen Fehlers. Überprüfen Sie die Zeilen: 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]; .... } } 

Hier, wenn und sonst, wenn dieselbe Bedingung überprüft wird, wodurch der Code im else, wenn body nie ausgeführt wird. Solche Fehler treten häufig auf, wenn Code mit der Copy-Paste- Methode geschrieben wird.

Eigentümerloser Ausdruck


Werfen wir einen Blick auf einen lustigen Code.

Analyzer-Warnung: V607 Besitzerloser Ausdruck '& remove_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); .... } 

Schauen wir uns nun die Makrodeklaration thread_extension_discover_response_tlv_write an :

 #define thread_extension_discover_response_tlv_write \ ( data, version, extension_bit)\ (data) 

Das Makro wird in das Datenargument erweitert, dh sein Aufruf innerhalb der Funktion thread_discovery_response_send , nachdem die Vorverarbeitung in einen Ausdruck umgewandelt wurde (& remove_response_tlv ) .

Warten Sie was


Ich habe keine Kommentare. Dies ist wahrscheinlich kein Fehler, aber ein solcher Code versetzt mich immer in einen ähnlichen Zustand wie das Bild auf dem Bild :).

Fazit


Die Liste der in PVS-Studio unterstützten Compiler wurde erweitert. Wenn Sie ein Projekt haben, das für die Montage mit der GNU Arm Embedded Toolchain vorgesehen ist, empfehle ich, es mit unserem Analysegerät zu testen. Laden Sie die Demo hier herunter. Beachten Sie auch die kostenlose Lizenzoption, die für einige kleine Projekte geeignet ist.



Wenn Sie diesen Artikel einem englischsprachigen Publikum zugänglich machen möchten, verwenden Sie bitte den Link zur Übersetzung: Yuri Minaev. PVS-Studio unterstützt jetzt GNU Arm Embedded Toolchain .

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


All Articles