本文档介绍了以下内容:
- 如何使用 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 的步骤
- 照常下载和构建 Clang 和 LLVM。
- 在单独的目录中,克隆 Ericsson 的 Clang 分支并切换到 ctu-master 分支。
将此脚本下载到 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
将生成的补丁逐一应用于上游 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 合并。
为了将 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 需要安装大量依赖项,最好使用 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
上启动网络服务器,该服务器将显示之前所有解析运行的报告。
获得帮助
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 通行证),SpinLockChecker 和 MutexChecker 也是如此。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
)
- 系统调用(例如