本文档介绍了以下内容:
- 如何使用 Zircon 中的 Clang Static Analyzer (CSA) 设置交叉翻译单元分析 (CTU);
- Kareem Khazem 在实习期间完成的工作;以及
- Zircon 还为 CTU 完全提供支持所需的其余工作。
在 Zircon 上设置并运行 CTU
摘要:下载 Clang 的源代码,并在编译之前对其应用一些非 Mainline 补丁。围绕分析工具运行封装容器脚本。下载 CodeChecker
工具;使用该工具消化分析结果,然后启动网络服务器以通过网页界面查看结果。
支持 CTU 的补丁
有两个需要注意的补丁集:
- Samsung 补丁集,这是一个向 Clang 添加 AST 合并支持的重要补丁。它主要包含对
lib/AST/ASTImporter.cpp
的添加。tools/xtu-build/*
下还有一组用于 CTU 分析的工具(初始工具,但目前效果不佳)。此补丁集基于 Clang 的旧版本;这一事实及其庞大的大小使得非常难以将批发到树尖 (ToT) Clang 的基础上进行批发。 - Ericsson 补丁集,其中包括 Samsung AST 合并工作的一部分,并且添加了多个支持 CTU 分析的新工具(
tools/xtu-build-new/*
和tools/scan-build-py/*
)。xtu-build-new 工具在 Samsung 的 xtu 构建工具的基础上进行了改进,但略有不同。该补丁集比三星的补丁集更新得多,作者正在努力让其继续基于 ToT。
我们将使用 Ericsson 的补丁集修补 Clang,因为 AST 合并工作采用了干净的 rebase 技术,我们还将获得新的分析工具。但请注意,对 Zircon 的 CTU 支持尚不完善;在某些情况下,Samsung 补丁集包含提供所需功能的代码(详见下文)。
构建支持 CTU 的 CSA 的步骤
- 照常下载和构建 Clang 和 LLVM。
- 在一个单独的目录中,克隆 Ericsson 的 Clang 分支,并切换到 ctu-master 分支。
将此脚本下载到 Ericsson 的分支中并运行它。该工具应将一系列补丁转储到补丁目录中。我特意只转储从爱立信更改开始时的提交内容到 1bb3636,这是我实习期间的最新修订版本。
- 如果您希望在 Ericsson 中进行更新,可以尝试将脚本中的 1bb3636 更改为 HEAD。请务必在脚本中指定其他范围,以跳过将上游提交合并到 ctu-master 分支的提交。git log --graph 有助于确定上游提交与爱立信的提交内容分别是什么,我使用
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
将生成的补丁逐一应用到上游 Clang(而不是 Ericsson 分支)中。
for p in $(ls $PATCH_DIR/*.patch | sort -n); do git am < $p; done
如果尚未发布,请应用下方列出的 Kareem Khazem 的补丁
重新构建上游 Clang 和 LLVM。
进行 CTU 分析
摘要:运行我的封装容器脚本。这将正常构建 Zircon,然后重新构建,但转储序列化 AST(而不是对象文件),最后使用转储的 AST 分析每个文件,以实现 CTU。
CTU 的工作原理
首先,故事往前看:
非 CTU 静态分析会分析每个 TU 的 AST;对外部函数的任何函数调用均被视为不透明。大致上,CTU 分析会试图用该函数实现的 AST 来替换不透明的函数调用节点。
因此,CTU 分析会像往常一样开始分析 AST,但当遇到函数调用节点时,它会尝试合并到该函数的 AST。这依赖于 AST 来实现提前序列化到磁盘的函数,以便分析器可以将 AST 重新加载到内存中。它还依赖于对 AST 合并的支持,这正是 Samsung 针对 ASTImporter.cpp
的补丁(以及从中派生的 Ericsson 补丁)的用途。
为了将 AST 序列化到磁盘,我们需要模拟实际的构建流程。这种方法是在记录编译器调用的同时真正构建 Zircon;这使我们能够“播放”调用,但修改了编译器标志,用于转储 AST 文件,而不是目标文件。
简而言之,这次向前:
- 使用 Clang 构建 zircon,并将构建流程封装在 bear 等程序中,以便记录编译器调用并生成 JSON 编译数据库。
- 重放相同的编译步骤,但转储 AST 文件,而不是对象文件。这就是 xtu-build.py 工具的作用。
- 照常执行静态分析,但在需要时对每个调用的函数的 AST 进行反序列化。这就是 xtu-analyze.py 工具在顶层的作用,方法是通过爱立信团队编写的精简扫描构建替换调用 scan-build-py/libscanbuild 目录中的工具。
下文提到的紫红色封装容器就记录了这些步骤。上述所有操作的结果都是一个包含所有报告的目录(Apple plist 格式),其中包含所报告 bug 的详细信息。
Ericsson 的封装容器脚本
有两套工具可用于运行交叉翻译单元分析:
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 在一个有问题的文件上运行 CSA,就需要使用以下命令。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 报告;
- 失败目录,其中包含返回非零的分析器调用的 std{out,err};
- 一个传递目录,其中包含返回 0 的分析器调用的 std{out,err}。
查看分析结果
目前,解析 plist 报告并通过网页界面查看它们的唯一方法是使用 CodeChecker 工具,该工具是 Ericsson 开发的,可用于代码理解和许多其他任务。CodeChecker 需要安装大量依赖项,因此最好使用 pip、npm 或任何工具进行安装,而不是使用 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
上启动 Web 服务器,该服务器会显示之前运行的所有解析的报告。
获得帮助
三星补丁集由 Aleksei Sidorin 及其团队编写。Aleksei 非常了解 ASTImporter.cpp
和其他 AST 合并方面,很有帮助。他和 Sean Callanan 非常乐意查看我的 AST Importer 补丁。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 卡券中移植),SpinLockChecker 和 MutexChecker 也是如此。Clang 检查的潜在审核者包括 Devin Coughlin(来自 Apple)、Artem Dergachev(三星的 Aleksei Sidorin 团队)和 Anna Zaks(也在 Apple 团队)。
这些检查工具通常是选择启用的,这意味着您需要向分析器传递一个标记(类似于 -analyzer-checker=optin.zircon.MutexInInterruptContext
)来启用它们。
如果这些补丁未包含在 Clang 中,您将需要应用它们。要使用它们通过 Ericsson 封装容器脚本分析 Zircon,您应修改 Fuchsia 封装容器脚本,方法是将 -e optin.zircon.MutexInInterruptContext
选项添加到文件末尾的 xtu-analyze.py
调用中。MutexInInterruptContext
的补丁有一个测试套件,可用作分析功能的示例。
Zircon 对 CTU 的支持进展
AST 导入程序中修复的问题
上游 CSA 在绝大多数 Zircon 文件上发生崩溃。本部分介绍了 Kareem Khazem 遇到的一些问题及相应的解决方法。
不受支持的 AST 节点
由于未实现对导入某些类型的 AST 节点的支持,因此 Clang Static Analyzer 无法导入大量 Zircon 代码。下面列出了支持这些节点的补丁:
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 补丁集不包含任何测试,因此仍需编写测试,然后才能向上游传送对该节点的支持。
海量段错误
ASTImporter.cpp
中的很多代码都是有 bug 的。有时,Aleksei 会有一些问题的专用补丁(例如这个),因此值得在 IRC 上快速 ping 他 (a-sid)。我的调试策略是在封装容器输出中查找以 analyze: DEBUG: exec command in
开头的第二个字符串(后跟分析器的实际命令行),并通过 gdb 运行该命令行。通常只需几个小时即可查明分段故障的来源。
在 CTU 前后发现 bug
VFS 中可能出现的 bug?
这是 oldparent
的二次释放,后者在 system/ulib/fs/vfs.c:vfs_rename
上声明为未初始化。两行后,调用 vfs_walk
(同一文件),并使用 oldparent
作为其第二个参数。通过进入 for 循环并在第一个循环上点击 return r
语句,可以从 vfs_walk
返回,而不向 oldparent
赋值。如果 r
的值大于零,请前往 else if
语句,该语句对 oldparent
(仍处于未初始化状态)调用 vn_release
。
线程中可能存在 bug?
这是释放后使用。路径如下:
kernel/kernel/thread.c:thread_detach_and_resume
- 调用
thread_detach(t)
- 返回
thread_join(t, NULL, 0)
- 免费
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
)
- 系统调用(例如