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

PPSSPP

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

Einführung


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

PPSSPP ist ein Emulator der PSP-Spielekonsole. Das Programm kann den Start jedes Spiels mit Bildern von Discs emulieren, die für Sony PSP entwickelt wurden. Das Programm wurde am 1. November 2012 veröffentlicht. PPSSPP wird unter der GPL v2-Lizenz vertrieben. Jeder kann den Quellcode des Projekts verbessern.

PVS-Studio - Static Code Analyzer zur Suche nach Fehlern und potenziellen Schwachstellen im Programmcode. In diesem Artikel werden wir PVS-Studio in der Cloud anstatt lokal auf dem Computer des Entwicklers für eine Vielzahl von Zwecken starten und nach Fehlern in PPSSPP suchen.

Travis ci hat sich eingerichtet


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

Gehen wir zur Travis CI- Site. Nach der Autorisierung mit Hilfe des GitHub-Kontos haben wir eine Liste der Repositorys:



Für den Test habe ich eine PPSSPP-Gabel gemacht.

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



Im Moment kann Travis CI unser Projekt nicht erstellen, da es keine Anweisungen zum Erstellen gibt. Deshalb ist es Zeit für die Konfiguration.

Während der Analyse benötigen wir einige Variablen, beispielsweise den Schlüssel für PVS-Studio, dessen Angabe in der Konfigurationsdatei unerwünscht wäre. Fügen wir also Umgebungsvariablen hinzu, indem wir den Build in Travis CI konfigurieren:



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:


Jetzt erstellen wir eine .travis.yml- Datei und legen sie im Stammverzeichnis des Projekts ab. PPSSPP hatte bereits eine Konfigurationsdatei für Travis CI, diese war jedoch zu groß und für das Beispiel nicht geeignet. Daher mussten wir sie vereinfachen und nur die Grundelemente belassen.

Geben Sie zunächst die Programmiersprache, die Version von Ubuntu Linux, die wir auf der virtuellen Maschine verwenden möchten, und die zum Erstellen erforderlichen Pakete an:

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 hinzugefügten Pakete werden nur für PPSSPP benötigt.

Geben Sie nun die Gebäudematrix 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, Compiler, Betriebssystemtypen, Umgebungsvariablen usw. anzugeben. mit der Liste, nach der die Matrix aller möglichen Kombinationen erzeugt wird; 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 ihn im Gegenteil mithilfe des Ausschlussabschnitts ausschließen . Weitere Informationen hierzu finden Sie in der Travis CI-Dokumentation .

Sie müssen nur noch projektspezifische Build-Anweisungen angeben:

 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 Befehle für verschiedene Phasen der Lebensdauer virtueller Maschinen hinzufügen. Der Abschnitt before_install wird vor der Installation der Pakete ausgeführt. Installieren Sie dann , was auf die Installation der Pakete aus der oben angegebenen Liste addons.apt folgt. Der Build selbst erfolgt im Skript . Wenn alles erfolgreich war, gelangen wir zu after_success (hier beginnen wir mit der statischen Analyse). Dies sind nicht alle Schritte, die Sie ändern können. Wenn Sie weitere benötigen, lesen Sie die Dokumentation zu Travis CI .

Zum besseren Lesen wurden die Befehle in ein separates Skript .travis.sh eingefügt , das sich im Stammverzeichnis des Projekts befindet.

Wir haben also die folgende Datei .travis.yml :

 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 

Lassen Sie uns vor der Installation der Pakete die Submodule aktualisieren. Dies ist erforderlich, um PPSSPPs zu erstellen. Fügen Sie die erste Funktion zu .travis.sh hinzu (beachten Sie die Erweiterung):

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

Jetzt haben wir direkt den automatischen Start von PVS-Studio in Travis CI eingerichtet. Zuerst müssen wir das PVS-Studio-Paket im 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 die Variable $ PVS_ANALYZE den Wert Yes speichert (wir haben ihn beim Konfigurieren der Build-Matrix im Abschnitt env angegeben), installieren wir das Paket pvs-studio . Daneben gibt es auch die Pakete libio-socket-ssl-perl und libnet-ssleay-perl. Sie werden jedoch benötigt, um die Ergebnisse per E-Mail zu senden. Sie sind daher nicht erforderlich, wenn Sie eine andere Art der Berichtszustellung 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 aufzubauen. 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 Abschnitt des Codes setzen wir das Exportflag des Kompilierungsbefehls für cmake . Dies ist für einen statischen Code-Analysator erforderlich. Weitere Informationen finden Sie im Artikel " Starten von PVS-Studio unter Linux und MacOS ".

Wenn der Build erfolgreich war, gelangen wir zu after_success, wo wir eine statische Analyse ausfü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 im Detail:

 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 die Lizenzdatei aus dem Benutzernamen und dem Schlüssel generiert, die wir zu Beginn der Konfiguration der Travis CI-Umgebungsvariablen angegeben haben.

Die zweite Zeile startet die Analyse direkt. Das Flag -j <N> legt die Anzahl der Analysethreads fest, das Flag -l <Datei> legt die Lizenz fest, das Flag -o <Datei> legt die Datei für die Ausgabe der Protokolle fest und das Flag- disableLicenseExpirationCheck ist für Testversionen erforderlich , da pvs-studio-analyzer den Benutzer standardmäßig vor dem bevorstehenden Ablauf der Lizenz warnt. Um dies zu verhindern, können Sie dieses Flag angeben.

Die Protokolldatei enthält eine unverarbeitete Ausgabe, die ohne Konvertierung nicht gelesen werden kann. Daher müssen Sie zuerst die Datei lesbar machen. Lassen Sie uns die Protokolle durch plog-converter ausführen und eine HTML-Datei an der Ausgabe erhalten.

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

Das Ergebnis war die folgende .travis.sh-Datei :

 #/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, und dann startet Travis CI automatisch den Build. Klicken Sie auf "ppsspp", um Berichte zu erstellen:


Wir werden einen Überblick über den aktuellen Build sehen:



Wenn der Build erfolgreich abgeschlossen wurde, erhalten wir eine E-Mail mit den Ergebnissen der statischen Analyse. Natürlich ist das Senden per Post nicht der einzige Weg, um den Bericht zu erhalten. Sie können eine beliebige Implementierungsmethode auswählen. Es ist jedoch wichtig zu beachten, dass nach Abschluss des Builds kein Zugriff auf die Dateien der virtuellen Maschine möglich ist.

Kurzer Überblick über Fehler


Wir haben den schwierigsten Teil erfolgreich abgeschlossen. Stellen wir jetzt sicher, dass alle unsere Bemühungen gerechtfertigt sind. Betrachten wir einige interessante Punkte aus dem statischen Analysebericht, der mir per E-Mail zugestellt wurde (nicht umsonst habe ich ihn angegeben).

Gefährliche Optimierungen


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

Die 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 einen schwerwiegenden Sicherheitsfehler ( CWE-14 ). Betrachten wir 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 Ordnung und die Memset- Funktion wird ausgeführt, wodurch wichtige Daten im RAM gelöscht werden, aber Sie sollten noch nicht froh sein. Betrachten wir die Assembler-Liste der Release-Version mit Optimierung:
 ; 354 : ; 355 : memset( sum, 0, sizeof( sum ) ); ; 356 :} 

Wie Sie der Auflistung entnehmen können, hat der Compiler den Aufruf von memset ignoriert. Dies hängt damit zusammen, dass die sha1- Funktion die ctx- Struktur nach dem Aufruf von memset nicht mehr aufruft. Aus diesem Grund sieht der Compiler keinen Sinn darin, Prozessorzeit für das Überschreiben von Speicher zu verschwenden, der in Zukunft nicht mehr verwendet wird. Sie können das Problem mithilfe der Funktion RtlSecureZeroMemory oder einer ähnlichen Funktion 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); } } 

Die PVS-Studio-Warnung: V547 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 links> 0xFFFFF || sind rightvol> 0xFFFF || leftvol <0 || rightvol <0 sind falsch. Daher erhalten wir die folgenden Aussagen, die für den else-Zweig zutreffen : leftvol <= 0xFFFFF, rightvol <= 0xFFFFF, leftvol> = 0 und rightvol> = 0 . Beachten Sie die letzten beiden Aussagen. Ist es sinnvoll zu überprüfen, welche Bedingung für die Ausführung dieses Codefragments erforderlich ist?

So können wir diese bedingten Operatoren ruhig löschen:

 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 liegt ein Fehler. Vielleicht haben wir überprüft, was wir nicht brauchen ...

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

Beachten Sie die Prüfung im Inneren, wenn . Kommt es Ihnen nicht seltsam vor, dass wir prüfen, ob die psmfData- Adresse doppelt so gültig ist? Ich finde es also seltsam ... Eigentlich haben wir natürlich einen Druckfehler vor uns, und die Idee war, beide Eingabeparameter zu überprüfen.

Die richtige Variante ist:

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

Die PVS-Studio-Warnung: V547 Ausdruck 'Größe == 8' ist immer falsch. syn-att.c 195

Dieser Fehler befindet sich im ext- Ordner, sodass er nicht wirklich für das Projekt gilt. Der Fehler wurde jedoch gefunden, bevor ich ihn bemerkte. Deshalb habe ich beschlossen, ihn beizubehalten. In diesem Artikel geht es jedoch nicht um die Fehlerüberprüfung, sondern um die Integration in Travis CI, und es wurde keine Analysatorkonfiguration durchgeführt.

Die Größenvariable wird mit einer Konstanten initialisiert, aber im Code bis zum if- Operator, der natürlich falsche Informationen generiert, während die Bedingung überprüft wird, überhaupt nicht verwendet, da die Größe , wie wir uns erinnern, gleich Null ist. Nachfolgende Überprüfungen sind ebenfalls nicht sinnvoll.

Anscheinend hat der Autor des Codefragments vergessen, die Größenvariable zuvor zu überschreiben.

Hör auf


Hier hören wir mit den Fehlern auf. Der Zweck dieses Artikels ist es, zu demonstrieren, wie PVS-Studio mit Travis CI funktioniert, und das Projekt nicht so gründlich wie möglich zu analysieren. Wenn Sie größere und schönere Fehler wünschen, können Sie diese hier immer sehen :).

Fazit


Durch die Verwendung von Webdiensten zum Erstellen von Projekten zusammen mit der inkrementellen Analysepraxis können Sie viele Probleme direkt nach dem Zusammenführen des Codes erkennen. Ein Build reicht jedoch möglicherweise nicht aus. Wenn Sie also Tests zusammen mit statischen Analysen einrichten, wird die Codequalität erheblich verbessert.

Nützliche Links


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


All Articles