In PVS-Studio hinzugefügte GNU Arm Embedded Toolchain

    GNU Arm Embedded Toolchain + PVS-Studio

    Eingebettete Systeme sind in unserem Leben seit langem fest etabliert. Die Anforderungen an ihre Stabilität und Zuverlässigkeit sind sehr hoch und die Fehlerkorrektur ist teuer. Daher ist es für Embedded-Entwickler besonders wichtig, regelmäßig spezialisierte Tools zu verwenden, um die Qualität des Quellcodes sicherzustellen. In diesem Artikel wird die Unterstützung von GNU Arm Embedded Toolchain im PVS-Studio Analyzer und Codefehler im Mbed OS-Projekt beschrieben.

    Einleitung


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


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

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

    Der Hauptzweck von GNU Embedded Toolchain besteht darin, Code zu generieren, der auf Bare Metal (Bare Metal) läuft, dh direkt auf einem Prozessor ohne Zwischenschicht in Form eines Betriebssystems. Das Paket enthält Compiler für C und C ++, Assembler, das GNU Binutils-Toolkit und die Newlib- Bibliothek . Der Quellcode aller Komponenten ist vollständig offen und wird unter der GNU GPL-Lizenz vertrieben. Von der offiziellen Website können Sie Versionen für Windows, Linux und Mac OS herunterladen.

    Mbed os


    Um das Analysegerät zu testen, benötigen Sie so viel Quellcode wie möglich. Normalerweise gibt es keine Probleme damit, aber wenn wir uns mit Embedded-Entwicklung befassen, die hauptsächlich auf IoT-Geräte abzielt, kann es schwierig sein, eine ausreichende Anzahl von großen Projekten zu finden. Glücklicherweise wurde dieses Problem durch die Verwendung spezieller Betriebssysteme gelöst, deren Quellcode meistens offen ist. Dann werden wir einen von ihnen besprechen.

    Med OS + PVS-Studio


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

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

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

    Die Wahl des Betriebssystems Mbed war kein Zufall, so beschreiben die Autoren das Projekt:

    Es ist ein Open Source-Betriebssystem für das Internet der Dinge. Es enthält einen Mikrocontroller, einschließlich Sicherheit, Konnektivität, E / A-Geräte.

    Dies ist ein ideales Build-Projekt unter Verwendung der GNU Embedded Toolchain, insbesondere wenn Arm an seiner Entwicklung beteiligt ist. Ich sage nur, dass ich nicht das Ziel hatte, so viele Fehler wie möglich in einem bestimmten Projekt zu finden und anzuzeigen, daher werden die Testergebnisse kurz überprüft.

    Fehler


    Bei der Verifizierung des Mbed-OS-Codes gab der PVS-Studio Analyzer 693 Warnungen aus, von denen 86 eine hohe Priorität hatten. Ich werde sie nicht alle im Detail betrachten, zumal viele von ihnen wiederholt werden oder kein großes Interesse haben. Zum Beispiel gab der Analysator viele Warnungen V547 (Ausdruck ist immer wahr / falsch) aus, die sich auf denselben Typ von Codefragmenten beziehen. Der Analysator kann so konfiguriert werden, dass er die Anzahl falscher und uninteressanter Positiven erheblich reduziert, aber beim Schreiben eines Artikels war dies nicht der Fall. Interessenten können ein Beispiel für eine solche Konfiguration sehen, die im Artikel " Eigenschaften des PVS-Studio-Analysators am Beispiel von EFL-Kernbibliotheken, 10-15% der Fehlalarme " beschrieben wird.

    Für den Artikel habe ich einige interessante Fehler ausgewählt, um den Betrieb des Analysators zu veranschaulichen.

    Speicherlecks


    Beginnen wir mit einer allgemeinen Klasse von Fehlern in C- und C ++ - Speicherverlusten.

    Analyzer Warnung: V773 CWE-401 Die Funktion wurde ohne Lesen des Zeigers 'read_buf' verlassen. 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 mit malloc zugewiesene Puffer wird nur innerhalb der Funktion verwendet und vor dem Beenden freigegeben. Das Problem ist, dass dies nicht der Fall ist, wenn die Funktion vorzeitig beendet wird. Beachten Sie den gleichen Code in den if- Blöcken . Höchstwahrscheinlich hat der Autor das oberste Fragment kopiert und vergessen, den kostenlosen Anruf hinzuzufügen .

    Ein anderes Beispiel, ähnlich dem vorherigen.

    Analysator Warnung: V773 CWE-401 Die Funktion der Schnittstelle. Ein Speicherverlust ist möglich. nanostackemacinterface.cpp 204

    nsapi_error_t Nanostack::add_ethernet_interface(
        EMAC &emac,
        bool default_if,
        Nanostack::EthernetInterface **interface_out,
        constuint8_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;
    }

    Ein Zeiger auf den zugewiesenen Speicher wird über den Ausgabeparameter zurückgegeben, jedoch nur, wenn der Aufruf zum Initialisieren erfolgreich ist. Wenn ein Fehler auftritt, tritt ein Leck auf, da die lokale Schnittstellenvariable den Gültigkeitsbereich verlässt und der Zeiger einfach verloren geht. Hier sollten Sie entweder delete aufrufen oder in jedem Fall zumindest die in der Schnittstellenvariable gespeicherte Adresse angeben , damit der aufrufende Code dafür sorgen kann.

    Memset


    Die Verwendung der Memset- Funktion führt häufig zu Fehlern. Beispiele für verwandte 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. Überprüfen Sie das dritte Argument. mbed_error.c 282

    mbed_error_status_t mbed_clear_all_errors(void)
    {
        ....
        //Clear the error and context capturing buffermemset(&last_error_ctx, sizeof(mbed_error_ctx), 0);
        //reset error count to 0
        error_count = 0;
        ....
    }

    Der Programmierer beabsichtigte, den von der last_error_ctx- Struktur belegten Speicher zurückzusetzen , vermischte jedoch das zweite und das dritte Argument. Daher werden 0 Bytes mit dem Wert für sizeof (mbed_error_ctx) gefüllt .

    Genau ein und derselbe Fehler ist hundert Zeilen darüber vorhanden:

    V575 CWE-628 Die Funktion 'Memset' verarbeitet '0'-Elemente. Überprüfen Sie das dritte Argument. mbed_error.c 123

    Bedingungslose 'return'-Anweisung in einer Schleife


    Analyzer-Warnung: V612 CWE-670 Ein unbedingter "Return" innerhalb einer Schleife. thread_network_data_storage.c 2348

    boolthread_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 servicesif (curService->S_id != S_id) {
          continue;
        }
        ns_list_foreach(thread_network_data_service_server_entry_t,
                        curServiceServer, &curService->server_list) {
          *rlocAddress = curServiceServer->router_id;
          returntrue;                     // <=
        }
      }
      returnfalse;
    }

    In diesem Snippet ist ns_list_foreach ein Makro, das sich in eine for- Anweisung erweitert . Die innere Schleife führt aufgrund des Aufrufs nicht mehr als eine Iteration aus, um unmittelbar nach der Zeile zurückzukehren, in der der Ausgabeparameter der Funktion initialisiert wird. Vielleicht funktioniert dieser Code wie beabsichtigt, aber die Verwendung der inneren Schleife erscheint in diesem Zusammenhang eher seltsam. Am ehesten sollte die Initialisierung von rlocAddress und das Beenden der Funktion entsprechend der Bedingung durchgeführt werden, oder Sie können die interne Schleife entfernen .

    Fehler in den Bedingungen


    Wie ich bereits gesagt habe, gab der Analysator ziemlich viele uninteressante Warnungen aus dem V547 heraus , deshalb untersuchte ich sie kurz und schrieb nur zwei Fälle für den Artikel auf.

    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 geht davon aus, dass die Bedingung pcb-> state == LISTEN immer falsch ist. Schauen wir uns den Grund dafür an.

    Vor der if-Anweisung wird das Makro LWIP_ERROR verwendet , das nach der Logik seiner Arbeit assert ähnelt . Seine Anzeige sieht so 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 Code aus, der durch den Handler- Parameter übergeben wird. In diesem Codefragment ist dies ein unbedingter Sprung mit goto .

    In diesem Beispiel wird die Bedingung 'pcb-> state == CLOSED' überprüft, das heißt, ein Übergang zum done- Label erfolgt, wenn pcb-> state einen anderen Wert hat. Die if-Anweisung , die auf den LWIP_ERROR- Aufruf folgt, überprüft pcb-> state auf LISTEN- Gleichheit . Diese Bedingung wird jedoch nie erfüllt, da der Status in dieser Zeile nur den CLOSED- Wert enthalten kann .

    Beachten Sie eine weitere Warnung in Bezug auf die Bedingungen: V517 CWE-570 Die Verwendung von if (A) {...} else if (A) {...} 'Muster wurde erkannt. Es besteht die Möglichkeit eines logischen Fehlers. Überprüfen Sie die Zeilen: 62, 65. libdhcpv6_server.c 62

    staticvoidlibdhcpv6_address_generate(....){
      ....
      if (entry->linkType == DHCPV6_DUID_HARDWARE_EUI64_TYPE) // <=
      {
        memcpy(ptr, entry->linkId, 8);
       *ptr ^= 2;
      }
      elseif (entry->linkType == DHCPV6_DUID_HARDWARE_EUI64_TYPE)// <=
      {
        *ptr++  = entry->linkId[0] ^ 2;
        *ptr++  = entry->linkId[1];
      ....
      }
    }

    Hier, wenn und sonst wenn die gleiche Bedingung geprüft wird, wird der Code im Rumpf des else if nie ausgeführt. Solche Fehler treten häufig auf, wenn Code mit der Methode " Kopieren / Einfügen " geschrieben wird .

    Besitzerloser Ausdruck


    Lassen Sie uns zum Schluss noch einen lustigen Code-Ausschnitt betrachten.

    Analyzer-Warnung: V607 Besitzerloser Ausdruck '& discover_response_tlv'. thread_discovery.c 562

    staticintthread_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);
      ....
    }

    Und nun sehen wir uns 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, d. H. Sein Aufruf in der Funktion thread_discovery_response_send wird nach der Vorverarbeitung zu einem Ausdruck (& discover_response_tlv) .

    Warten Sie was


    Ich habe keine kommentare Wahrscheinlich ist dies kein Fehler, aber ein solcher Code führt mich immer in einen Zustand, der dem Bild im Bild ähnelt :).

    Fazit


    Die Liste der in PVS-Studio unterstützten Compiler wurde erweitert. Wenn Sie über ein Projekt verfügen, das mit der GNU Arm Embedded Toolchain erstellt werden soll, sollten Sie es mit unserem Analyzer ausprobieren. Laden Sie die Demoversion hier herunter . Beachten Sie auch die kostenlose Lizenzoption , die sich für kleinere Projekte eignet.



    Wenn Sie diesen Artikel mit einem englischsprachigen Publikum teilen möchten, verwenden Sie bitte den Link zur Übersetzung: Yuri Minaev. PVS-Studio unterstützt jetzt die eingebettete GNU Arm-Toolchain .

    Jetzt auch beliebt: