如何以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,并在PPSSPP中查找错误。

配置Travis CI


我们将需要在GitHub上有一个需要我们所在项目的存储库,以及PVS-Studio的密钥(您可以获取试用密钥免费获得Open Source项目 )。

让我们转到Travis CI网站。 使用GitHub帐户授权后,我们将获得一个存储库列表:


为了测试,我分叉了PPSSPP。

我们激活我们要收集的存储库:


目前,Travis CI无法组装我们的项目,因为没有组装说明。 因此,是时候进行配置了。

在分析过程中,一些变量对我们有用,例如,PVS-Studio的密钥,这在配置文件中无法指定。 因此,使用Travis CI中的构建设置添加环境变量:


我们将需要:

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

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

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


现在创建一个.travis.yml文件并将其放在项目的根目录中。 Travis CI的配置文件已经存在于PPSSPP中,但是它太大了,对于示例来说是完全不合适的,因此我不得不对其进行大幅简化,只保留基本元素。

首先,我们说明语言,我们要在虚拟机中使用的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列表中安装软件包之后进行。 程序集本身在脚本中进行 。 如果一切顺利,那么我们进入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 } 

现在,我们直接设置PVS-Studio,使其在Travis CI中自动启动。 首先,我们需要在系统上安装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存储值“ 是” (在组装矩阵的配置过程中在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版本时生成的汇编程序列表:

 ; 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> 0xFFFF ||时 ,代码才会执行​​。 rightvol> 0xFFFF || leftvol <0 || rightvol <0将被证明是错误的。 因此,我们得到以下对于else分支都是正确的语句: leftvol <= 0xFFFFrightvol <= 0xFFFFleftvol> = 0rightvol> = 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的集成,并且没有进行分析仪的配置。

变量的大小由一个常量初始化,但是,直到if语句为止,它在代码中根本没有使用过,它在检查条件时当然会返回false ,因为我们记得, 大小为零。 后续检查也没有意义。

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

停下


在此上,也许我们最终会出错。 本文的目的是演示与Travis CI结合使用PVS-Studio的工作,而不是尽可能全面地分析项目。 如果您想要越来越多的美丽错误,那么可以随时在这里欣赏它们:)。

结论


结合增量分析的实践,使用Web服务来构建项目可以在代码合并后立即检测到许多问题。 的确,单个程序集可能还不够,因此将测试与静态分析结合起来可以显着提高代码质量。

有用的链接





如果您想与讲英语的读者分享这篇文章,请使用以下链接:Maxim Zvyagintsev。 如何以PSP游戏机模拟器为例在Travis CI中设置PVS-Studio

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


All Articles