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 构建的步骤
像往常一样下载和构建 Clang 和 LLVM
在一个单独的目录中,clone 爱立信的 Clang 分支并切换到 ctu-master 分支
下载这个脚本到爱立信的分叉并运行它。它应该将一系列补丁转储到补丁目录中。我故意把从爱立信的修改开始到我实习期间的最新修改1bb3636为止的提交内容全部删除。
如果您想从 爱立信 获得更多最新更改,您可以尝试在脚本中将 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
一次一个地将生成的补丁应用到 upstream Clang(不是 爱立信 fork)。
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合并的支持,这也是三星对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目录下
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文件,这些是错误报告。
- 一个失败目录,包含了分析器调用的std{out,err},返回非零的。
- 一个pass目录,包含分析器调用的std{out,err},返回0的。
查看分析结果
目前,解析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
-n
的参数在每次调用CodeChecker plist
时需要是唯一的,因为它代表了一次解析运行。否则CodeChecker会报错。然后,运行CodeChecker server
在localhost: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程序),还有SpinLockChecker和MutexChecker。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.MutexInInterruptContext
。MutexInInterruptContext
的补丁有一个测试套件,可以作为分析能力的一个例子。
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 returnNO_ERROR
- free
- Return
NO_ERROR
- Return
- Check for error is 1false1
- Call
thread_resume(t)
, which has been freed.thread_resume
then accessest
’s fields.
- Call
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不能解决通过函数指针调用的函数的实现问题。这意味着它不能对函数的返回值作出任何假设,也不能对函数可能对输出参数产生的任何影响作出假设。
有几类函数的实现是分析器无法访问的。同样,分析器不能知道这类函数触及它们的输出参数,所以它们会虚假地报告说下面的代码是从一个垃圾值中读取的。
struct timeval tv;
gettimeofday(&tv, NULL);
printf("%d\n", tv.tv_usec); // [SPURIOUS REPORT] Access to
// uninitialized variable ‘tv’
容易受到这种不精确性影响的一些函数种类包括:
- 系统调用(像
gettimeofday
) - 编译构建(像
memcpy
)
- 系统调用(像