使用LLVM检测C / C ++代码

时间:2011-09-23 08:58:29

标签: c++ llvm static-analysis clang instrumentation

我刚刚读到了LLVM项目,它可以用来使用LLVM前端的分析器Clang对C / C ++代码进行静态分析。我想知道是否可以使用LLVM提取源代码中对内存的所有访问(变量,本地和全局)。

LLVM中是否存在任何内置库,我可以使用它来提取此信息。 如果没有,请建议我如何编写函数来做同样的事情。(现有的源代码,参考,教程,示例......) 我想到的是,我会先将源代码转换为LLVM,然后用它来进行分析,但不知道该怎么做。


我试图找出自己应该使用哪种IR用于我的目的(Clang的抽象语法树(AST)或LLVM的SSA中间表示(IR)。),但无法确定要使用哪一个。 这是我想要做的。 给定任何C / C ++程序(如下面给出的那样),我试图在每个读/写内存的指令之前和之后插入一些函数的调用。例如,考虑下面的C ++程序(Account.cpp)

#include <stdio.h>

class Account {
  int balance;

public:
  Account(int b) {
    balance = b;
  }

  int read() {
    int r;
    r = balance;
    return r;
  }

  void deposit(int n) {
    balance = balance + n;
  }

  void withdraw(int n) {
    int r = read();
    balance = r - n;
  }
};

int main () {
  Account* a = new Account(10);
  a->deposit(1);
  a->withdraw(2);
  delete a;
}

所以在仪器之后我的程序应该是这样的:

#include <stdio.h>

class Account {
  int balance;

public:
  Account(int b) {
    balance = b;
  }

  int read() {
    int r;
    foo();
    r = balance;
    foo();
    return r;
  }

  void deposit(int n) {
    foo();
    balance = balance + n;
    foo();
  }

  void withdraw(int n) {
    foo();
    int r = read();
    foo();
    foo();
    balance = r - n;
    foo();
  }
};

int main () {
  Account* a = new Account(10);
  a->deposit(1);
  a->withdraw(2);
  delete a;
}

其中foo()可以是任何函数,比如获取当前系统时间或递增计数器......等等。我明白要插入上面的函数,我必须首先获取IR然后在IR上运行一个检测传递,它会将这些调用插入IR,但我真的不知道如何实现它。请告诉我如何解决这个问题。

另外我理解,一旦我将程序编译到IR中,在我的原始程序和仪表化IR之间获得1:1映射真的很困难。那么,是否有可能将IR中的变化(因为仪器)反映到原始程序中。

为了开始使用LLVM,以及如何自己创建一个,我查看了一个向LLVM IR加载和存储添加运行时检查的传递示例,SAFECode的加载/存储检测传递({ {3}}和http://llvm.org/viewvc/llvm-project/safecode/trunk/include/safecode/LoadStoreChecks.h?view=markup)。但我无法弄清楚如何运行此传球。请告诉我如何在某些程序上运行此传递,如上面的Account.cpp。

2 个答案:

答案 0 :(得分:29)

首先,您必须决定是否要使用clang或LLVM。它们都在非常不同的数据结构上运行,这些结构具有优点和缺点。

根据您对问题的稀疏描述,我建议您在LLVM中进行优化传递。使用IR将使清理,分析和注入代码变得更加容易,因为这就是它的设计目标。缺点是您的项目将依赖于LLVM,这对您来说可能是也可能不是问题。您可以使用C后端输出结果,但人类无法使用。

使用优化过程时的另一个重要缺点是您还丢失了原始源代码中的所有符号。即使Value类(后面会有更多内容)使用getName方法,您也应该从不依赖它来包含任何有意义的内容。它旨在帮助您调试通行证,而不是其他任何内容。

您还必须对编译器有基本的了解。例如,了解basic blocksstatic single assignment form是一项要求。幸运的是,他们学习或理解的概念并不是很难(维基百科文章应该足够)。

在开始编码之前,首先要做一些阅读,所以这里有一些链接可以帮助你入门:

  • Architecture Overview :LLVM的快速架构概述。能帮助您了解您的工作内容以及LLVM是否适合您。

  • Documentation Head :您可以在此处找到以下所有链接以及更多内容。如果我遗漏了任何内容,请参阅此内容。

  • LLVM's IR reference :这是您将要操作的LLVM IR的完整描述。语言相对简单,所以学习的内容不多。

  • Programmer's manual :快速概述使用LLVM时需要了解的基本内容。

  • Writting Passes :撰写转化或分析过程中需要了解的所有内容。

  • LLVM Passes :LLVM提供的所有通行证的完整列表,您可以并且应该使用这些通行证。这些可以真正帮助清理代码并使分析更容易。例如,在使用循环时,lcssasimplify-loopindvar次传递将挽救您的生命。

  • Value Inheritance Tree :这是Value类的doxygen页面。这里重要的一点是继承树,您可以按照该树获取IR参考页面中定义的所有指令的文档。只要忽略他们称之为协作图的不敬虔的怪物。

  • Type Inheritance Tree :与上述内容相同,但适用于类型。

一旦你理解了所有这一切,那就是蛋糕了。要查找内存访问?搜索storeload说明。仪器?只需使用Value类的适当子类创建所需内容,然后在存储和加载指令之前或之后插入它。因为你的问题有点过于宽泛,所以我无法真正帮助你。 (见下文更正)

顺便说一句,几周前我不得不做类似的事情。在大约2-3周内,我能够学习所有关于LLVM的知识,创建一个分析传递来查找循环中的内存访问(以及更多),并使用我创建的转换传递来检测它们。没有涉及奇特的算法(除了LLVM提供的算法),一切都非常简单。故事的寓意是LLVM易于学习和使用。


更正:当我说你要做的就是搜索loadstore指令时,我犯了一个错误。

loadstore指令仅提供使用指针对堆进行的访问。为了获得所有内存访问,您还必须查看可以表示堆栈上的内存位置的值。在寄存器分配阶段期间确定值是写入堆栈还是存储在寄存器中,这发生在后端的优化传递中。这意味着它依赖于平台,不应该依赖它。

现在,除非你提供更多关于你正在寻找什么样的内存访问的信息,在什么情况下以及你打算如何设置它们,我对此不能帮助你。

答案 1 :(得分:3)

由于两天后你的问题没有答案,我会提供一个稍微但不完全偏离主题的答案。

作为LLVM的替代方案,对于C程序的静态分析,您可以考虑编写Frama-C插件。

计算C函数输入列表的现有插件需要访问函数体中的每个左值。这是在文件src / inout / inputs.ml中实现的。实现很简单(复杂性在其他插件中,为其提供结果,例如解析指针),并且可以用作您自己的插件的骨架。

框架提供了抽象语法树的访问者。为了对左值进行特殊处理,您只需定义相应的方法即可。输入插件的核心是方法定义:

method vlval lv = ...

以下是输入插件的功能示例:

int a, b, c, d, *p;

main(){
  p = &a;
  b = c + *p;
}

main()的输入因此被计算出来:

$ frama-c -input t.c
...
[inout] Inputs for function main:
          a; c; p;

有关编写Frama-C插件的更多信息,请参见here