如何逐行解析LLVM IR

时间:2015-05-12 15:22:54

标签: c++ parsing llvm llvm-ir

我特别需要在我的c ++代码运行期间逐行解析LLVM IR代码,我需要知道每行上哪些操作数发生了什么操作。

例如,如果IR代码是:

%0 = load i32* %a, align 4

我想知道在我的c ++代码运行期间%a的值被加载到%0。 我已经考虑使用简单的文本解析c ++程序来执行此操作(解析IR并搜索IR关键字),但想知道是否有任何现有的库(可能来自LLVM本身)将帮助我避免这样做。

1 个答案:

答案 0 :(得分:7)

假设

理论上,我们可以直接利用LLVM::LLLexer为LLVM IR编写我们自己的解析器,逐行解析。

以下答案假设您只对LLVM IR文件的每个函数内部的操作感兴趣,因为LLVM IR文件中的其他信息不包含该操作。操作只能位于一个功能中。对于IR的其他部分,例如结构定义,函数声明等,它们只有类型的信息,并且不包含任何有关操作的信息。

实施

基于上述假设,您关于在IR文件中逐行解析LLVM IR的问题可以转换为解析LLVM IR文件的每个函数中的每个操作。

LLVM确实有一个现有的实现,可以直接解析LLVM IR文件,直接获取有关操作的信息,并且由于IR文件的功能序列是它们在LLVM IR文件中出现的,操作顺序以下实现的输出只是给定LLVM IR文件中的操作序列。

因此我们可以利用llvm提供的parseBitcodeFile接口。此类接口首先使用LLVM::LLLexer将LLVM IR文件拆分为令牌,然后将令牌提供给Parser进行分析,最后生成ErrorOr<llvm::Module *>模块信息,功能序列模块中的列表与llvm ir文件中的序列相同。

然后我们可以LLVM::BasicBlock中的每个LLVM::Function LLVM::Module。然后迭代每个LLVM::Instruction,并获取有关每个操作数LLVM::Value的信息。以下是实施代码。

#include <iostream>
#include <string>
#include <llvm/Support/MemoryBuffer.h>
#include <llvm/Support/ErrorOr.h>
#include <llvm/IR/Module.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/Bitcode/ReaderWriter.h>
#include <llvm/Support/raw_ostream.h>

using namespace llvm;

int main(int argc, char *argv[]) {
  if (argc != 2) {
    std::cerr << "Usage: " << argv[0] << "bitcode_filename" << std::endl;
    return 1;
  }
  StringRef filename = argv[1];
  LLVMContext context;

  ErrorOr<std::unique_ptr<MemoryBuffer>> fileOrErr =
    MemoryBuffer::getFileOrSTDIN(filename);
  if (std::error_code ec = fileOrErr.getError()) {
    std::cerr << " Error opening input file: " + ec.message() << std::endl;
    return 2;
  }
  ErrorOr<llvm::Module *> moduleOrErr =
      parseBitcodeFile(fileOrErr.get()->getMemBufferRef(), context);
  if (std::error_code ec = fileOrErr.getError()) {
    std::cerr << "Error reading Moduule: " + ec.message() << std::endl;
    return 3;
  }

  Module *m = moduleOrErr.get();
  std::cout << "Successfully read Module:" << std::endl;
  std::cout << " Name: " << m->getName().str() << std::endl;
  std::cout << " Target triple: " << m->getTargetTriple() << std::endl;

  for (auto iter1 = m->getFunctionList().begin();
       iter1 != m->getFunctionList().end(); iter1++) {
    Function &f = *iter1;
    std::cout << " Function: " << f.getName().str() << std::endl;
    for (auto iter2 = f.getBasicBlockList().begin();
         iter2 != f.getBasicBlockList().end(); iter2++) {
      BasicBlock &bb = *iter2;
      std::cout << "  BasicBlock: " << bb.getName().str() << std::endl;
      for (auto iter3 = bb.begin(); iter3 != bb.end(); iter3++) {
        Instruction &inst = *iter3;
        std::cout << "   Instruction " << &inst << " : " << inst.getOpcodeName();

    unsigned int  i = 0;
    unsigned int opnt_cnt = inst.getNumOperands();
        for(; i < opnt_cnt; ++i)
        {
          Value *opnd = inst.getOperand(i);
          std::string o;
          //          raw_string_ostream os(o);
          //         opnd->print(os);
          //opnd->printAsOperand(os, true, m);
          if (opnd->hasName()) {
            o = opnd->getName();
            std::cout << " " << o << "," ;
          } else {
            std::cout << " ptr" << opnd << ",";
          }
        }
        std:: cout << std::endl;
      }
    }
  }
  return 0;
}

请使用以下命令生成可执行文件:

clang++ ReadBitCode.cpp -o reader `llvm-config --cxxflags --libs --ldflags --system-libs`

以下面的c代码为例:

struct a {
  int f_a;
  int f_b;
  char f_c:5;
  char f_d:4;
};

int my_func( int arg1, struct a obj_a) {
  int x = arg1;
  return x+1 + obj_a.f_c;
}

int main() {
  int a = 11;
  int b = 22;
  int c = 33;
  int d = 44;
  struct a obj_a;
  obj_a.f_a = 1;
  obj_a.f_b = 2;
  obj_a.f_c = 3;
  obj_a.f_c = 4;
  if ( a > 10 ) {
    b = c;
  } else {
    b = my_func(d, obj_a);
  }
  return b;
}

在以下命令之后,我们可以获得一些输出:

clang -emit-llvm -o foo.bc -c foo.c
./reader foo.bc

输出应如下所示:

 Name: foo.bc
 Target triple: x86_64-unknown-linux-gnu
 Function: my_func
  BasicBlock: entry
   Instruction 0x18deb68 : alloca ptr0x18db940,
   Instruction 0x18debe8 : alloca ptr0x18db940,
   Instruction 0x18dec68 : alloca ptr0x18db940,
   Instruction 0x18dece8 : alloca ptr0x18db940,
   Instruction 0x18de968 : getelementptr coerce, ptr0x18de880, ptr0x18de880,
   Instruction 0x18de9f0 : store obj_a.coerce0, ptr0x18de968,
   Instruction 0x18df0a8 : getelementptr coerce, ptr0x18de880, ptr0x18db940,
   Instruction 0x18df130 : store obj_a.coerce1, ptr0x18df0a8,
   Instruction 0x18df1a8 : bitcast obj_a,
   Instruction 0x18df218 : bitcast coerce,
   Instruction 0x18df300 : call ptr0x18df1a8, ptr0x18df218, ptr0x18de8d0, ptr0x18de1a0, ptr0x18de1f0, llvm.memcpy.p0i8.p0i8.i64,
   Instruction 0x18df3a0 : store arg1, arg1.addr,
   Instruction 0x18df418 : load arg1.addr,
   Instruction 0x18df4a0 : store ptr0x18df418, x,
   Instruction 0x18df518 : load x,
   Instruction 0x18df5a0 : add ptr0x18df518, ptr0x18db940,
   Instruction 0x18df648 : getelementptr obj_a, ptr0x18de880, ptr0x18deab0,
   Instruction 0x18df6b8 : load f_c,
   Instruction 0x18df740 : shl bf.load, ptr0x18deb00,
   Instruction 0x18df7d0 : ashr bf.shl, ptr0x18deb00,
   Instruction 0x18df848 : sext bf.ashr,
   Instruction 0x18df8d0 : add add, conv,
   Instruction 0x18df948 : ret add1,
 Function: llvm.memcpy.p0i8.p0i8.i64
 Function: main
  BasicBlock: entry
   Instruction 0x18e0078 : alloca ptr0x18db940,
   Instruction 0x18e00f8 : alloca ptr0x18db940,
   Instruction 0x18e0178 : alloca ptr0x18db940,
   Instruction 0x18e01f8 : alloca ptr0x18db940,
   Instruction 0x18e0278 : alloca ptr0x18db940,
   Instruction 0x18e02f8 : alloca ptr0x18db940,
   Instruction 0x18e0378 : alloca ptr0x18db940,
   Instruction 0x18e0410 : store ptr0x18de880, retval,
   Instruction 0x18e04a0 : store ptr0x18dfe30, a,
   Instruction 0x18e0530 : store ptr0x18dfe80, b,
   Instruction 0x18e05c0 : store ptr0x18dfed0, c,
   Instruction 0x18e0650 : store ptr0x18dff20, d,
   Instruction 0x18e06f8 : getelementptr obj_a, ptr0x18de880, ptr0x18de880,
   Instruction 0x18e0780 : store ptr0x18db940, f_a,
   Instruction 0x18e0828 : getelementptr obj_a, ptr0x18de880, ptr0x18db940,
   Instruction 0x18e08b0 : store ptr0x18deab0, f_b,
   Instruction 0x18e0958 : getelementptr obj_a, ptr0x18de880, ptr0x18deab0,
   Instruction 0x18e09c8 : load f_c,
   Instruction 0x18e0a50 : and bf.load, ptr0x18dff70,
   Instruction 0x18e0ae0 : or bf.clear, ptr0x18deb00,
   Instruction 0x18e0b70 : store bf.set, f_c,
   Instruction 0x18e0c18 : getelementptr obj_a, ptr0x18de880, ptr0x18deab0,
   Instruction 0x18e0c88 : load f_c1,
   Instruction 0x18e0d10 : and bf.load2, ptr0x18dff70,
   Instruction 0x18e0da0 : or bf.clear3, ptr0x18dffc0,
   Instruction 0x18ded80 : store bf.set4, f_c1,
   Instruction 0x18dedf8 : load a,
   Instruction 0x18dee80 : icmp ptr0x18dedf8, ptr0x18e0010,
   Instruction 0x18def28 : br cmp, if.else, if.then,
  BasicBlock: if.then
   Instruction 0x18def98 : load c,
   Instruction 0x18e1440 : store ptr0x18def98, b,
   Instruction 0x18df008 : br if.end,
  BasicBlock: if.else
   Instruction 0x18e14b8 : load d,
   Instruction 0x18e1528 : bitcast obj_a.coerce,
   Instruction 0x18e1598 : bitcast obj_a,
   Instruction 0x18e1680 : call ptr0x18e1528, ptr0x18e1598, ptr0x18de8d0, ptr0x18de880, ptr0x18de1f0, llvm.memcpy.p0i8.p0i8.i64,
   Instruction 0x18e1738 : getelementptr obj_a.coerce, ptr0x18de880, ptr0x18de880,
   Instruction 0x18e17a8 : load ptr0x18e1738,
   Instruction 0x18e1848 : getelementptr obj_a.coerce, ptr0x18de880, ptr0x18db940,
   Instruction 0x18e18b8 : load ptr0x18e1848,
   Instruction 0x18e1970 : call ptr0x18e14b8, ptr0x18e17a8, ptr0x18e18b8, my_func,
   Instruction 0x18e1a10 : store call, b,
   Instruction 0x18e1a88 : br if.end,
  BasicBlock: if.end
   Instruction 0x18e1af8 : load b,
   Instruction 0x18e1b68 : ret ptr0x18e1af8,

说明

为了更好地了解上述输出,请注意。

LLVM使用指令地址作为返回值id

在内部,对于每个LLVM指令,LLVM将直接使用其指令的地址来表示返回值。当返回值用于另一条指令时,它将直接使用该指令的地址。

对于由clang生成的人类可读IR,返回值(例如%0%add%conv由LLVM IR写入生成,仅供阅读

LLVM Instruction类没有LLVM IR文件行号信息

LLVM IR仅包含有关原始C源代码的行号信息。这意味着我们无法了解LLVM IR代码中每个操作的行号。

因此,虽然我们可以逐行解析操作,但我们无法知道操作位于哪一行。

参考

上述源代码是从How to write a custom intermodular pass in LLVM?借用的,并且也针对此问题进行了修改。