Clang Static Analyzer 系列(一)编译 Clang 及运行 Checker

童冠玉
2023-12-01

编译 Clang

CSA (Clang Static Analyzer) 是 clang 的一部分。建议使用自行编译的 clang ,源码在 llvm/llvm-project (github.com) 上获取。

编译 clang 前首先要生成 clang 的编译脚本。在生成 clang 的编译脚本时通常需要设置如下几个参数:

  • -S 源代码路径

  • -B 生成的编译脚本放置的目录

  • -G 编译工具的选择,对应生成不同工具需要的编译脚本,如 ninja ,默认是 make 和 cmake

  • -DLLVM_ENABLE_PROJECTS 说明要编译的子项目,如 clang 、 clang-tools-extra 等,默认是 all

  • -DCMAKE_BUILD_TYPE 说明编译的类型,如 Debug 、 Release 等,默认是 Debug

  • -CMAKE_INSTALL_PREFIX 类似于 configure 脚本的 –prefix ,用于配合 INSTALL 指令( make install )使用,指定安装路径

  • -DCMAKE_CXX_COMPILERDCMAKE_C_COMPILER 指定使用的编译器,如 clang 或 gcc

使用 cmake 的例子1:

cd llvm-project
mkdir build
cd build
cmake -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra" -DCMAKE_BUILD_TYPE=Release -G "Unix Makefiles" ../llvm
cmake -DLLVM_ENABLE_PROJECTS=clang -G "Unix Makefiles" ../llvm
make

使用 cmake 的例子2:

git clone --depth 1 --branch llvmorg-12.0.1 https://github.com/llvm/llvm-project.git
cmake -S llvm-project/llvm -B llvm-project/build \
        -DCMAKE_BUILD_TYPE=Release \
        -DLLVM_ENABLE_PROJECTS=all \
        -DCMAKE_CXX_COMPILER=clang++ \
        -DCMAKE_C_COMPILER=clang
cmake --build llvm-project/build -j8
cmake --install llvm-project/build --prefix /usr/local  # or somewhere else

使用 ninja 的例子:

cd llvm-project
mkdir build
cd build
cmake -S ../llvm -B build -G Ninja -DLLVM_ENABLE_PROJECTS="clang;libcxx;libcxxabi" -DCMAKE_INSTALL_PREFIX=/home/xxx/llvm_project/build_tmp
cd build
ninja && ninja install

最终我的版本:

cd llvm-project
mkdir installed
# 编译 Debug 版本的 clang 和 clang-tools-extra ,中间编译脚本存储在 build ,生成的可执行程序包安装在 installed
cmake -S llvm -B build -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang -DCMAKE_INSTALL_PREFIX=$PWD/installed
# 使用 build 目录中的内容进行编译
cmake --build build -j8
# 安装到前面 DCMAKE_INSTALL_PREFIX 指定的目录
# 试过 cmake --install build --prefix installed 不成功
make install
# 把生成的 clang 等可执行文件路径设置到环境变量中
export PATH=/xxx/llvm-project/installed/bin/:$PATH

参考:

Clang - Getting Started (llvm.org)

LLVM(1)-编译自己的LLVM和Clang - 掘金 (juejin.cn)

打印 AST CFG CallGraph ExplodedGraph 等信息

拥有 clang 的可执行文件后,我们可以先试一下输出代码的基础信息,如 AST 、 CFG 、 CallGraph 和 ExplodedGraph 。其中 AST 并非 CSA 提供的功能,后面三种图都可以通过指定 CSA 的 Checker 进行输出。 ExplodedGraph 是 CSA 中最重要的概念,包含了代码的执行路径和程序状态。这里需要注意使用 debug 开头的 Checker 需要 clang 是 Debug 版本,也就是如果想要输出 CFG 、 CallGraph 和 ExplodedGraph 信息,则需要编译 clang 时指定 -DCMAKE_BUILD_TYPEDebug

打印 AST 信息:

clang -cc1 -ast-dump test.c

列出 CSA 中支持列出的信息:

# 以 llvm-project 12.0.1 为例,仅 -analyzer-checker-help 无法显示所有的 Checkers
# 若需要显示 debug 开头的 Checkers ,需要使用 -analyzer-checker-help-developer
clang -cc1 -analyzer-checker-help-developer

其中 debug 开头的 Checkers 有:

debug.AnalysisOrder           Print callbacks that are called during analysis in order
debug.ConfigDumper            Dump config table
debug.DebugContainerModeling  Check the analyzer's understanding of C++ containers
debug.DebugIteratorModeling   Check the analyzer's understanding of C++ iterators
debug.DumpCFG                 Display Control-Flow Graphs
debug.DumpCallGraph           Display Call Graph
debug.DumpCalls               Print calls as they are traversed by the engine
debug.DumpControlDependencies Print the post control dependency tree for a given CFG
debug.DumpDominators          Print the dominance tree for a given CFG
debug.DumpLiveExprs           Print results of live expression analysis
debug.DumpLiveVars            Print results of live variable analysis
debug.DumpPostDominators      Print the post dominance tree for a given CFG
debug.DumpTraversal           Print branch conditions as they are traversed by the engine
debug.ExprInspection          Check the analyzer's understanding of expressions
debug.ReportStmts             Emits a warning for every statement.
debug.Stats                   Emit warnings with analyzer statistics
debug.StdCLibraryFunctionsTester
Add test functions to the summary map, so testing of individual summary constituents becomes possible.
debug.StreamTester            Add test functions to StreamChecker for test and debugging purposes.
debug.TaintTest               Mark tainted symbols as such.
debug.ViewCFG                 View Control-Flow Graphs using GraphViz
debug.ViewCallGraph           View Call Graph using GraphViz
debug.ViewExplodedGraph       View Exploded Graphs using GraphViz

打印 CFG 、 CallGraph 、 ExplodedGraph 信息:

clang -cc1 -analyze -analyzer-checker=debug.DumpCFG test.c
# 针对 c++ ,建议使用 -Xclang 和 -c ,如果使用 -cc1 可能还需要手动 -I 指定库头文件地址
# -Xclang <arg>    Pass <arg> to the clang compiler  表示传参给编译器部分
# -c               Only run preprocess, compile, and assemble steps
clang++ -Xclang -analyze -Xclang -analyzer-checker=debug.DumpCFG -c test.cpp
# 或直接使用 --analyze 来执行 CSA ,就不需要使用 -c ,本身 CSA 就不会进行编译后的过程
clang++ --analyze -Xclang -analyzer-checker=debug.DumpCFG test.cpp
clang++ --analyze -Xclang -analyzer-checker=debug.ViewCFG test.cpp
clang++ --analyze -Xclang -analyzer-checker=debug.DumpCallGraph test.cpp
clang++ --analyze -Xclang -analyzer-checker=debug.ViewCallGraph test.cpp
clang++ --analyze -Xclang -analyzer-checker=debug.ViewExplodedGraph test.cpp

对于 View 开头的选项即查看 CFG 等,需要有合适的 viewer ,推荐安装 Graphviz :

sudo apt install graphviz

一般来说安装后使用 View 选项即可弹出窗口显示图, dot 文件会暂时保存到 tmp 目录中,若想保存为 SVG (推荐)或 JPG 或 PDF 格式可以:

dot -Tsvg /tmp/ExprEngine-123456.dot > ExprEngine-123456.svg
dot -Tjpg /tmp/ExprEngine-123456.dot > ExprEngine-123456.jpg
dot -Tpdf /tmp/ExprEngine-123456.dot > ExprEngine-123456.pdf

对于一个函数内容比较多时,建议关闭 dotty 弹出窗口,否则有可能一下弹出上千个窗口。

参考:

clang static analyzer源码分析(二)_电影旅行敲代码的博客-CSDN博客

PDF | Graphviz

运行 Checker 检查代码

使用 --analyze 选项运行 CSA

CSA 中包含很多写好的 Checker ,或者我们可以编写自己的 Checker 。这些 Checker 不是被动地读取 ExplodedGraph 的信息,而是主动参与到 ExplodedGraph 的建立中,帮助我们发现安全问题。其实在查看 CFG 、 CallGraph 和 ExplodedGraph 信息时,我们就已经运行了 Checker ,这里更正式地讲一下如何运行 Checker 。

Clang 的 --analyze 编译选项表示运行静态分析器(the static analyzer,指的就是 CSA):

# --analyze  Run the static analyzer
clang --analyze test.c
# 另一种方式
clang -Xclang -analyze -c test.c

使用 -analyzer-checker 选项指定具体的 Checker :

clang --analyze -Xclang -analyzer-checker=core test.c

列出可用的 Checker :

clang -cc1 -analyzer-checker-help
clang -cc1 -analyzer-checker-help-developer

默认分析的输出为 plist 格式,指定输出 html 格式的报告,这时需要用 -o 指定输出的路径:

# --analyzer-output <value>    Static analyzer report output format (html|plist|plist-multi-file|plist-html|sarif|text).
# 输出的路径不用提前创建,会自动创建
clang --analyze --analyzer-output html -o <output-dir> test.c
clang --analyze --analyzer-output html -o <output-dir> -Xclang -analyzer-checker=core test.c
# 另一种方式(我更偏好)
clang --analyze -Xclang -analyzer-output=html -o <output-dir> test.c
clang --analyze -Xclang -analyzer-output=html -o <output-dir> -Xclang -analyzer-checker=core test.c

使用 scan-build 运行 CSA

(不推荐使用)

如果使用 scan-buildscan-view 来集成:

scan-build [scan-build options] <command> [command options]

即在编译命令 <command> 前加上 scan-build 即可。比如 scan-build makescan-build -V clang -c file.c 。其中 -V 表示输出报告。

关于 scan-buildscan-view

scan-build: scan-build is the high-level command line utility for running the analyzer
scan-view: scan-view a companion command line utility to scan-build, scan-view is used to view analysis results generated by scan-build. There is an option that one can pass to scan-build to cause scan-view to run as soon as it the analysis of a build completes

参考:

scan-build: running the analyzer from the command line (llvm.org)

 类似资料: