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 .