逐行解析LLVM IR的方法

8
我需要在运行时逐行解析LLVM IR代码,以了解每行上正在发生的操作和操作数。具体来说,我希望知道每行上正在进行什么操作并操作哪些操作数。例如,如果IR代码如下:
%0 = load i32* %a, align 4

我想知道在运行我c++代码时,%a的值是否被加载到了%0中。 我已经考虑过使用简单的文本解析c++程序来完成这个任务(解析IR并搜索IR关键字),但是我想知道是否存在任何现有的库(可能是来自LLVM自身),可以帮助我避免这样做。

1个回答

10

假设

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

以下的回答假设你只关心 LLVM IR 文件中每个函数内部的操作,因为 LLVM IR 文件中的其他信息不包含有关操作的任何内容。操作只能位于函数中,而 IR 的其他部分(例如结构定义、函数声明等)仅包含类型信息,不包含任何关于操作的内容。

实现

基于上述假设,你关于逐行解析 LLVM IR 文件以获取 IR 文件中操作信息的问题可以转化为解析 LLVM IR 文件中每个函数中的操作信息。

LLVM 确实已经有了一个现有的实现,可以直接逐行解析 LLVM IR 文件以获取操作信息。由于 IR 文件中函数的顺序与它们在 LLVM IR 文件中出现的顺序相同,因此以下实现输出的操作序列就是给定 LLVM IR 文件中的操作序列。

因此,我们可以利用 llvm 提供的 parseBitcodeFile 接口。该接口首先使用 LLVM::LLLexer 将 LLVM IR 文件拆分为标记,然后将标记提供给解析器进行分析,并最终生成一个 ErrorOr<llvm::Module *> 模块信息。模块中的函数列表顺序与 llvm ir 文件中的顺序相同。

然后,我们可以对 LLVM::Module 中的每个 LLVM::Function 的每个 LLVM::BasicBlock 进行迭代,并获取有关每个操作数 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 代码中每个操作所在行的信息。

因此,尽管我们可以逐行解析操作,但我们无法知道每个操作所在的行。

参考文献

上面的源代码来源于如何在 LLVM 中编写自定义模块间传递?,并经过修改以适应本问题。


小错误:这里两次使用了 fileOrErr.getError() 而不是 moduleOrErr.takeError() - orestisf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接