Zircon 交叉翻译单元静态分析

这篇文章下面要阐述:

  • 在 Zircon 上如何使用 Clang 静态分析(CSA)来设置交叉翻译单元分析(CTU)
  • 这部分工作已经由 Kareem Khazem 在实习期间完成了,剩下的工作需要 CTU 全面支持

在 Zircon 上设置并运行 CTU

总结:下载 Clang 的源码并在编译之前使用非主分支补丁。运行分析工具的已封装好的脚本,下载 CodeChecker 工具,使用它来理解分析的结果,并启动 web 服务来显示结果的网页接口。

CTU 开启补丁

以下有2个补丁需要注意的:

  • 三星补丁 在 Clang 添加了 AST 支持的庞大补丁,对 lib/AST/ASTImporter.cpp 的补充,在 tools/xtu-build/* 下有一组工具(原始的,效果不佳)用于CTU分析工具。这个补丁是在 Clang 的旧版上。事实上,它的大小很大,要结合到最新的 Clang 分支是很困难的。
  • 爱立信 的补丁,其中包括三星 AST 合并工作的一个子集,还添加了几个允许 CTU 分析的新工具(tools/xtu-build-new/*tools/scan-build-py/*)。 xtu-build-new 工具改进了三星的 xtu-build 工具,但与三星的 xtu-build 工具有所不同。这个补丁集比三星的要新得多,作者正在努力使其重新基于 ToT(最新的分支)。

我们将使用 Ericsson 的补丁集修补 Clang,因为 AST 合并工作重新建立在干净的基础,并且我们还获得了更新的分析工具。但是,请注意 CTU 对 Zircon 的支持是不完整的;在某些情况下,三星补丁集包含提供所需功能的代码(更多详细信息见下文)。

CTU-capable CSA 构建的步骤

  1. 像往常一样下载和构建 Clang 和 LLVM

  2. 在一个单独的目录中,clone 爱立信的 Clang 分支并切换到 ctu-master 分支

  3. 下载这个脚本到爱立信的分叉并运行它。它应该将一系列补丁转储到补丁目录中。我故意把从爱立信的修改开始到我实习期间的最新修改1bb3636为止的提交内容全部删除。

    • 如果您想从 爱立信 获得更多最新更改,您可以尝试在脚本中将 1bb3636 更改为 HEAD。通过在脚本中指定其他范围,确保跳过将上游提交合并到 ctu-master 分支的提交。 git log —graph 可以帮助确定上游提交与爱立信的提交是什么,我使用

      1. git log --graph --decorate --date=relative --format=format:'%C(green)%h%C(yellow) %s%C(reset)%w(0,6,6)%C(bold green)\n%C(cyan)%G? %C(bold red)%aN%C(reset) %cr%C(reset)%w(0,0,0)\n%-D\n%C(reset)' --all
  4. 一次一个地将生成的补丁应用到 upstream Clang(不是 爱立信 fork)。

    1. for p in $(ls $PATCH_DIR/*.patch | sort -n); do git am < $p; done
  5. 应用 下面 列出的 Kareem Khazem 的补丁,如果他们还没有登陆的话

  6. 重构建最新的 Clang & LLVM

运行 CTU 分析

总结:运行我的包装脚本。这将正常构建 Zircon,然后再次构建它但转储序列化的 AST 而不是目标文件,然后最后使用转储的 AST 分析每个文件以实现 CTU

CTU如何工作的

首先,事情的背景如下:

非CTU静态分析, 分析每个TU的AST;任何对外部函数的调用都被视为不透明的。粗略地说,CTU分析试图用该函数实现的AST来替代不透明的函数调用节点。

因此,CTU分析将像往常一样开始分析一个AST,但是当它遇到一个函数调用节点时,它将尝试合并该函数的AST。这有赖于该函数的AST事先已经被序列化到磁盘上,这样分析器就可以将AST重新加载到内存中。这也依赖于对AST合并的支持,这也是三星对ASTImporter.cpp的补丁(以及由其衍生的爱立信分支)的作用。

为了将AST序列化到磁盘,我们需要模拟真实的构建过程。这样做的方法是在记录编译器调用的同时,对Zircon进行一次真正的构建;这允许我们 “回放 “调用,但要修改编译器的标志,以转储AST文件而不是对象文件。

所以总结如下:

  • 使用Clang构建zircon,并将构建过程包裹在 bear 这样的程序中,以便记录编译器的调用并生成JSON编译数据库。
  • 重放同样的编译步骤,但转储AST文件而不是对象文件。这就是 xtu-build.py 工具的作用。
  • 像往常一样进行静态分析,但在需要时对每个调用的函数的AST进行反序列化。这就是 xtu-analyze.py 工具在顶层所做的,通过爱立信团队编写的轻盈的 scan-build replacement 调用 scan-build-py/libscanbuild 目录下的工具。

这些步骤在下面提到的 Fuchsia wrapper 中有所体现。这一切的结果是一个充满报告的目录,以 Apple plist 的格式,其中包含了报告的bug的细节。

{#ericsson-wrapper-script}

爱立信封装脚本 {#ericsson-wrapper-script}

有两套工具用于运行跨单元的分析:

  • tools/xtu-build-new 下的工具是顶层的脚本。由于底层分析器可能失败(即由于CSA崩溃),我对xtu-analyze.py(在Ericsson的分支中)进行了修补,使其将分析器的输出(stdout/stderr,而不是报告)转储到一个文件中。输到$OUT_DIR/{passes,fails}中,这取决于分析器的返回代码,其中$OUT_DIR是传递给xtu-analyze.py-o参数的目录。这些文件中特别有用的部分是以analyze.py开头的第二行DEBUG: exec command in,这是由libscanbuild工具发出的(下一个要点)。该命令是在修改CSA的命令行的漫长而乏味的过程之后,对CSA的实际调用。因此,如果你想用gdb在一个有问题的文件上运行CSA,你将需要这个命令。
  • tools/scan-build-py下的工具是一个鸟巢式的工具,用来包裹对Clang的实际调用。它们负责修改命令行。我对它们不是很熟悉,而且过去也没有干涉过它们。

Fuchsia 封装的脚本{#fuchsia-wrapper-script}

这个非常小的shell脚本包装了爱立信xtu-build-new包装器。要对Zircon进行完整的分析,请确保首先进行清理,并指定你构建的Clang的正确路径。然后,在zircon目录下

  1. ninja -t clean && ninja && ./run.sh

为了只构建内核,指定一个 TARGET 作为环境变量:

  1. ninja -t clean && ninja clean && TARGET=./build-zircon-pc-x64/zircon.elf ./run.sh

该脚本还要求clangify.py在zircon目录下,并设置可执行位。分析结束后,会有一个.result-xtu目录,其中包含。

  • 一堆Apple plist文件,这些是错误报告。
  • 一个失败目录,包含了分析器调用的std{out,err},返回非零的。
  • 一个pass目录,包含分析器调用的std{out,err},返回0的。

查看分析结果

目前,解析plist报告并使用网络界面查看报告的唯一方法是使用CodeChecker工具,该工具由爱立信开发,用于代码理解和其他许多任务。CodeChecker需要安装大量的依赖项,最好用pipnpm或其他方式安装,而不是用apt-get。简而言之,在进行分析并将plist转储到.result-xtu后,你可以调用CodeChecker plist来解析plist。

  1. CodeChecker plist -d .result-xtu -n 2016-12-12T21:47_uniq_name -j 48

-n的参数在每次调用CodeChecker plist时需要是唯一的,因为它代表了一次解析运行。否则CodeChecker会报错。然后,运行CodeChecker serverlocalhost:8001上启动一个webserver,它将显示所有先前的解析运行报告。

获取帮助

三星的补丁集是由Aleksei Sidorin和他的团队编写的。Aleksei对 ASTImporter.cpp 和其他AST合并方面的知识相当了解,而且非常有帮助。他和Sean Callanan都很乐意审查我的AST Importer补丁。Aleksei还在2016年的LLVM开发者会议上做了一个相关的演讲,介绍了基于摘要的程序间分析。

爱立信的补丁集是由Gábor Horváth和他的团队编写的。Gábor在如何使用xtu-build-new工具运行CTU分析方面提供了很多建议。

我(Kareem Khazem)也很乐意在我力所能及的地方提供帮助。

LLVM的irc频道也可以提供帮助。

Zircon-specific 分析

上游Clang一直非常乐于接受针对Zircon的Clang检查器的补丁。MutexInInterruptContext检查器是一个例子(移植自Farid Molazem Tabrizi编写的LLVM程序),还有SpinLockCheckerMutexChecker。Clang检查的潜在审查者是Devin Coughlin(来自苹果)、Artem Dergachev(在三星的Aleksei Sidorin团队)和Anna Zaks(也在苹果)。

这些检查器通常是opt-in,意味着你需要给分析器传递一个标志来启用它们:比如-分析器-检查器=optin.zircon.MutexInInterruptContext

如果这些补丁还没有在Clang中出现,你将需要应用这些补丁。要使用它们来分析Ericsson wrapper scripts的Zircon,你应该修改Fuchsia wrapper script,在文件末尾的xtu-analyze.py调用中加入选项-e optin.zircon.MutexInInterruptContextMutexInInterruptContext的补丁有一个测试套件,可以作为分析能力的一个例子。

CTU 支持在Zircon上的发展

在AST导入器的问题修复

上游CSA在绝大多数的Zircon文件上都会崩溃。本节介绍了Kareem Khazem遇到的一些问题及其解决方法。

不支持 AST 节点{#zircon-patches}

Clang静态分析器无法导入大量的Zircon代码,这是因为没有实现对导入某些类型的AST节点的支持。支持这些节点的补丁在此列出。

AtomicType Patch merged into upstream
CXXDependentScopeMemberExpr https://reviews.llvm.org/D26904
UnresolvedLookupExpr https://reviews.llvm.org/D27033
DependentSizedArray  
CXXUnresolvedConstructExpr  
UsingDecl https://reviews.llvm.org/D27181
UsingShadowDecl https://reviews.llvm.org/D27181
FunctionTemplateDecl https://reviews.llvm.org/D26904

一般来说,在实现对新节点类型的支持时,必须在ASTImporter.cpp中实现VisitNode函数,还要实现单元测试和功能测试;Kareem的上述补丁包含了一些例子。仍然有不少不支持的AST节点;在分析器输出目录中搜索 “error: cannot import unsupported AST node”。

爱立信补丁集只包含了三星补丁集中 “ASTImporter “代码的一个子集。在某些情况下,不支持的节点的`Visit’功能可以直接从三星补丁集中获取。然而,Samsung补丁集不包括任何测试,所以在对该节点的支持上行之前,仍然需要编写测试。

Segfaults galore

ASTImporter.cpp中的很多代码都有错误。有时Aleksei会对问题进行私人修补,比如这个,所以值得在IRC上给他(a-sid)一个快速的ping。我的调试策略是通过包装器的输出来寻找以 “analyze “开头的第二个字符串DEBUG: exec command in(后面是分析器的实际命令行),并通过gdb运行该命令行。通常只需要几个小时就能追踪到一个segfault的来源。

在CTU之前和之后发现错误

在 VFS 可能的错误

这是对oldparent的双重释放,它在system/ulib/fs/vfs.c:vfs_rename中被声明为未初始化。两行之后,vfs_walk(同一文件)被调用,oldparent是其第二个参数。通过进入for循环并在第一个循环中点击 “return r “语句,可以从vfs_walk返回而不分配给oldparent。如果r的值大于0,那么我们就进入else if语句,在oldparent上调用vn_release(它仍然是未初始化的)。

在线程上可能出现的错误

这是使用后释放,路径如下:

  • kernel/kernel/thread.c:thread_detach_and_resume
    • Call thread_detach(t)
      • Return thread_join(t, NULL, 0)
        • free t and return NO_ERROR
      • Return NO_ERROR
    • Check for error is 1false1
    • Call thread_resume(t), which has been freed.
      • thread_resume then accesses t’s fields.
  • kernel/kernel/thread.c:thread_detach_and_resume
    • 调用 thread_detach(t)
      • 返回 thread_join(t, NULL, 0)
        • 释放 t 并返回 NO_ERROR
      • 返回 NO_ERROR
    • 检测错误是1 返回 false
    • 调用 thread_resume(t), 这时已经释放了
      • thread_resume 之后反问t 的变量

CTU 误报

  • CSA不能解决通过函数指针调用的函数的实现问题。这意味着它不能对函数的返回值作出任何假设,也不能对函数可能对输出参数产生的任何影响作出假设。
  • 有几类函数的实现是分析器无法访问的。同样,分析器不能知道这类函数触及它们的输出参数,所以它们会虚假地报告说下面的代码是从一个垃圾值中读取的。

    1. struct timeval tv;
    2. gettimeofday(&tv, NULL);
    3. printf("%d\n", tv.tv_usec); // [SPURIOUS REPORT] Access to
    4. // uninitialized variable ‘tv’
  • 容易受到这种不精确性影响的一些函数种类包括:

    • 系统调用(像 gettimeofday
    • 编译构建(像memcpy