PVS-Studio instellen in Travis CI met de PSP-emulator als voorbeeld

PVS-Studio instellen in Travis CI met de PSP-emulator als voorbeeld
Travis CI is een gedistribueerde webservice voor het bouwen en testen van software die GitHub gebruikt als broncodehosting. Naast de bovenstaande bedieningsscenario's kunt u uw eigen scenario's toevoegen dankzij de uitgebreide configuratiemogelijkheden. In dit artikel zullen we Travis CI configureren om met PVS-Studio te werken met behulp van het PPSSPP-codevoorbeeld.

Introductie

Travis CI is een webservice voor het bouwen en testen van software. Het wordt meestal gebruikt in combinatie met continue integratiepraktijken.

PPSSPP - PSP-gameconsole-emulator. Het programma kan de lancering van alle games emuleren vanaf schijfimages bedoeld voor Sony PSP. Het programma werd uitgebracht op 1 november 2012. PPSSPP is gelicentieerd onder GPL v2. Iedereen kan verbeteringen aanbrengen broncode van het project.

PVS Studio — een statische code-analysator voor het zoeken naar fouten en potentiële kwetsbaarheden in programmacode. In dit artikel starten we PVS-Studio voor de verandering niet lokaal op de machine van de ontwikkelaar, maar in de cloud, en zoeken we naar fouten in PPSSPP.

Travis CI instellen

We hebben een repository op GitHub nodig, waar het project dat we nodig hebben zich bevindt, evenals een sleutel voor PVS-Studio (je kunt deze krijgen proefsleutel of gratis voor Open Source-projecten).

Laten we naar de site gaan Travis CI. Na autorisatie met uw GitHub-account zien we een lijst met repositories:

PVS-Studio instellen in Travis CI met de PSP-emulator als voorbeeld
Voor de test heb ik PPSSPP gevorkt.

We activeren de repository die we willen verzamelen:

PVS-Studio instellen in Travis CI met de PSP-emulator als voorbeeld
Op dit moment kan Travis CI ons project niet bouwen omdat er geen bouwinstructies zijn. Het is dus tijd voor configuratie.

Tijdens de analyse zullen sommige variabelen nuttig voor ons zijn, bijvoorbeeld de sleutel voor PVS-Studio, die niet wenselijk zou zijn om in het configuratiebestand op te geven. Laten we dus omgevingsvariabelen toevoegen met behulp van de build-instellingen in Travis CI:

PVS-Studio instellen in Travis CI met de PSP-emulator als voorbeeld
Wij hebben nodig:

  • PVS_USERNAME - gebruikersnaam
  • PVS_KEY - sleutel
  • MAIL_USER - e-mailadres dat wordt gebruikt om het rapport te verzenden
  • MAIL_PASSWORD - e-mailwachtwoord

De laatste twee zijn optioneel. Deze worden gebruikt om de resultaten per e-mail te verzenden. Als u het rapport op een andere manier wilt verspreiden, hoeft u dit niet aan te geven.

We hebben dus de omgevingsvariabelen toegevoegd die we nodig hebben:

PVS-Studio instellen in Travis CI met de PSP-emulator als voorbeeld
Laten we nu een bestand maken .travis.yml en plaats het in de root van het project. PPSSPP had al een configuratiebestand voor Travis CI, maar dit was te groot en volkomen ongeschikt voor het voorbeeld, dus moesten we het sterk vereenvoudigen en alleen de basiselementen overlaten.

Laten we eerst de taal, de versie van Ubuntu Linux die we in de virtuele machine willen gebruiken, en de benodigde pakketten voor de build aangeven:

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 vermelde pakketten zijn uitsluitend nodig voor PPSSPP.

Nu geven we de assemblagematrix aan:

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

Nog even over de rubriek Matrix. In Travis CI zijn er twee manieren om bouwopties te creëren: de eerste is het specificeren van een lijst met compilers, besturingssysteemtypen, omgevingsvariabelen, enz., waarna een matrix van alle mogelijke combinaties wordt gegenereerd; de tweede is een expliciete indicatie van de matrix. Natuurlijk kunt u deze twee benaderingen combineren en een uniek geval toevoegen, of juist uitsluiten met behulp van de sectie uitsluiten. Meer hierover leest u in Travis CI-documentatie.

Het enige dat overblijft is het verstrekken van projectspecifieke montage-instructies:

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

Met Travis CI kunt u uw eigen opdrachten toevoegen voor verschillende fasen van de levensduur van een virtuele machine. Sectie voor_installatie uitgevoerd voordat pakketten worden geïnstalleerd. Dan installeren, die volgt op de installatie van pakketten uit de lijst addons.aptdie we hierboven hebben aangegeven. De montage zelf vindt plaats in script. Als alles goed is gegaan, zijn we binnen na_succes (in deze sectie zullen we een statische analyse uitvoeren). Dit zijn niet alle stappen die aangepast kunnen worden, als je er meer nodig hebt, dan moet je even kijken Travis CI-documentatie.

Voor het leesgemak zijn de opdrachten in een apart script geplaatst .travis.sh, die in de hoofdmap van het project wordt geplaatst.

We hebben dus het volgende bestand .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

Voordat we de pakketten installeren, zullen we de submodules bijwerken. Dit is nodig om PPSSPP te bouwen. Laten we de eerste functie toevoegen aan .travis.sh (let op de extensie):

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

Nu komen we direct bij het instellen van de automatische lancering van PVS-Studio in Travis CI. Eerst moeten we het PVS-Studio-pakket op het systeem installeren:

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
}

Aan het begin van de functie travis_install we installeren de compilers die we nodig hebben met behulp van omgevingsvariabelen. Dan als de variabele $PVS_ANALYZE slaat waarde op Ja (we hebben dit aangegeven in de sectie env tijdens de build-matrixconfiguratie), installeren we het pakket pvs-studio. Daarnaast worden er ook pakketten aangegeven libio-socket-ssl-perl и libnet-ssleay-perlZe zijn echter vereist voor het verzenden van resultaten en zijn dus niet nodig als u een andere methode hebt gekozen voor het aanleveren van uw rapport.

Functie download_extract downloadt en pakt het opgegeven archief uit:

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

Het is tijd om het project samen te stellen. Dit gebeurt in de sectie script:

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
}

In feite is dit een vereenvoudigde originele configuratie, behalve deze regels:

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

In dit codegedeelte gaan we aan de slag cmake flag voor het exporteren van compilatieopdrachten. Dit is nodig voor een statische code-analysator. Meer hierover leest u in het artikel “Hoe PVS-Studio op Linux en macOS te draaien".

Als de montage succesvol was, dan komen we na_succes, waar we statische analyses uitvoeren:

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 [email protected] 
              -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
}

Laten we de volgende regels eens nader bekijken:

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

De eerste regel genereert een licentiebestand op basis van de gebruikersnaam en sleutel die we helemaal aan het begin hebben opgegeven bij het instellen van de Travis CI-omgevingsvariabelen.

De tweede regel start de analyse direct. Vlag -J stelt het aantal threads in voor analyse, flag -l geeft licentie, vlag aan -O definieert het bestand voor het uitvoeren van logboeken en de vlag -LicenseExpirationCheck uitschakelen standaard vereist voor proefversies pvs-studio-analyzer waarschuwt de gebruiker dat de licentie bijna verloopt. Om dit te voorkomen, kunt u deze vlag opgeven.

Het logbestand bevat onbewerkte uitvoer die zonder conversie niet kan worden gelezen, dus u moet het bestand eerst leesbaar maken. Laten we de logboeken doorgeven plog-converter, en de uitvoer is een HTML-bestand.

In dit voorbeeld heb ik besloten om rapporten per e-mail te verzenden met behulp van de opdracht e-mail sturen.

Als resultaat kregen we het volgende bestand .travis.sh:

#/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 [email protected] 
              -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;

Nu is het tijd om de wijzigingen naar de git-repository te pushen, waarna Travis CI de build automatisch zal uitvoeren. Klik op “ppsspp” om naar de buildrapporten te gaan:

PVS-Studio instellen in Travis CI met de PSP-emulator als voorbeeld
We zullen een overzicht zien van de huidige build:

PVS-Studio instellen in Travis CI met de PSP-emulator als voorbeeld
Als de build succesvol is voltooid, ontvangen we een e-mail met de resultaten van de statische analyse. Uiteraard is mailen niet de enige manier om een ​​rapport te ontvangen. U kunt elke implementatiemethode kiezen. Maar het is belangrijk om te onthouden dat nadat de build is voltooid, het niet meer mogelijk is om toegang te krijgen tot de bestanden van de virtuele machine.

Foutoverzicht

Het moeilijkste deel hebben we met succes afgerond. Laten we er nu voor zorgen dat al onze inspanningen de moeite waard zijn. Laten we eens kijken naar enkele interessante punten uit het statische analyserapport dat per post naar mij toekwam (ik heb het niet voor niets aangegeven).

Gevaarlijke optimalisatie

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 waarschuwing: V597 De compiler kan de functieaanroep 'memset' verwijderen, die wordt gebruikt om de 'sum'-buffer leeg te maken. De functie RtlSecureZeroMemory() moet worden gebruikt om de privégegevens te wissen. sha1.cpp 325

Dit stukje code bevindt zich in de beveiligde hashing-module, maar bevat een ernstig beveiligingsprobleem (CWE-14). Laten we eens kijken naar de assembly-lijst die wordt gegenereerd bij het compileren van de Debug-versie:

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

Alles is in orde en de functie memeset wordt uitgevoerd, waardoor belangrijke gegevens in het RAM worden overschreven, maar u hoeft zich nog niet te verheugen. Laten we eens kijken naar de assemblagelijst van de releaseversie met optimalisatie:

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

Zoals uit de lijst blijkt, negeerde de compiler de oproep memeset. Dit komt door het feit dat in de functie sha1 na de oproep memeset geen verwijzing meer naar structuur ctx. Daarom heeft de compiler geen zin in het verspillen van processortijd met het overschrijven van geheugen dat in de toekomst niet wordt gebruikt. U kunt dit oplossen door de functie te gebruiken RtlSecureZeroMemory of een soortgelijke haar.

corrigeren:

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

Onnodige vergelijking

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 waarschuwing: V547 Expressie 'leftvol >= 0' is altijd waar. sceAudio.cpp 120

Besteed eerst aandacht aan de else-tak if. De code wordt alleen uitgevoerd als aan alle voorwaarden is voldaan leftvol > 0xFFFF || rechtsvol > 0xFFFF || linksvol < 0 || rechtsvol < 0 zal vals blijken te zijn. Daarom krijgen we de volgende uitspraken, die waar zullen zijn voor de else-vertakking: leftvol <= 0xFFFF, rechtsvol <= 0xFFFF, restvol >= 0 и rechtsvol >= 0. Let op de laatste twee uitspraken. Heeft het zin om te controleren wat een noodzakelijke voorwaarde is voor de uitvoering van dit stukje code?

We kunnen deze voorwaardelijke uitspraken dus veilig verwijderen:

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

Een ander scenario. Er schuilt een fout achter deze overbodige voorwaarden. Misschien hebben ze niet gecontroleerd wat er nodig was.

Ctrl+C Ctrl+V slaat terug

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 Er zijn identieke subexpressies '!Memory::IsValidAddress(psmfData)' links en rechts van de '||' exploitant. scePsmf.cpp 703

Let op de cheque binnenin if. Vindt u het niet vreemd dat wij controleren of het adres geldig is? psmfData, twee keer zoveel? Dit lijkt mij dus vreemd... In feite is dit natuurlijk een typefout, en het idee was om beide invoerparameters te controleren.

Juiste optie:

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

Vergeten variabele

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 waarschuwing: V547 Expressie 'grootte == 8' is altijd onwaar. syn-att.c 195

Deze fout bevindt zich in de map ext, dus niet echt relevant voor het project, maar de bug werd gevonden voordat ik hem merkte, dus besloot ik hem te verlaten. Dit artikel gaat immers niet over het beoordelen van fouten, maar over de integratie met Travis CI, en er is geen configuratie van de analyser uitgevoerd.

Variabel grootte wordt geïnitialiseerd door een constante, maar wordt helemaal niet gebruikt in de code, zelfs niet door de operator if, wat natuurlijk geeft vals terwijl we de voorwaarden controleren, omdat, zoals we ons herinneren, grootte gelijk aan nul. Controles achteraf hebben ook geen zin.

Blijkbaar heeft de auteur van het codefragment vergeten de variabele te overschrijven grootte daarvoor.

stop

Dit is waar we waarschijnlijk zullen eindigen met de fouten. Het doel van dit artikel is om het werk van PVS-Studio samen met Travis CI te demonstreren, en niet om het project zo grondig mogelijk te analyseren. Als je grotere en mooiere fouten wilt, kun je ze altijd bewonderen hier :).

Conclusie

Door webservices te gebruiken om projecten te bouwen in combinatie met de praktijk van incrementele analyse, kun je veel problemen direct na het samenvoegen van code ontdekken. Eén build is echter mogelijk niet genoeg, dus het opzetten van testen in combinatie met statische analyse zal de kwaliteit van de code aanzienlijk verbeteren.

Nuttige links

PVS-Studio instellen in Travis CI met de PSP-emulator als voorbeeld

Als u dit artikel wilt delen met een Engelssprekend publiek, gebruik dan de vertaallink: Maxim Zvyagintsev. Hoe PVS-Studio in Travis CI in te stellen met behulp van het voorbeeld van de PSP-gameconsole-emulator.

Bron: www.habr.com

Voeg een reactie