So konfigurieren Sie PVS-Studio in Travis CI am Beispiel des PSP-Spielekonsolenemulators

PPSSPP

Travis CI ist ein verteilter Webdienst zum Erstellen und Testen von Software, der GitHub als Quellcode-Hosting verwendet. Zusätzlich zu den oben genannten Szenarien können Sie dank der umfangreichen Konfigurationsoptionen Ihre eigenen hinzufügen. In diesem Artikel konfigurieren wir Travis CI für die Arbeit mit PVS-Studio anhand des Beispiel-PPSSPP-Codes.

Einführung


Travis CI ist ein Webdienst zum Erstellen und Testen von Software. Es wird normalerweise in Verbindung mit der Praxis der kontinuierlichen Integration verwendet.

PPSSPP - PSP-Spielekonsolenemulator. Das Programm kann den Start von Spielen von Disk-Images emulieren, die für Sony PSP entwickelt wurden. Das Programm wurde am 1. November 2012 veröffentlicht. PPSSPP ist unter der GPL v2 lizenziert. Jeder kann den Quellcode des Projekts verbessern.

PVS-Studio ist ein statischer Code-Analysator zur Suche nach Fehlern und potenziellen Schwachstellen im Programmcode. In diesem Artikel werden wir zur Abwechslung PVS-Studio nicht lokal auf dem Computer des Entwicklers, sondern in der Cloud starten und nach Fehlern in PPSSPP suchen.

Konfigurieren Sie Travis CI


Wir benötigen ein Repository auf GitHub, in dem das von uns benötigte Projekt liegt, sowie einen Schlüssel für PVS-Studio (Sie können einen Testschlüssel erhalten oder kostenlos für Open Source-Projekte ).

Gehen wir zur Travis CI- Website. Nach der Autorisierung über das GitHub-Konto haben wir eine Liste der Repositorys:


Für den Test habe ich PPSSPP gegabelt.

Wir aktivieren das Repository, das wir sammeln möchten:


Derzeit kann Travis CI unser Projekt nicht zusammenbauen, da keine Montageanleitung vorliegt. Daher ist es Zeit für die Konfiguration.

Während der Analyse sind einige Variablen für uns nützlich, beispielsweise der Schlüssel für PVS-Studio, dessen Angabe in der Konfigurationsdatei unerwünscht wäre. Fügen Sie also Umgebungsvariablen mithilfe der Build-Einstellungen in Travis CI hinzu:


Wir werden brauchen:

  • PVS_USERNAME - Benutzername
  • PVS_KEY - Schlüssel
  • MAIL_USER - E-Mail, die zum Senden des Berichts verwendet wird
  • MAIL_PASSWORD - E-Mail-Passwort

Die letzten beiden sind optional. Sie werden verwendet, um die Ergebnisse per Post zu senden. Wenn Sie den Bericht auf andere Weise senden möchten, müssen Sie sie nicht angeben.

Also haben wir die Umgebungsvariablen hinzugefügt, die wir brauchen:


Erstellen Sie nun eine .travis.yml- Datei und legen Sie sie im Stammverzeichnis des Projekts ab. Die Konfigurationsdatei für Travis CI war bereits in PPSSPP vorhanden, sie war jedoch zu groß und für das Beispiel völlig ungeeignet, sodass ich sie erheblich vereinfachen und nur die Grundelemente belassen musste.

Zunächst geben wir die Sprache, die Version von Ubuntu Linux an, die wir in der virtuellen Maschine verwenden möchten, und die erforderlichen Pakete für die Assembly:

language: cpp dist: xenial addons: apt: update: true packages: - ant - aria2 - build-essential - cmake - libgl1-mesa-dev - libglu1-mesa-dev - libsdl2-dev - pv - sendemail - software-properties-common sources: - sourceline: 'ppa:ubuntu-toolchain-r/test' - sourceline: 'ppa:ubuntu-sdk-team/ppa' 

Alle aufgeführten Pakete gelten nur für PPSSPP.

Geben Sie nun die Baugruppenmatrix an:

 matrix: include: - os: linux compiler: "gcc" env: PPSSPP_BUILD_TYPE=Linux PVS_ANALYZE=Yes - os: linux compiler: "clang" env: PPSSPP_BUILD_TYPE=Linux 

Ein bisschen mehr über den Matrixabschnitt . In Travis CI gibt es zwei Möglichkeiten, Build-Optionen zu erstellen: Die erste besteht darin, die Compiler, Betriebssystemtypen, Umgebungsvariablen usw. aufzulisten. Anschließend wird eine Matrix aller möglichen Kombinationen generiert. Die zweite ist eine explizite Angabe der Matrix. Natürlich können Sie diese beiden Ansätze kombinieren und einen eindeutigen Fall hinzufügen oder alternativ mithilfe des Ausschlussabschnitts ausschließen . Weitere Informationen hierzu finden Sie in der Travis CI-Dokumentation .

Es bleibt die projektspezifische Montageanleitung zu spezifizieren:

 before_install: - travis_retry bash .travis.sh travis_before_install install: - travis_retry bash .travis.sh travis_install script: - bash .travis.sh travis_script after_success: - bash .travis.sh travis_after_success 

Mit Travis CI können Sie Ihre eigenen Teams für verschiedene Phasen des Lebens einer virtuellen Maschine hinzufügen. Der Abschnitt before_install wird ausgeführt, bevor Pakete installiert werden. Installieren Sie dann die Installation von Paketen aus der oben angegebenen Liste addons.apt . Die Montage selbst erfolgt in einem Skript . Wenn alles gut gegangen ist, gelangen wir zu after_success (in diesem Abschnitt führen wir eine statische Analyse durch). Dies sind nicht alle Schritte, die geändert werden können. Wenn Sie mehr benötigen, sollten Sie in der Travis CI-Dokumentation nachsehen.

Zur Vereinfachung des Lesens wurden die Befehle in ein separates Skript .travis.sh verschoben , das sich im Stammverzeichnis des Projekts befindet.

Wir haben also die folgende .travis.yml- Datei:

 language: cpp dist: xenial addons: apt: update: true packages: - ant - aria2 - build-essential - cmake - libgl1-mesa-dev - libglu1-mesa-dev - libsdl2-dev - pv - sendemail - software-properties-common sources: - sourceline: 'ppa:ubuntu-toolchain-r/test' - sourceline: 'ppa:ubuntu-sdk-team/ppa' matrix: include: - os: linux compiler: "gcc" env: PVS_ANALYZE=Yes - os: linux compiler: "clang" before_install: - travis_retry bash .travis.sh travis_before_install install: - travis_retry bash .travis.sh travis_install script: - bash .travis.sh travis_script after_success: - bash .travis.sh travis_after_success 

Aktualisieren Sie vor der Installation der Pakete die Submodule. Dies ist erforderlich, um PPSSPP zu erstellen. Fügen Sie die erste Funktion zu .travis.sh hinzu (achten Sie auf die Erweiterung):

 travis_before_install() { git submodule update --init --recursive } 

Jetzt haben wir PVS-Studio so eingerichtet, dass es automatisch in Travis CI gestartet wird. Zuerst müssen wir das PVS-Studio-Paket auf dem System installieren:

 travis_install() { if [ "$CXX" = "g++" ]; then sudo apt-get install -qq g++-4.8 fi if [ "$PVS_ANALYZE" = "Yes" ]; then wget -q -O - https://files.viva64.com/etc/pubkey.txt \ | sudo apt-key add - sudo wget -O /etc/apt/sources.list.d/viva64.list \ https://files.viva64.com/etc/viva64.list sudo apt-get update -qq sudo apt-get install -qq pvs-studio \ libio-socket-ssl-perl \ libnet-ssleay-perl fi download_extract \ "https://cmake.org/files/v3.6/cmake-3.6.2-Linux-x86_64.tar.gz" \ cmake-3.6.2-Linux-x86_64.tar.gz } 

Zu Beginn der Funktion travis_install installieren wir die benötigten Compiler mithilfe von Umgebungsvariablen. Wenn dann die Variable $ PVS_ANALYZE den Wert Yes speichert (wir haben ihn während der Konfiguration der Assembly-Matrix im Abschnitt env angegeben), installieren wir das Paket pvs-studio . Darüber hinaus werden auch die Pakete libio-socket-ssl-perl und libnet-ssleay-perl angegeben. Sie werden jedoch zum Senden der Ergebnisse per E-Mail benötigt, sodass sie nicht erforderlich sind, wenn Sie eine andere Methode für die Zustellung von Berichten gewählt haben.

Die Funktion download_extract lädt das angegebene Archiv herunter und entpackt es:

 download_extract() { aria2c -x 16 $1 -o $2 tar -xf $2 } 

Es ist Zeit, ein Projekt zusammenzustellen. Dies geschieht im Skriptabschnitt :

 travis_script() { if [ -d cmake-3.6.2-Linux-x86_64 ]; then export PATH=$(pwd)/cmake-3.6.2-Linux-x86_64/bin:$PATH fi CMAKE_ARGS="-DHEADLESS=ON ${CMAKE_ARGS}" if [ "$PVS_ANALYZE" = "Yes" ]; then CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}" fi cmake $CMAKE_ARGS CMakeLists.txt make } 

Tatsächlich handelt es sich um eine vereinfachte ursprüngliche Konfiguration, mit Ausnahme der folgenden Zeilen:

 if [ "$PVS_ANALYZE" = "Yes" ]; then CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}" fi 

In diesem Codeabschnitt setzen wir das Exportflag der Kompilierungsbefehle für cmake . Dies ist für einen statischen Code-Analysator erforderlich. Weitere Informationen hierzu finden Sie im Artikel " Ausführen von PVS-Studio unter Linux und MacOS ".

Wenn die Montage erfolgreich war, befinden wir uns in after_success , wo wir eine statische Analyse durchführen:

 travis_after_success() { if [ "$PVS_ANALYZE" = "Yes" ]; then pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic \ -o PVS-Studio-${CC}.log \ --disableLicenseExpirationCheck plog-converter -t html PVS-Studio-${CC}.log -o PVS-Studio-${CC}.html sendemail -t mail@domain.com \ -u "PVS-Studio $CC report, commit:$TRAVIS_COMMIT" \ -m "PVS-Studio $CC report, commit:$TRAVIS_COMMIT" \ -s smtp.gmail.com:587 \ -xu $MAIL_USER \ -xp $MAIL_PASSWORD \ -o tls=yes \ -f $MAIL_USER \ -a PVS-Studio-${CC}.log PVS-Studio-${CC}.html fi } 

Betrachten wir die folgenden Zeilen genauer:

 pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic \ -o PVS-Studio-${CC}.log \ --disableLicenseExpirationCheck plog-converter -t html PVS-Studio-${CC}.log -o PVS-Studio-${CC}.html 

In der ersten Zeile wird eine Lizenzdatei aus dem Benutzernamen und dem Schlüssel generiert, die wir zu Beginn beim Einrichten der Travis CI-Umgebungsvariablen angegeben haben.

Die zweite Zeile startet die Analyse direkt. Das Flag -j <N> legt die Anzahl der zu analysierenden Streams fest, das Flag -l <Datei> gibt die Lizenz an, das Flag -o <Datei> definiert die Datei für die Protokollausgabe und das Flag -disableLicenseExpirationCheck ist für Testversionen erforderlich, da standardmäßig pvs- Studio-Analyzer warnt den Benutzer vor dem Ablauf der Lizenz. Um dies zu verhindern, können Sie dieses Flag angeben.

Die Protokolldatei enthält eine Rohausgabe, die ohne Konvertierung nicht gelesen werden kann. Sie müssen die Datei daher zuerst lesbar machen. Wir überspringen die Protokolle durch den Plog-Konverter und die Ausgabe ist eine HTML-Datei.

In diesem Beispiel habe ich beschlossen, Berichte per E-Mail mit dem Befehl sendemail zu senden.

Als Ergebnis haben wir die folgende .travis.sh- Datei erhalten:

 #/bin/bash travis_before_install() { git submodule update --init --recursive } download_extract() { aria2c -x 16 $1 -o $2 tar -xf $2 } travis_install() { if [ "$CXX" = "g++" ]; then sudo apt-get install -qq g++-4.8 fi if [ "$PVS_ANALYZE" = "Yes" ]; then wget -q -O - https://files.viva64.com/etc/pubkey.txt \ | sudo apt-key add - sudo wget -O /etc/apt/sources.list.d/viva64.list \ https://files.viva64.com/etc/viva64.list sudo apt-get update -qq sudo apt-get install -qq pvs-studio \ libio-socket-ssl-perl \ libnet-ssleay-perl fi download_extract \ "https://cmake.org/files/v3.6/cmake-3.6.2-Linux-x86_64.tar.gz" \ cmake-3.6.2-Linux-x86_64.tar.gz } travis_script() { if [ -d cmake-3.6.2-Linux-x86_64 ]; then export PATH=$(pwd)/cmake-3.6.2-Linux-x86_64/bin:$PATH fi CMAKE_ARGS="-DHEADLESS=ON ${CMAKE_ARGS}" if [ "$PVS_ANALYZE" = "Yes" ]; then CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}" fi cmake $CMAKE_ARGS CMakeLists.txt make } travis_after_success() { if [ "$PVS_ANALYZE" = "Yes" ]; then pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic \ -o PVS-Studio-${CC}.log \ --disableLicenseExpirationCheck plog-converter -t html PVS-Studio-${CC}.log -o PVS-Studio-${CC}.html sendemail -t mail@domain.com \ -u "PVS-Studio $CC report, commit:$TRAVIS_COMMIT" \ -m "PVS-Studio $CC report, commit:$TRAVIS_COMMIT" \ -s smtp.gmail.com:587 \ -xu $MAIL_USER \ -xp $MAIL_PASSWORD \ -o tls=yes \ -f $MAIL_USER \ -a PVS-Studio-${CC}.log PVS-Studio-${CC}.html fi } set -e set -x $1; 

Es ist Zeit, die Änderungen zum Git-Repository hinzuzufügen. Danach startet Travis CI den Build automatisch. Klicken Sie auf "ppsspp", um zu den Montageberichten zu gelangen:


Wir werden einen Überblick über die aktuelle Versammlung sehen:


Bei erfolgreichem Abschluss der Montage erhalten wir per Post einen Brief mit den Ergebnissen der statischen Analyse. Mailing ist natürlich nicht die einzige Möglichkeit, einen Bericht zu erhalten. Sie können eine beliebige Implementierungsmethode auswählen. Beachten Sie jedoch, dass nach Abschluss der Assembly kein Zugriff auf die Dateien der virtuellen Maschine möglich ist.

Zusammenfassung der Fehler


Wir haben den schwierigsten Teil erfolgreich abgeschlossen. Stellen wir jetzt sicher, dass alle unsere Bemühungen gerechtfertigt sind. Betrachten Sie einige interessante Punkte aus dem Bericht über statische Analysen, der mir per Post zugestellt wurde (nicht umsonst, auf den ich hingewiesen habe).

Gefährliche Optimierung


 void sha1( unsigned char *input, int ilen, unsigned char output[20] ) { sha1_context ctx; sha1_starts( &ctx ); sha1_update( &ctx, input, ilen ); sha1_finish( &ctx, output ); memset( &ctx, 0, sizeof( sha1_context ) ); } 

PVS-Studio Warnung: V597 Der Compiler könnte den Funktionsaufruf 'memset' löschen, mit dem der 'Summen'-Puffer geleert wird. Die Funktion RtlSecureZeroMemory () sollte verwendet werden, um die privaten Daten zu löschen. sha1.cpp 325

Dieses Codefragment befindet sich im sicheren Hashing-Modul, enthält jedoch eine schwerwiegende Sicherheitslücke ( CWE-14 ). Betrachten Sie die Assembler-Liste, die beim Kompilieren der Debug-Version generiert wird:

 ; Line 355 mov r8d, 20 xor edx, edx lea rcx, QWORD PTR sum$[rsp] call memset ; Line 356 

Alles ist in perfekter Reihenfolge, und die Memset- Funktion wird ausgeführt, wodurch wichtige Daten im RAM überschrieben werden. Freuen Sie sich jedoch bisher nicht. Betrachten Sie die Assembler-Liste der Release-Version mit Optimierung:

 ; 354 : ; 355 : memset( sum, 0, sizeof( sum ) ); ; 356 :} 

Wie aus der Auflistung hervorgeht, hat der Compiler den Memset- Aufruf ignoriert. Dies liegt daran, dass die Funktion sha1 die ctx- Struktur nach dem Aufruf von memset nicht mehr aufruft. Daher sieht der Compiler nicht den Sinn, Prozessorzeit damit zu verschwenden, nicht verwendeten Speicher in Zukunft zu überschreiben. Sie können dies mithilfe der Funktion RtlSecureZeroMemory oder ähnlichem beheben.

Richtig:

 void sha1( unsigned char *input, int ilen, unsigned char output[20] ) { sha1_context ctx; sha1_starts( &ctx ); sha1_update( &ctx, input, ilen ); sha1_finish( &ctx, output ); RtlSecureZeroMemory(&ctx, sizeof( sha1_context ) ); } 

Unnötiger Vergleich


 static u32 sceAudioOutputPannedBlocking (u32 chan, int leftvol, int rightvol, u32 samplePtr) { int result = 0; // For some reason, this is the only one that checks for negative. if (leftvol > 0xFFFF || rightvol > 0xFFFF || leftvol < 0 || rightvol < 0) { .... } else { if (leftvol >= 0) { chans[chan].leftVolume = leftvol; } if (rightvol >= 0) { chans[chan].rightVolume = rightvol; } chans[chan].sampleAddress = samplePtr; result = __AudioEnqueue(chans[chan], chan, true); } } 

PVS-Studio- Warnung : V547 Der Ausdruck 'leftvol> = 0' ist immer wahr. sceAudio.cpp 120

Achten Sie beim ersten Wenn auf den Zweig else. Der Code wird nur ausgeführt, wenn alle Bedingungen leftvol> 0xFFFF || sind rightvol> 0xFFFF || leftvol <0 || rightvol <0 wird sich als falsch herausstellen. Daher erhalten wir die folgenden Aussagen, die für den else-Zweig zutreffen : leftvol <= 0xFFFF , rightvol <= 0xFFFF , leftvol> = 0 und rightvol> = 0 . Beachten Sie die letzten beiden Aussagen. Ist es sinnvoll zu überprüfen, was eine Voraussetzung für die Ausführung dieses Codes ist?

So können wir diese bedingten Aussagen ruhig entfernen:

 static u32 sceAudioOutputPannedBlocking (u32 chan, int leftvol, int rightvol, u32 samplePtr) { int result = 0; // For some reason, this is the only one that checks for negative. if (leftvol > 0xFFFF || rightvol > 0xFFFF || leftvol < 0 || rightvol < 0) { .... } else { chans[chan].leftVolume = leftvol; chans[chan].rightVolume = rightvol; chans[chan].sampleAddress = samplePtr; result = __AudioEnqueue(chans[chan], chan, true); } } 

Ein anderes Szenario. Hinter diesen redundanten Bedingungen steckt eine Art Fehler. Vielleicht haben sie nicht überprüft, was erforderlich ist.

Strg + C Strg + V schlägt zurück


 static u32 scePsmfSetPsmf(u32 psmfStruct, u32 psmfData) { if (!Memory::IsValidAddress(psmfData) || !Memory::IsValidAddress(psmfData)) { return hleReportError(ME, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad address"); } .... } 

V501 Links und rechts vom '||' befinden sich identische Unterausdrücke '! Memory :: IsValidAddress (psmfData)'. Betreiber. scePsmf.cpp 703

Achten Sie auf den Scheck im Inneren, wenn . Kommt es Ihnen nicht seltsam vor, dass wir prüfen, ob die Adresse psmfData doppelt so gültig ist ? Es kommt mir also seltsam vor ... Tatsächlich haben wir natürlich einen Tippfehler, und die Idee war, beide Eingabeparameter zu überprüfen.

Die richtige Option:

 static u32 scePsmfSetPsmf(u32 psmfStruct, u32 psmfData) { if (!Memory::IsValidAddress(psmfStruct) || !Memory::IsValidAddress(psmfData)) { return hleReportError(ME, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad address"); } .... } 

Vergessene Variable


 extern void ud_translate_att( int size = 0; .... if (size == 8) { ud_asmprintf(u, "b"); } else if (size == 16) { ud_asmprintf(u, "w"); } else if (size == 64) { ud_asmprintf(u, "q"); } .... } 

PVS-Studio- Warnung : V547 Der Ausdruck 'size == 8' ist immer falsch. syn-att.c 195

Dieser Fehler befindet sich im Ordner " ext " und gilt daher nicht für das Projekt. Der Fehler wurde jedoch gefunden, bevor ich dies bemerkte. Daher habe ich beschlossen, ihn zu verlassen. In diesem Artikel geht es jedoch nicht um eine Überprüfung von Fehlern, sondern um die Integration mit Travis CI, und es wurde keine Konfiguration des Analysators durchgeführt.

Die Variablengröße wird durch eine Konstante initialisiert, wird jedoch im Code bis zur if-Anweisung , die bei der Überprüfung der Bedingung natürlich false zurückgibt, überhaupt nicht verwendet, da die Größe , wie wir uns erinnern, natürlich Null ist. Nachfolgende Überprüfungen sind ebenfalls nicht sinnvoll.

Anscheinend hat der Autor des Codefragments vergessen, die Variablengröße vorher zu überschreiben.

Hör auf


Vielleicht haben wir hier Fehler. Der Zweck dieses Artikels ist es, die Arbeit von PVS-Studio in Verbindung mit Travis CI zu demonstrieren und das Projekt nicht so gründlich wie möglich zu analysieren. Wenn Sie immer schönere Fehler wollen, können Sie sie hier immer bewundern :).

Fazit


Die Verwendung von Webdiensten zum Erstellen von Projekten in Verbindung mit der Praxis der inkrementellen Analyse kann viele Probleme unmittelbar nach dem Zusammenführen von Code erkennen. Eine einzelne Baugruppe reicht möglicherweise nicht aus. Wenn Sie also Tests in Verbindung mit statischen Analysen einrichten, wird die Qualität des Codes erheblich verbessert.

Nützliche Links





Wenn Sie diesen Artikel einem englischsprachigen Publikum zugänglich machen möchten, verwenden Sie bitte den Link zur Übersetzung: Maxim Zvyagintsev. So richten Sie PVS-Studio in Travis CI am Beispiel des PSP-Spielekonsolenemulators ein .

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


All Articles