Zircon 中的跨翻译单元静态分析

本文档介绍了以下内容:

  • 如何使用 Zircon 中的 Clang 静态分析器 (CSA) 设置跨翻译单元分析 (CTU);
  • Kareem Khazem 在实习期间完成的工作;和
  • 为了使 CTU 在 Zircon 上获得完全支持,您还需完成其他工作。

在 Zircon 上设置并运行 CTU

摘要:下载 Clang 源代码,并在编译之前向其应用多个非 Mainline 补丁。围绕分析工具运行封装容器脚本。下载 CodeChecker 工具;用它来汇总分析结果,并启动 Web 服务器,通过网页界面查看结果。

支持 CTU 的补丁

您需要注意以下两个补丁集:

  • Samsung 补丁集,这是一个向 Clang 添加 AST 合并支持的大型补丁。它主要包含对 lib/AST/ASTImporter.cpp 的附加内容。tools/xtu-build/* 下还有一组(原始,但不太好用)用于 CTU 分析的工具。此补丁集基于 Clang 的旧版本;事实以及它的体积较大,使得我们很难将批量 rebase 到树尖 (ToT) Clang 上。
  • Ericsson 补丁集,其中包含部分 Samsung AST 合并工作,还添加了几种新工具(tools/xtu-build-new/*tools/scan-build-py/*),用于进行 CTU 分析。xtu-build-new 工具是对 Samsung 的 xtu-build 工具的改进,而且与 Samsung 的 xtu-build 工具有些不同。该补丁集比三星的补丁集更新得多,作者正努力使其重新基于 ToT。

我们将使用 Ericsson 的补丁集修补 Clang,因为 AST 合并工作会整洁有序地执行 rebase 操作,并且我们还获得了更新的分析工具。但请注意,我们尚未对 Zircon 提供 CTU 支持;在某些情况下,Samsung 补丁集包含可提供所需功能的代码(详见下文)。

制作支持 CTU 的 CSA 的步骤

  1. 照常下载和构建 Clang 和 LLVM。
  2. 在单独的目录中,克隆 Ericsson 的 Clang 分支并切换到 ctu-master 分支。
  3. 此脚本下载到 Ericsson 的分支中并运行它。它应将一系列补丁转储到补丁目录中。我特意只转储从 Ericsson 更改开始直到 1bb3636(这是实习期间的最新版本)期间的提交内容。

    • 如果您想从 Ericsson 获取更多最新更改,可以尝试在脚本中将 1bb3636 更改为 HEAD。请务必通过在脚本中指定其他范围来跳过将上游提交合并到 ctu-master 分支的提交。git log --graph 可帮助确定上游提交与 Ericsson 提交分别是什么,我使用
    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. 将生成的补丁逐一应用于上游 Clang(不是 Ericsson 分支)

    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 合并的支持,而 Samsung 对 ASTImporter.cpp 的补丁(以及由此派生的 Ericsson 补丁)的目的就是实现 AST 合并。

为了将 AST 序列化到磁盘,我们需要模拟实际的构建流程。为此,可以在记录编译器调用时实际进行 Zircon 的构建;这样我们就可以“回放”调用,但需要修改编译器标志,以转储 AST 文件而非对象文件。

总而言之,接下来我们要转发一下:

  • 使用 Clang 构建 zircon,并将构建流程封装在 bear 等程序中,以记录编译器调用并生成 JSON 编译数据库。
  • 重放相同的编译步骤,但转储 AST 文件,而非对象文件。这就是 xtu-build.py 工具的作用。
  • 照常执行静态分析,但需要时会对每个调用的函数的 AST 进行反序列化。这就是 xtu-analyze.py 工具在顶层执行的操作:通过爱立信团队编写的精简扫描构建替换,调用 scan-build-py/libscanbuild 目录中的工具。

下文提到的 Fuchsia 封装容器中提供了这些步骤。以上操作的结果是一个目录,其中满是 Apple plist 格式的报告,其中包含所报告 bug 的详细信息。

爱立信的封装容器脚本

有两组工具可用于运行跨翻译单元分析:

  • tools/xtu-build-new 下的工具是顶级脚本。由于底层分析器可能会失败(即由于 CSA 崩溃),我修补了 xtu-analyze.py(位于 Ericsson 的分支中),使其将分析器的输出(stdout/stderr,而不是报告)转储到一个文件。输出位于 $OUT_DIR/{passes,fails} 中,具体取决于分析器的返回代码,其中 $OUT_DIR 是传递给 xtu-analyze.py-o 参数的目录。这些文件中特别有用的部分是以 analyze: DEBUG: exec command in 开头的第二行,该行由 libscanbuild 工具发出(下一个要点)。在修改命令行的漫长而繁琐的过程之后,对 CSA 进行实际调用。因此,如果您要使用 gdb 对有问题的文件运行自定义搜索广告,就需要使用此命令。
  • tools/scan-build-py 下的工具是封装对 Clang 的实际调用的通用工具。他们负责修改命令行。我对他们不太熟悉,也从来没有干扰过他们。

Fuchsia 封装容器脚本

这个非常小的 Shell 脚本封装了 Ericsson xtu-build-new 封装容器。如需对 Zircon 进行全面分析,请务必先进行清理,并指定 Clang 构建的正确路径。然后,在 zircon 目录中:

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

为了仅构建内核,请将 TARGET 指定为环境变量:

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

该脚本还要求 clangify.py 位于 zircon 目录中并设置了可执行位。分析完成后,会有一个 .result-xtu 目录,其中包含:

  • 大量 Apple plist 文件,即 bug 报告;
  • 失败目录,其中包含返回非零的分析器调用的标准 {out,err};
  • 一个传递目录,其中包含返回 0 的分析器调用的 std{out,err} 值。

查看分析结果

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

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

每次调用 CodeChecker plist 时,-n 的参数都必须是唯一的,因为它表示一次解析运行。否则,CodeChecker 会指出错误。然后,运行 CodeChecker server 以在 localhost:8001 上启动网络服务器,该服务器将显示之前所有解析运行的报告。

获得帮助

Samsung 补丁集由 Aleksei Sidorin 及其团队编写。Aleksei 非常了解 ASTImporter.cpp 和其他 AST 合并方面,非常有用。他和 Sean Callanan 非常乐意查看我的 AST 导入程序补丁。Aleksei 还在 2016 年 LLVM 开发者会议上就基于摘要的交互过程分析发表了相关演讲

Ericsson 补丁集由 Gábor Horváth 及其团队编写。Gábor 提供了有关如何使用 xtu-build-new 工具运行 CTU 分析的建议,非常有帮助。

我 (Kareem Khazem) 也非常乐意竭力为您提供帮助。

LLVM irc 通道也很有用。

锆石特定分析

上游 Clang 一直非常愿意接收适用于 Zircon 专用 Clang 检查工具的补丁程序。MutexInInterruptContext 检查工具就是一个示例(移植自 Farid Molazem Tabrizi 编写的 LLVM 通行证),SpinLockCheckerMutexChecker 也是如此。Clang 检查的潜在审核者为 Devin Coughlin(来自 Apple)、Artem Dergachev(来自三星的 Aleksei Sidorin 团队)和 Anna Zaks(也在 Apple)。

这些检查工具通常是可选择启用的,这意味着您需要向分析器传递一个标志才能启用这些检查工具:类似于 -analyzer-checker=optin.zircon.MutexInInterruptContext

如果这些补丁尚未发布到 Clang 中,您需要应用它们。若要使用它们通过 Ericsson 封装容器脚本分析 Zircon,您应修改 Fuchsia 封装容器脚本,方法是在文件末尾对 xtu-analyze.py 的调用中添加 -e optin.zircon.MutexInInterruptContext 选项。MutexInInterruptContext 的补丁包含一个测试套件,可用作分析功能的示例。

Zircon 中的 CTU 支持进度

AST 导入程序中修复的问题

上游 CSA 在绝大多数 Zircon 文件中崩溃。本部分介绍了 Kareem Khazem 遇到的一些问题及其修复方法。

不支持的 AST 节点

Clang Static Analyzer 无法导入大量 Zircon 代码,因为未实现对导入特定类型的 AST 节点的支持。下面列出了用于支持这些节点的补丁:

AtomicType 补丁已合并到上游
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 节点:grep 查找 error: cannot import unsupported AST node 的分析器输出目录。

Ericsson 补丁集仅包含 Samsung 补丁集中一部分 ASTImporter 代码。在某些情况下,不受支持的节点的 Visit 函数可以直接取自 Samsung 补丁集。不过,Samsung 补丁集不包含任何测试,因此仍然需要在上游为该节点提供支持之前编写测试。

段众多的故障

ASTImporter.cpp 中的很多代码都存在 bug。有时,Aleksei 会针对一些问题(例如这个问题)提供专用补丁,因此有必要在 IRC 中快速给他 (a-sid) 发送 ping 命令。我的调试策略是查看第二个字符串的封装容器输出,其中以 analyze: DEBUG: exec command in 开头(后跟分析器的实际命令行),然后通过 gdb 运行该命令行。跟踪分段错误的来源通常只需几个小时即可。

CTU 前后发现的错误

VFS 中可能存在 bug?

这是 oldparent(在 system/ulib/fs/vfs.c:vfs_rename 上声明未初始化)的双重释放。两行后,调用 vfs_walk(同一文件),并将 oldparent 作为其第二个参数。通过进入 for 循环并在第一个循环上点击 return r 语句,可以在不分配给 oldparent 的情况下从 vfs_walk 返回。如果 r 的值大于零,则转到 else if 语句,该语句会对 oldparent(仍未初始化)调用 vn_release

线程中可能出现 bug?

这是释放后使用。路径为:

  • kernel/kernel/thread.c:thread_detach_and_resume
    • 致电thread_detach(t) <ph type="x-smartling-placeholder">
        </ph>
      • 返航时间:thread_join(t, NULL, 0) <ph type="x-smartling-placeholder">
          </ph>
        • 免费t,退货NO_ERROR
      • 返回 NO_ERROR
    • 检查错误是否为 1false1
    • 调用已释放的 thread_resume(t)
      • 然后,thread_resume 会访问 t 的字段。

CTU 假正例

  • CSA 无法解析通过函数指针调用的函数的实现。这意味着它不能对函数的返回值做任何假设,也不能对函数可能对输出参数产生任何影响做任何假设。
  • 分析器无法访问各类函数的实现。同样,分析器无法得知此类函数会触及其输出参数,因此会错误地报告以下代码从垃圾回收值读取:

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

    • 系统调用(例如 gettimeofday
    • 编译器内置函数(如 memcpy