如何使用 PSP 遊戲控制台模擬器的範例在 Travis CI 中配置 PVS-Studio

如何使用 PSP 遊戲控制台模擬器的範例在 Travis CI 中配置 PVS-Studio
Travis CI 是一種分散式 Web 服務,用於建置和測試使用 GitHub 作為原始碼託管的軟體。 除了上述操作場景之外,您還可以透過廣泛的配置選項來新增自己的操作場景。 在本文中,我們將使用 PPSSPP 程式碼範例配置 Travis CI 以與 PVS-Studio 配合使用。

介紹

特拉維斯CI 是用於建置和測試軟體的 Web 服務。 它通常與持續整合實踐結合使用。

PPSSPP - PSP 遊戲機模擬器。 該程式能夠模擬從適用於索尼 PSP 的磁碟映像啟動任何遊戲。 該計劃於1年2012月2日發布。 PPSSPP 根據 GPL vXNUMX 獲得許可。 任何人都可以做出改進 專案原始碼.

PVS工作室 — 靜態程式碼分析器,用於搜尋程式碼中的錯誤和潛在漏洞。 在本文中,為了進行更改,我們將不在開發人員的本機電腦上啟動 PVS-Studio,而是在雲端啟動,並在 PPSSPP 中尋找錯誤。

設定 Travis CI

我們需要 GitHub 上的儲存庫(我們需要的專案所在的位置),以及 PVS-Studio 的金鑰(您可以取得 試用金鑰免費用於開源項目).

我們去網站看看吧 特拉維斯CI。 使用您的 GitHub 帳戶授權後,我們將看到儲存庫清單:

如何使用 PSP 遊戲控制台模擬器的範例在 Travis CI 中配置 PVS-Studio
為了測試,我分叉了 PPSSPP。

我們啟動我們想要收集的儲存庫:

如何使用 PSP 遊戲控制台模擬器的範例在 Travis CI 中配置 PVS-Studio
目前,Travis CI 無法建立我們的項目,因為沒有建置說明。 所以是時候進行配置了。

在分析過程中,有些變數對我們來說是有用的,例如PVS-Studio的key,我們不希望在設定檔中指定這些變數。 因此,讓我們使用 Travis CI 中的建置設定來新增環境變數:

如何使用 PSP 遊戲控制台模擬器的範例在 Travis CI 中配置 PVS-Studio
我們需要:

  • PVS_USERNAME - 使用者名
  • PVS_KEY - 鍵
  • MAIL_USER - 將用於發送報告的電子郵件
  • MAIL_PASSWORD - 電子郵件密碼

最後兩項是可選的。 這些將用於透過郵件發送結果。 如果您想以其他方式分發報告,則無需指明。

所以,我們加入了我們需要的環境變數:

如何使用 PSP 遊戲控制台模擬器的範例在 Travis CI 中配置 PVS-Studio
現在讓我們建立一個文件 .travis.yml 並將其放置在專案的根目錄中。 PPSSPP 已經有一個 Travis CI 的配置文件,但它太大了,完全不適合這個例子,所以我們必須大大簡化它,只留下基本元素。

首先,我們指定要在虛擬機器中使用的語言、Ubuntu Linux 版本以及建置所需的軟體包:

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'

列出的所有軟體包都是 PPSSPP 專用的。

現在我們指出裝配矩陣:

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

關於該部分的更多信息 矩陣。 在Travis CI中,有兩種​​建立建置選項的方法:第一種是指定編譯器、作業系統類型、環境變數等列表,之後產生所有可能組合的矩陣; 第二個是矩陣的明確指示。 當然,您可以結合這兩種方法並添加一個獨特的案例,或者相反,使用該部分排除它 排除。 您可以閱讀有關此內容的更多信息 Travis CI 文件.

剩下的就是提供特定於項目的組裝說明:

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

Travis CI 可讓您為虛擬機器生命週期的各個階段新增自己的指令。 部分 安裝前 在安裝軟體包之前執行。 然後 安裝,它遵循列表中的軟體包的安裝 插件.apt我們在上面指出了這一點。 組裝本身發生在 腳本。 如果一切順利的話,我們就會發現自己處於 成功後 (我們將在本節中執行靜態分析)。 這些並不是所有可以修改的步驟,如果您需要更多,那麼您應該查看 Travis CI 文件.

為了便於閱讀,命令被放置在單獨的腳本中 .travis.sh,它放置在專案根目錄下。

所以我們有以下文件 .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

在安裝軟體包之前,我們將更新子模組。 這是建構 PPSSPP 所必需的。 讓我們加入第一個函數 .travis.sh (注意擴展名):

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

現在我們直接在 Travis CI 中設定 PVS-Studio 自動啟動。 首先我們需要在系統上安裝PVS-Studio軟體包:

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
}

在函數的開頭 崔維斯安裝 我們使用環境變數安裝所需的編譯器。 那麼如果變數 $PVS_ANALYZE 儲存價值 (我們在章節中指出了 ENV 在建置矩陣配置期間),我們安裝該套件 pvs工作室。 除此之外,還標明了套餐 libio-socket-ssl-perl и libnet-ssleay-perl但是,它們是郵寄結果所必需的,因此如果您選擇了其他方法來發送報告,則不需要它們。

功能 下載解壓縮 下載並解壓縮指定的檔案:

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

是時候將專案整合在一起了。 這發生在該部分 腳本:

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
}

事實上,這是一個簡化的原始配置,除了這些行:

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

在這部分程式碼中我們設定為 製作 用於導出編譯命令的標誌。 這對於靜態程式碼分析器是必需的。 您可以在文章“如何在 Linux 和 macOS 上運行 PVS-Studio“。

如果組裝成功,那麼我們就可以 成功後,我們進行靜態分析:

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
}

讓我們仔細看看以下幾行:

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

第一行會根據我們在設定 Travis CI 環境變數時指定的使用者名稱和金鑰產生授權檔案。

第二行直接開始分析。 旗幟 -j 設定用於分析的線程數、標誌 -l 表示許可證、標誌 -o 定義輸出日誌的文件,以及標誌 -禁用許可證過期檢查 試用版需要,因為預設情況下 pvs-studio-分析儀 將警告用戶許可證即將過期。 為了防止這種情況發生,您可以指定此標誌。

日誌檔案包含未經轉換就無法讀取的原始輸出,因此您必須先使該檔案可讀。 讓我們傳遞日誌 plog轉換器,輸出是一個 html 檔。

在此範例中,我決定使用以下命令透過郵件發送報告 發電子郵件.

結果我們得到瞭如下文件 .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;

現在是時候將變更推送到 git 儲存庫了,之後 Travis CI 將自動運行建置。 點擊“ppsspp”以轉到構建報告:

如何使用 PSP 遊戲控制台模擬器的範例在 Travis CI 中配置 PVS-Studio
我們將看到當前建置的概述:

如何使用 PSP 遊戲控制台模擬器的範例在 Travis CI 中配置 PVS-Studio
如果建置成功完成,我們將收到一封包含靜態分析結果的電子郵件。 當然,郵寄並不是接收報告的唯一方式。 您可以選擇任何實施方法。 但重要的是要記住,建置完成後,將無法存取虛擬機器檔案。

錯誤總結

我們已經成功地完成了最困難的部分。 現在讓我們確保我們所有的努力都是值得的。 讓我們來看看透過郵件發送給我的靜態分析報告中的一些有趣的點(我指出它並非毫無意義)。

危險的優化

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 警告: V597 編譯器可以刪除“memset”函數調用,該函數調用用於刷新“sum”緩衝區。 應使用 RtlSecureZeroMemory() 函數來擦除私有資料。 sha1.cpp 325

這段程式碼位於安全哈希模組中,但是,它包含一個嚴重的安全漏洞(CWE-14)。 讓我們來看看編譯 Debug 版本時產生的組件清單:

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

一切都井然有序且功能齊全 記憶集 被執行,從而覆蓋 RAM 中的重要數據,但是,現在還不要高興。 我們來看看Release版本最佳化後的程式集清單:

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

從清單中可以看出,編譯器忽略了調用 記憶集。 這是因為在函數中 sha1 通話後 記憶集 不再參考結構 CTX。 因此,編譯器認為浪費處理器時間來覆蓋將來不使用的記憶體是沒有意義的。 您可以使用該功能來修復此問題 RtlSecureZeroMemory 類似的 給她。

更正:

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

不必要的比較

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 警告: V547 表達式“leftvol >= 0”始終為真。 sceAudio.cpp 120

首先註意 else 分支 if。 只有在滿足所有條件時才會執行程式碼 左卷 > 0xFFFF || 右卷 > 0xFFFF || 左側體積 < 0 || 右體積 < 0 將會被證明是錯誤的。 因此,我們得到以下語句,這對 else 分支來說也是正確的: 左側體積 <= 0xFFFF, 右側體積 <= 0xFFFF, 左側體積 >= 0 и 右體積 >= 0。 注意最後兩條語句。 檢查執行這段程式碼的必要條件是什麼有意義嗎?

所以我們可以安全地刪除這些條件語句:

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

另一種情況。 這些多餘的條件背後隱藏著某種錯誤。 也許他們沒有檢查需要什麼。

Ctrl+C Ctrl+V 反擊

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 “||”的左側和右側有相同的子表達式“!Memory::IsValidAddress(psmfData)” 操作員。 scePsmf.cpp 703

注意裡面的檢查 if。 您不覺得我們檢查地址是否有效很奇怪嗎? psmf數據, 兩倍? 所以這對我來說似乎很奇怪......事實上,這當然是一個拼寫錯誤,其想法是檢查兩個輸入參數。

正確選項:

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

被遺忘的變數

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 警告: V547 表達式“size == 8”始終為 false。 同步-att.c 195

該錯誤位於資料夾中 分機,所以與專案不太相關,但在我注意到之前就發現了這個錯誤,所以我決定離開它。 畢竟本文不是要審查錯誤,而是要與 Travis CI 集成,並且沒有進行分析器的配置。

多變的 尺寸 由常數初始化,但是,在程式碼中根本沒有使用它,直到運算符 if,這當然給了 在檢查條件時,因為,正如我們所記得的, 尺寸 等於零。 後續的檢查也沒有任何意義。

顯然,程式碼片段的作者忘記覆蓋變數 尺寸 在那之前。

停止

這就是我們可能會犯錯的地方。 本文的目的是示範 PVS-Studio 與 Travis CI 一起的工作,而不是盡可能徹底分析這個專案。 如果你想要更大更漂亮的錯誤,你總是可以欣賞它們 這裡 :)。

結論

使用Web服務建立項目,再加上增量分析的實踐,可以讓你在合併程式碼後立即發現很多問題。 然而,一次建置可能還不夠,因此將測試與靜態分析一起設定將顯著提高程式碼的品質。

有用的鏈接

如何使用 PSP 遊戲控制台模擬器的範例在 Travis CI 中配置 PVS-Studio

如果您想與英語讀者分享這篇文章,請使用翻譯鏈接:Maxim Zvyagintsev。 如何使用 PSP 遊戲機模擬器在 Travis CI 中設定 PVS-Studio.

來源: www.habr.com

添加評論