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