Clang Static Analyzer 阅读实验

1. 编译LLVM和clang

首先下载 LLVMclang 的源码。把它们放在某个文件夹下,并在此处打开终端,执行下面的命令(保证你安装了xz、cmake):

tar xvf llvm-3.9.0.src.tar.xz
mv llvm-3.9.0.src llvm
tar xvf cfe-3.9.0.src.tar.xz
mv cfe-3.9.0.src llvm/tools/clang

# we compile everything under build/
mkdir build
cd build
cmake ../llvm   # you can add `-G ninja` option
make -j4        # use -jN to specify number of threads

这可能要花很长时间(使用默认配置(开启了DEBUG),在 Core i7、开8线程下,大约需要1小时。内存使用峰值会超过12G,如果你遇到内存不够的问题,参考文档这个答案。可以考虑使用RELEASE配置,只需为cmake添加-DCMAKE_BUILD_TYPE=Release选项,时间可缩短到20分钟)。

你需要记录一下你的编译环境、所用时间、内存等信息,放在sa/compile.txt中,格式如下:

CPU:
内存大小:
操作系统:ubuntu/windows/mac/...
cmake目标:make/ninja/vs/...
cmake build type:Debug/Release
编译线程数:
编译耗时:
内存占用峰值:
遇到的问题及如何解决:

你可以先去喝几杯茶。

编译结束后,你会在bin目录下看到所有的可执行文件。

假设之后你需要执行这里的可执行文件,你可以在调用时指定路径,或者将这个路径加入到PATH环境变量中。

2. 使用Clang Static Analyzer

在深入了解 clang 静态分析的实现机制之前,可以先使用它检测包含有 bug 的 C 程序,初步了解静态分析对程序员到底有何用处。

Clang静态分析可以通过多种渠道来被使用,这里可以使用命令scan-build来调用 Clang 静态分析。

例如,如下是一个含有潜在导致悬空引用(全局变量p引用了局部变量str的地址)的C程序,假设保存在test.c中:

char *p;
void test()
{
    char str[] = "hello";
    p = str;
}

如果用一般的编译器去编译这个程序,可能都不会报任何warning。

接下来可以执行命令:

$ scan-build clang -cc1 test.c    

其中clang -cc1指令表示只调用clang的前端进行编译。 你可以看到它会检测出bug,并报了warning。如果为scan-build添加-V选项,你还可以看到经过良好排版的网页端结果。

你也可以在直接调用clang命令时指定要使用的某个checker,从而在clang执行期间会调用这个checker对代码进行检查。考虑下面这个程序testfile.c

#include <stdio.h>

FILE *open(char *file)
{
    return fopen(file, "r");
}

void f1(FILE *f)
{
    // do something...
    fclose(f);
}

void f2(FILE *f)
{
    // do something...
    fclose(f);
}

int main()
{
    FILE *f = open("foo");
    f1(f);
    f2(f);
    return 0;
}

将这个文件保存于dblclose.c,之后执行:

$ clang --analyze -Xanalyzer -analyzer-checker=alpha.unix.SimpleStream dblclose.c    

即可对这个文件执行SimpleStreamChecker。这个checker的含义如它名字所言。

3. 学习现有的checker

Clang 中实现了很多独立的 checker 用来做静态检查。先前看到的test.c 中的bug,就是被其中的一个名为(core.StackAddressEscape)的检查器检测出来的。

你可以执行 clang -cc1 -analyze -analyzer-checker-help 来查看有哪些checker可用。

所有checker的代码都在llvm/tools/clang/lib/StaticAnalyzer/Checkers/下。 你可以按照自己的方式阅读。

下面是一些指导和需要回答的问题:

3.1 对程序绘制AST、CFG和exploded graph

阅读这一小节,完成下面几项:

  1. 安装 graphviz
  2. 写一个含有循环、跳转逻辑的简单程序,保存为sa/test.c
  3. 使用clang -cc1 -ast-view test.c绘制程序的AST,输出保存为sa/AST.svg
  4. 根据文档说明,绘制sa/CFG.svg, sa/ExplodedGraph.svg
  5. 简要说明test.cAST.svgCFG.svgExplodedGraph.svg之间的联系与区别
  6. 特别说明:如果你采用了release配置,或者你无法正常产生svg,你可以选择使用dump选项,并将文字输出放在对应名字的txt中。其他格式的图片也可以接受,不需要为格式问题耗费时间。

3.2 阅读Checker Developer Manual的Static Analyzer Overview一节

回答下面的问题:

  1. Checker 对于程序的分析主要在 AST 上还是在 CFG 上进行?
  2. Checker 在分析程序时需要记录程序状态,这些状态一般保存在哪里?
  3. 简要解释分析器在分析下面程序片段时的过程,在过程中产生了哪些symbolic values? 它们的关系是什么?

一段程序:

int x = 3, y = 4;
int *p = &x;
int z = *(p + 1);

3.3 简要阅读LLVM Programmer's ManualLLVM Coding Standards

这两个manual比较长,你不需要全部阅读,你只需要给出下面几个问题的答案:

  1. LLVM 大量使用了 C++11/14的智能指针,请简要描述几种智能指针的特点、使用场合,如有疑问也可以记录在报告中.
  2. LLVM 不使用 C++ 的运行时类型推断(RTTI),理由是什么?LLVM 提供了怎样的机制来代替它?
  3. 如果你想写一个函数,它的参数既可以是数组,也可以是std::vector,那么你可以声明该参数为什么类型?如果你希望同时接受 C 风格字符串和 std::string 呢?
  4. 你有时会在cpp文件中看到匿名命名空间的使用,这是出于什么考虑?

3.4 阅读clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp

回答下面问题:

  1. 这个 checker 对于什么对象保存了哪些状态?保存在哪里?
  2. 状态在哪些时候会发生变化?
  3. 在哪些地方有对状态的检查?
  4. 函数SimpleStreamChecker::checkPointerEscape的逻辑是怎样的?实现了什么功能?用在什么地方?
  5. 根据以上认识,你认为这个简单的checker能够识别出怎样的bug?又有哪些局限性?请给出测试程序及相关的说明。

3.5 Checker的编译

阅读这一节,以及必要的相关源码,回答下面的问题:

  1. 增加一个checker需要增加哪些文件?需要对哪些文件进行修改?
  2. 阅读clang/include/clang/StaticAnalyzer/Checkers/CMakeLists.txt,解释其中的 clang_tablegen 函数的作用。
  3. .td文件在clang中出现多次,比如这里的clang/include/clang/StaticAnalyzer/Checkers/Checkers.td。这类文件的作用是什么?它是怎样生成C++头文件或源文件的?这个机制有什么好处?

4 扩展要求

完成上述基础要求后,你可以考虑完成这部分扩展要求。由于这个扩展要求难度较高,各位请量力而行。

扩展部分提供两种选择,分析现有 checker 的不足或者编写自己的 checker. 如果觉得很难想到一个有价值的 checker, 建议选择第一个.

4.1 分析现有 checker 的缺陷

clang 静态分析提供了对程序中一些常见问题的检查. 比如 cplusplus.NewDelete、unix.Malloc 能对内存泄漏等 bug 进行一定的检查. 但它们并不能解决程序中出现的所有这类问题.

在官网 Open Project 里提到了一些 clang 静态分析不能解决的问题. 比如:

  • 浮点值的处理问题:当前clang 静态分析器将所有的浮点值处理为unknown,故不能静态检测含浮点值的条件,从而审查代码。
void foo(double f) {
    int *pi = 0;
    if (f > 1.)
        pi = new int;  // bug report: potential leakage
    if (f > 0. && pi)  // must taken, no leakage
        delete pi;
}
  • 不能处理多次循环:当前分析器简单地将每个循环展开 N(比较小的整数) 次
    void foo() {
     int *pi = new int;
     for (int i = 0; i < 3; i++) // if replace 3 with 100, no bug report
         if (i == 1000)
             delete pi;          // bug report: potential leakage
    }
    
  • 不能处理按位运算

    int main (int argc, char **argv)
    {
      const char *space;
      int flags = argc;
    
      if (flags & (0x01 | 0x02))
          space = "qwe";
    
      if (flags & 0x01)
          return *space; // bug report: Dereference of undefined pointer value
    
      return 0;
    }
    
  • 除了这些缺陷以外, clang静态分析器还有哪些缺陷?

  • 以动态内存、或文件等资源有关的缺陷检查为例,对clang 静态分析器进行如下使用和分析工作:
    1. 是否能检查该类缺陷?
    2. 检查能力到什么程度(程序存在哪些特征时检查不出来)?
    3. 检查的实现机制是什么?列出相关的源码位置和主要处理流程
    4. (可选)从实现机制上分析,为什么检查不出来上述问题2的解答中所列的特征?
    5. (可选)如果想增强检查能力,可以怎么做?
  • 可选的动态内存、或文件等资源有关的缺陷检查
    • cplusplus.NewDelete
    • unix.Malloc
    • unix.API
    • 悬空引用
    • 文件未关闭
    • ......

4.2 编写自己的 checker

在阅读clang代码大致了解 checker 的编写方法之后,你可以仔细阅读Checker Developer Manual,另外这份slides 也是非常好的材料。

作为快速指导,下面将整理一下你需要做的主要工作,和每个阶段工作可能需要参考的材料:

  1. 确定你想要检测的bug,或者完成的功能。你可以参考这个链接来寻找一些idea。如果你想要做一种bug checker,你应当先考虑清楚你需要记录哪些状态,状态在哪些时机改变,哪些时机需要对状态进行检查从而确定是否有问题。
  2. 开始编码。这里你需要实现你刚刚的想法。你应当已经知道checker应该如何保存状态、如何设置callback、如何获取程序符号,你想知道的其他细节需要在文档中找,另外你会发现你的很多做法会有其他checker可以参考。
  3. 注册checker并编译:根据这一节,核实你的checker可以通过编译,并能够被clang调用。
  4. 编写测试样例进行测试。你的测试样例要能体现出你的checker能完成以及不能完成的事情。注意检查会不会有false positive的情况。测试样例放在sa/test/目录下。
  5. 编写说明文档。文档中说明你完成的功能,遇到的困难等。

编写好后,你需要提交你的PBXXXXXXXXChecker.cpp,以及对你的checker功能的说明(在README(.md)中简述)、测试样例。请保证你的llvm、clang版本为3.9.0,且可以编译通过,之后在实验评测时会将各位的checker统一注册编译。

4.3 需要提交的目录格式:

mp1/
 ...                        以前的内容

sa/
 ├─ compile.txt             编译记录
 ├─ AST.svg, CFG.svg, ExplodedGraph.svg
 ├─ test.c                  3.1中要求的程序和图
 ├─ answers.txt(或.md)      回答问题
 │                          以上为基础要求部分
 ├─ PBXXXXXXXXChecker.cpp   编写的checker源码
 ├─ checker.(md|doc|tex|txt)    对checker的说明
 ├─ analysis.(md|doc|tex|txt)   对 clang 静态分析器的分析
 └─ test/
     ├─ ...                 你的测试样例

参考

某人写的CSA源码分析的文章, 可以看看 link, 知乎

results matching ""

    No results matching ""