写点什么

Clang 编译数据库信息扩展

作者:maijun
  • 2023-08-30
    新加坡
  • 本文字数:4062 字

    阅读完需:约 13 分钟

1. 背景介绍

LLVM + Clang 是目前使用非常火的编译器,其优点这里不再赘述,总之,该工具成为当前做编译器、程序分析等相关工作和研究的企业和学者的首选。说 LLVM + Clang 养活了全球一半做编译器和程序分析的企业和高校团队也不为过。

因为 LLVM 和 Clang 的优势,在 SAST 领域,Clang 和 LLVM 已经成为很多 SAST 工具的基础框架,目前已经延伸出了相当多的优越的 SAST 工具,包含但不仅限于:

  • Clang Static Analyzer,clang 原生提供的基于符号执行的检查工具

  • Clang-Tidy,clang 原生提供的基于 ASTMatcher 的检查工具,主要是风格检查

  • Klee,基于 llvm bc 的基于符号执行的程序验证工具

  • Phasar,基于 llvm bc 的基于 IFDS 实现的程序分析框架,其中包含了很多其他的分析框架

  • IKOS,基于 llvm bc 的基于抽象解释的程序分析工具

  • SVF,基于 llvm bc 的程序分析工具,有非常优越地指针分析的能力

  • ...


但是,为了能够应用这些检查工具,就要求代码必须使用 Clang 进行构建。在这个过程中,Clang 的编译数据库[1]起到了非常重要的作用。目前也有很多工具支持导出相关的编译数据库,例如:

  • bear[2]

  • compiledb[3]

  • cmake、ninja、bazel 等相关构建工具可以直接导出相关的编译数据库


但是,为了更好地使用上面的 SAST 工具,Clang 的原始的编译数据库中的信息,是不够的,需要结合更多地信息,才能完成分析(所有的基于编译的 SAST 工具都有类似的需求,并不仅仅是 Clang,本文中论述的各种扩展的编译信息需求,来自于对 Coverity 的编译适配的理解)。


下面,我们就结合实际的情况,给大家介绍一下还需要哪些编译信息。

2. 扩展信息介绍

2.1 环境变量信息

从我们的开发实践中,发现存在部分的编译器或者源代码在编译过程中,依赖特定的环境变量。从这个角度讲,我们需要将这些环境变量的信息收集起来。Coverity 在编译捕获过程中,会记录每一个识别到的编译器特定命令的环境变量信息。

在下面,我们获取编译器其他信息时,也需要这些环境变量信息。

2.2 编译器的默认 target、bit

比如下面的例子,原始命令为:

gcc -c test.c
复制代码

我们知道,clang 的编译数据库会比较忠实地体现编译命令的信息。上面的编译命令,记录到 clang 编译数据库,也是一样的信息。那么,上面的信息足够使用了吗?

我们知道,c/c++编译的结果文件,有特定的运行环境,比如运行在 x86-64 上面,还是运行在 arm 上面,是运行在 32 位系统上,还是 64 位系统上面。这些在编译的时候,就需要确定,如果在编译命令中不指定,就会有默认值,比如默认的 target 和 位数。但是不同的编译器的默认值并不一定是 clang 编译器的默认值。比如 交叉编译器(aarch64_be-linux-gnu-gcc、mips-linux-gcc 等),我们看名字就知道,如果这些编译器编译,即使没有默认的 target 和位数,应该默认值不会和 clang 一样。

那么如何确认编译器默认的 target 和 位数 呢?

对于 target 信息,可以使用 -dumpmachine 确认,如下:

$ gcc -dumpmachinex86_64-linux-gnu
复制代码

如上,我们可以看到,gcc 在我们的这个操作系统中,默认是 x86_64-linux-gnu.

对于 bits 信息,可以采用编译一个测试文件,然后查看测试文件的位数确定,如下:

$ gcc -c -o test_default.o test.c$ file test_default.otest_default.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped$ gcc -c -m32 -o test_m32.o test.c$ file test_m32.otest_m32.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
复制代码

我们可以看到,在默认编译参数编译完成后,是 ELF 64-bit 的文件,如果额外添加了 -m32,就是 ELF 32-bit 的文件了。

2.3 编译器系统头文件路径

不同的 c/c++编译器,都会在标准 c/c++语法的基础上,扩展自己的语法,或者是新增一些函数、类型等(扩展的非标准语法不再本文讨论范围,这里只讨论新增的函数、类型)。新增的函数、类型等,一般是定义在编译器的系统头文件路径下面的。因此,我们在捕获到编译命令之后,还需要获取特定的系统头文件路径信息。

系统头文件路径,和编译器的 target、bits 等信息有关,获取系统头文件里面,可以在命令中添加 -E -v 来获取,对应的系统头文件路径,可以在 “#include <...> search starts here:”和 “End of search list.”之间解析。如下:

使用 gcc 默认的命令,结果如下:

$ gcc -c -E -v test.c...#include <...> search starts here: /usr/lib/gcc/x86_64-linux-gnu/9/include /usr/local/include /usr/include/x86_64-linux-gnu /usr/includeEnd of search list....
复制代码

在 gcc 默认的命令的基础上,添加 -m32,结果如下:

maijun@DESKTOP-OM57KKD:/mnt/d/wsl/test/basic$ gcc -c -m32 -E -v test.c...#include <...> search starts here: /usr/lib/gcc/x86_64-linux-gnu/9/include /usr/local/include /usr/includeEnd of search list....
复制代码

我切换成 arm 交叉编译器,使用默认命令,结果如下:

$ arm-linux-gnueabihf-gcc -c -E -v test.c...#include <...> search starts here: /usr/lib/gcc-cross/arm-linux-gnueabihf/9/include /usr/lib/gcc-cross/arm-linux-gnueabihf/9/../../../../arm-linux-gnueabihf/include /usr/includeEnd of search list....
复制代码

如上面的三个例子:我们展示了不同编译器、或者相同编译器的不同参数,生成的系统头文件路径的差异。在实际使用中,要获取特定编译命令的系统头文件路径,一般可以反向去掉肯定不影响系统头文件路径的参数之后,直接添加 -E -v 运行程序即可(可以不使用源文件,使用一个空源文件替代,如果捕获到的命令过多,可以通过缓存,减少执行量)。

2.4 预定义宏信息

预定义宏和系统头文件类似,都会影响到源文件的编译。需要获取到原始编译器的预定义宏,原因在于两点:① 编译命令中的编译参数会通过影响预定义宏来对源码生效;② 不同的编译器中,支持的预定义宏不同,因此有可能存在从原始编译器到 clang 后,有宏缺失的情况。因此,我们需要获取原始编译器的预定义宏,传递给 clang。

获取预定义宏的方式也非常简单,在编译命令中添加 -E -dM 参数即可。

下面,我们也同样通过三种情况举例:

使用 gcc 默认的命令,结果如下:

$ gcc -c -E -dM test.c#define __SSP_STRONG__ 3#define __DBL_MIN_EXP__ (-1021)#define __FLT32X_MAX_EXP__ 1024#define __UINT_LEAST16_MAX__ 0xffff#define __ATOMIC_ACQUIRE 2#define __FLT128_MAX_10_EXP__ 4932#define __FLT_MIN__ 1.17549435082228750796873653722224568e-38F#define __GCC_IEC_559_COMPLEX 2#define __UINT_LEAST8_TYPE__ unsigned char#define __SIZEOF_FLOAT80__ 16#define __INTMAX_C(c) c ## L#define __CHAR_BIT__ 8#define __UINT8_MAX__ 0xff
复制代码

在 gcc 默认的命令的基础上,添加 -m32,结果如下:

$ gcc -c -m32 -E -dM test.c#define __SSP_STRONG__ 3#define __DBL_MIN_EXP__ (-1021)#define __FLT32X_MAX_EXP__ 1024#define __pentiumpro__ 1#define __UINT_LEAST16_MAX__ 0xffff#define __ATOMIC_ACQUIRE 2#define __FLT128_MAX_10_EXP__ 4932#define __FLT_MIN__ 1.17549435082228750796873653722224568e-38F#define __GCC_IEC_559_COMPLEX 2#define __UINT_LEAST8_TYPE__ unsigned char#define __SIZEOF_FLOAT80__ 12#define __INTMAX_C(c) c ## LL#define __CHAR_BIT__ 8#define __UINT8_MAX__ 0xff...
复制代码

我切换成 arm 交叉编译器,使用默认命令,结果如下:

$ arm-linux-gnueabihf-gcc -c -E -dM test.c#define __SSP_STRONG__ 3#define __DBL_MIN_EXP__ (-1021)#define __HQ_FBIT__ 15#define __FLT32X_MAX_EXP__ 1024#define __UINT_LEAST16_MAX__ 0xffff#define __ARM_SIZEOF_WCHAR_T 4#define __ATOMIC_ACQUIRE 2#define __SFRACT_IBIT__ 0#define __FLT_MIN__ 1.1754943508222875e-38F#define __GCC_IEC_559_COMPLEX 2#define __UFRACT_MAX__ 0XFFFFP-16UR#define __UINT_LEAST8_TYPE__ unsigned char
复制代码

如上,前面两个,我们都使用 gcc 的情况下,不同的位数,会导致类型的长度不同,比如 FLOAT80,在 64 位是 16,在 32 位是 12。在 arm 交叉编译器情况下,存在一些宏是 x86 下所没有的,比如 __ARM_x 类型。

2.5 链接信息

链接命令信息,更多地可以为跨文件分析提供辅助。虽然在没有链接命令的情况下,也可以通过启发式的方法获取 CallGraph 的信息,比如通过头文件、mangle function 等,但是,在多 target 的情况下,可能会存在错误链接的情况。因此,链接命令可以辅助执行更加准确的跨文件分析(一定程度上,链接命令确实不是必须的)。

2.6 编译器版本、软链接、wrapper 等信息

即使是相同的编译器,版本不同,支持的语法也有所差异,最后需要适配的工作也不同。比如我们针对 gcc 4.8.0 适配的内容,放到 gcc 7.3.0 中,可能就会有问题。因此编译器版本的获取和适配,是工程化的需要。编译器版本获取方式如下:

$ gcc --versiongcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0Copyright (C) 2019 Free Software Foundation, Inc.This is free software; see the source for copying conditions.  There is NOwarranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
复制代码

通过预定义宏,也可以获取到版本的信息,如下(还能获取到一些更详细的信息):

$ gcc -c -E -dM test.c | grep VERSION#define __GXX_ABI_VERSION 1013#define __VERSION__ "9.4.0"#define __STDC_VERSION__ 201710L
复制代码


对于 wrapper,主要是有些编译器名,看着是编译器,但是实际上是脚本,并不是真正的编译命令,需要加以区分。


对于软链接,主要目的是可以减少重复编译命令获取,减少不必要的错误。

3. 总结

如果要实现一款工业级的基于编译的面向 C/C++的 SAST 工具,编译命令信息获取必不可少。本文介绍了除了 clang 编译数据库 中的信息外,其他的一些必要的信息,帮助提高编译准确率,完善工程实现。

参考

[1] https://clang.llvm.org/docs/JSONCompilationDatabase.html

[2] https://github.com/rizsotto/Bear

[3] https://github.com/nickdiego/compiledb


发布于: 刚刚阅读数: 5
用户头像

maijun

关注

还未添加个人签名 2019-09-20 加入

关注SAST工具开发、应用,DevSecOps、研发效能等

评论

发布
暂无评论
Clang编译数据库信息扩展_Clang_maijun_InfoQ写作社区