如何使用PSP游戏机模拟器的示例在Travis CI中设置PVS-Studio

聚苯硫醚

Travis CI是用于构建和测试软件的分布式Web服务,该服务使用GitHub作为源代码托管服务。 除了上述脚本,您还可以添加自己的脚本,这要归功于广泛的配置选项。 在本文中,我们将通过PPSSPP代码示例将Travis CI设置为可与PVS-Studio一起使用。

引言


Travis CI是用于构建和测试软件的Web服务。 它通常与持续集成的实践结合使用。

PPSSPP是PSP游戏机的模拟器。 该程序可以使用为Sony PSP设计的光盘映像来模拟任何游戏的启动。 该程序于2012年11月1日发布。PPSSPP是根据GPL v2许可发行的。 任何人都可以对项目的源代码进行改进。

PVS-Studio-静态代码分析器,用于搜索程序代码中的错误和潜在漏洞。 在本文中,出于各种目的,我们将在云中启动PVS-Studio,而不是在开发人员的计算机上本地启动PVS-Studio,并将搜索PPSSPP中的错误。

Travis ci成立


我们将需要在GitHub上找到我们需要的项目所在的存储库,以及PVS-Studio的密钥(您可以为开放源代码项目获得一个试用密钥免费的 密钥 )。

让我们去Travis CI网站。 在GitHub帐户的帮助下进行授权后,我们将获得一个存储库列表:



为了进行测试,我制作了一个PPSSPP叉子。

我们激活要构建的存储库:



目前,Travis CI无法构建我们的项目,因为没有有关构建它的说明。 这就是为什么要进行配置的原因。

在分析过程中,我们将需要一些变量,例如PVS-Studio的密钥,这在配置文件中无法指定。 因此,让我们通过在Travis CI中配置构建来添加环境变量:



我们将需要:

  • PVS_USERNAME-用户名
  • PVS_KEY-键
  • MAIL_USER-将用于发送报告的电子邮件
  • MAIL_PASSWORD-电子邮件密码

最后两个是可选的。 它们将用于通过邮件发送结果。 如果要以其他方式发送报告,则无需指定它们。

因此,我们添加了所需的环境变量:


现在,让我们创建一个.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允许您为虚拟机生命的不同阶段添加自己的命令。 在安装软件包之前, before_install节运行。 然后安装 ,这将在上面指定的addons.apt列表中安装软件包。 构建本身在script中进行 。 如果一切都成功,那么我们进入after_success (在这里我们将开始静态分析)。 这些不是您可以修改的所有步骤,如果需要更多步骤,则应查看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 } 

travis_install函数的开头,我们使用环境变量安装所需的编译器。 然后,如果$ PVS_ANALYZE变量存储了Yes的值(我们在配置构建矩阵时在env部分中指定了它),则将安装pvs-studio软件包。 除此之外,还有libio-socket-ssl-perllibnet-ssleay-perl软件包,但是通过邮件发送结果是必需的,因此,如果您选择了另一种报告传递方式,则不需要它们。

download_extract函数下载并解压缩指定的归档文件:

 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 

在代码的这一部分,我们将编译命令export标志设置为cmake 。 这对于静态代码分析器是必需的。 您可以在文章“ 如何在Linux和macOS中启动PVS-Studio ”中阅读有关它的更多信息。

如果构建成功,我们将进入after_success ,在此我们将运行静态分析:

 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 } 

让我们详细考虑以下几行:

 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 <N>设置分析线程数,标志-l <file>设置许可证,标志-o <file>设置文件以输出日志,标志-disableLicenseExpirationCheck对于试用版是必需的,因为默认情况下pvs-studio-analyzer会警告用户许可证即将到期。 为防止这种情况发生,可以指定此标志。

日志文件包含未经处理的输出,未经转换就无法读取,因此首先需要使文件可读。 让我们通过plog-converter运行日志,并在输出中获取一个html文件。

在此示例中,我决定使用sendemail命令通过邮件发送报告。

结果是以下.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 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; 

现在是将更改添加到git存储库的时候了,然后Travis CI将自动开始构建。 单击“ ppsspp”以生成报告:


我们将看到当前版本的概述:



如果构建成功完成,我们将收到一封包含静态分析结果的电子邮件。 当然,通过邮件发送不是获取报告的唯一方法。 您可以选择任何实现方法。 但是重要的是要记住,构建完成后将不可能访问虚拟机的文件。

错误简要概述


我们已经成功完成了最困难的部分。 现在让我们确保我们所有的努力都是合理的。 让我们考虑一下通过邮件发送给我的静态分析报告中的一些有趣的观点(我没有特别指出)。

危险的优化


 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编译器可以删除“ 内存集 ”函数调用,该函数调用用于刷新“求和”缓冲区。 RtlSecureZeroMemory()函数应用于擦除私有数据。 sha1.cpp 325

此代码片段位于安全哈希模块中,但其中包含严重的安全缺陷( CWE-14 )。 让我们考虑一下Debug-version编译时生成的汇编列表:

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

一切都很好,并且执行了memset函数,从而擦除了RAM中的重要数据,但是您不应该感到高兴。 让我们考虑经过优化的发行版的汇编程序列表:
 ; 354 : ; 355 : memset( sum, 0, sizeof( sum ) ); ; 356 :} 

从清单中可以看到,编译器忽略了memset的调用。 这与以下事实有关: sha1函数在调用memset之后不再调用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'始终为true。 sceAudio.cpp 120

注意第一个if的else分支。 仅当所有条件leftvol> 0xFFFFF ||时 ,代码才会执行​​。 rightvol> 0xFFFF || leftvol <0 || rightvol <0为假。 因此,我们得到以下对于else分支正确的语句: leftvol <= 0xFFFFF,rightvol <= 0xFFFFF,leftvol> = 0和rightvol> = 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

注意是否检查内部。 您正在检查psmfData地址是否是有效地址的两倍,对您来说似乎并不奇怪吗? 因此,我觉得很奇怪……实际上,当然,我们摆错了标签,其想法是检查两个输入参数。

正确的变体是:

 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。 195

该错误位于ext文件夹中,因此它实际上并不适用于项目,但是在我注意到该错误之前就已发现该错误,因此我决定保留此错误。 仍然,本文不是关于错误检查,而是关于与Travis CI的集成,并且没有执行分析器配置。

size变量是用常量初始化的,但是直到if运算符为止,它在代码中根本没有使用过,它当然会在检查条件时生成错误信息,因为我们记得, size等于零。 后续检查也没有意义。

显然,代码片段的作者之前忘记了覆盖size变量。

停下


那就是我们要停止错误的地方。 本文的目的是演示PVS-Studio如何与Travis CI一起使用,而不是尽可能全面地分析项目。 如果您想要更大,更漂亮的错误,可以随时在这里查看它们:)。

结论


将Web服务与增量分析实践一起用于构建项目,可以使您在代码合并后立即检测到许多问题。 但是,一个构建可能还不够,因此将测试与静态分析一起设置将大大提高代码质量。

有用的链接


Source: https://habr.com/ru/post/zh-CN464641/


All Articles