写点什么

从 CodeChecker 看 SAST 工具编译命令捕获实现

作者:maijun
  • 2023-08-31
    新加坡
  • 本文字数:2339 字

    阅读完需:约 8 分钟

SAST 工具,白盒测试工具,在面向 C/C++代码检查时,有基于编译和非编译两种实现。非编译的典型代码是 CheckMarx,现在已有的商用工具中,几乎全部都是基于编译的方式,比如 Coverity、Fortify、Klocwork 等。但是编译捕获工作大体相同,都需要经过如下的几个步骤:

图 1 SAST 编译捕获步骤

CodeChecker 是爱立信开源的一款程序分析框架。虽然该框架自己并没有实现分析引擎,但是集成了 Clang Static Analyzer 和 Clang-Tidy 等基于编译的静态代码分析工具。该工具在前置的编译信息提取方面,做了非常好地示例。

本文也将以 CodeChecker 为例,介绍上面提到的 SAST 工具在编译命令捕获阶段完成的事情。

1. CodeChecker 编译命令捕获

CodeChecker 在编译命令捕获中,使用了一个工具为 build-logger(该工具的实现原理和 bear 完全相同,如果各位在做自己的 SAST 工具,使用 bear 担心协议问题,可以选择 CodeChecker 中的 build-logger,基于 Apache 2.0 开源,完全可以商用)。

build-logger 是基于 hook 机制开发的命令捕获工具。主要是使用了 linux 系统中动态库的使用机制。在程序中,如果需要调用的函数在多个动态库中存在,可以基于 LD_PRELOAD 指定优先加载的动态库,这样函数就会调用我们指定的动态库。

在 linux 操作系统中,任何命令的执行,都需要调用 execve 族的函数才能到内核态执行,build-logger 就是通过开发一个动态库,重写 execve 族的函数,记录相关的命令来达到编译命令捕获的目的的。

build-logger 代码,在 analyzer/tools/build-logger 目录下,会编译成一个动态库,动态库的使用,用户构建命令的拉起,是在 analyzer/codechecker_analyzer/buildlog/build_manager.py 中。

2. CodeChecker 额外编译信息获取

在我之前的一个资料“Clang 编译数据库信息扩展”中,已经提到,Clang 编译数据库原生的信息不足提供使 Clang 能够准确构建的信息,在 CodeChecker 的参数处理过程中,提供前述参数的解析。

CodeChecker 的这部分参数解析,在 analyzer/codechecker_analyzer/buildlog/log_parser.py 文件中。主要的实现在类 ImplicitCompilerInfo 中。

已经支持的解析的参数有:

  • 系统头文件路径信息

在函数 get_compiler_includes 和 __parse_compiler_includes 中,我们简单介绍一下注释信息:


  • 编译结果的 target 信息

在函数 get_compiler_target 中:

  • 支持的编码标准(一般和编译器版本存在一一映射)

在函数 get_compiler_standard 中:

在命令中,提取到的编码标准信息,影响到了编译命令中的 -std 参数


目前,从代码实现来看,CodeChecker 没有提取环境变量的信息,bit 的信息,版本只是关心了编码标准,也就是在预定义宏中,只获取了部分,并没有全部使用,其他的信息都有涉及。


在此,我要重点介绍一下 filter_compiler_includes_extra_args 方法,我在之前的文章中有提到获取系统头文件和预定义宏信息,但是没有讲到编译命令里面的哪些参数会影响到获取系统头文件和预定义宏的获取,在该函数中,给出来答案:

def filter_compiler_includes_extra_args(compiler_flags):    """Return the list of flags which affect the list of implicit includes.
compiler_flags -- A list of compiler flags which may affect the list of implicit compiler include paths, like -std=, --sysroot=, -m32, -m64, -nostdinc or -stdlib=. """ # If these options are present in the original build command, they must # be forwarded to get_compiler_includes and get_compiler_defines so the # resulting includes point to the target that was used in the build. pattern = re.compile('-m(32|64)|-std=|-stdlib=|-nostdinc|--driver-mode=') extra_opts = list(filter(pattern.match, compiler_flags))
pos = next((pos for pos, val in enumerate(compiler_flags) if val.startswith('--sysroot')), None) if pos is not None: if compiler_flags[pos] == '--sysroot': extra_opts.append('--sysroot=' + compiler_flags[pos + 1]) else: extra_opts.append(compiler_flags[pos])
return extra_opts
复制代码

这个是 CodeChecker 给出来的会影响系统头文件路径和预定义宏获取的参数的解析方法。

3. CodeChecker 编译参数处理

至于编译参数的处理,也是在 analyzer/codechecker_analyzer/buildlog/log_parser.py 中。

对编译参数的处理,离不开下面的三种情况:

  • 参数去除

比如,如下的参数列表:

IGNORED_OPTIONS_GCC = [    # --- UNKNOWN BY CLANG --- #    '-fallow-fetchr-insn',    '-fcall-saved-',    '-fcond-mismatch',    '-fconserve-stack',    '-fcrossjumping',    '-fcse-follow-jumps',    '-fcse-skip-blocks',    '-fcx-limited-range$',    '-fext-.*-literals',    ...]
复制代码
  • 参数替换

比如,如下的参数列表:

REPLACE_OPTIONS_MAP = {    '-mips32': ['-target', 'mips', '-mips32'],    '-mips64': ['-target', 'mips64', '-mips64'],    '-mpowerpc': ['-target', 'powerpc'],    '-mpowerpc64': ['-target', 'powerpc64']}
复制代码
  • 参数新增


这部分代码逻辑比较复杂,但是都是针对编译命令参数的处理,大家有兴趣可以自己阅读以下源码,逻辑还是非常清晰的。

4. 总结

CodeChecker 已经实现了非常完成的编译命令捕获实现,但是还不够,主要在下面两点:

  • 当前只针对 gcc 和 clang 命令进行了适配,目前已知的编译器何止几百种;

  • 目前我还没有看到语法适配的实现

参考

[1] https://xie.infoq.cn/article/07bb0781a985b359b9adbb798

[2] https://github.com/Ericsson/codechecker/tree/master


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

maijun

关注

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

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

评论

发布
暂无评论
从CodeChecker看SAST工具编译命令捕获实现_maijun_InfoQ写作社区