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.
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-osDie 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;  
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;  
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) { ....  
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) {  
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);  if (pcb->state == LISTEN) {  
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)  
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 ) .
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 .