如何读写LLVM bitcode
本文翻译自How to read & write LLVM bitcode
我已经在社交媒体上读了很多帖子,它们都在抱怨LLVM太可怕了。Repository非常庞大,每天提交数百次,邮件列表几乎不可能跟踪,生成的可执行文件现在已经超过40Mb,这对解决问题没有任何帮助。
撇开这些麻烦不谈,一旦你能抓住重点,LLVM就非常容易处理。为了帮助人们使用LLVM,我想我应该把用LLVM可以做的最简单的no-op示例放在一起 —— 解析LLVM的一个中间表示文件(称为bitcode,文件扩展名.bc),然后将其写出来。
首先,让我们来看一些高级LLVM术语:
- LLVM对用户代码的主要抽象是
Module
。它是一个类,包含您或其他用户编写的代码的所有函数、全局变量和指令。 bitcode
文件实际上是LLVM Module
的序列化,以便以后可以在不同的程序中重建它。- LLVM使用
MemoryBuffer
对象来处理来自文件、stdin或数组的数据。
对于我的例子,我们将使用LLVM C API
—— 一个在LLVM的核心C++ header
之上的更稳定的抽象。如果您希望使用多个版本的LLVM,那么C API
非常有用,它比LLVM C++ header
稳定得多。(顺便说一句,我在工作中大量使用LLVM,几乎每周都会有一些LLVM C++ header
的更改会破坏我们的代码。我从来没有过C API
破坏过我的代码。
首先,我假设您已经下载了LLVM,构建并安装了它。下面是这个过程简单的步骤:
git clone https://git.llvm.org/git/llvm.git <llvm dir>
cd <llvm dir>
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=RELEASE -DCMAKE_INSTALL_PREFIX=install ..
cmake --build . --target install
完成上述操作后,您将在/build/install中安装LLVM !
对于我们的小型的可执行文件,我使用了CMake。CMake是目前为止与LLVM集成最简单的方法,因为它也是LLVM所使用的构建系统。
project(llvm_bc_parsing_example)
cmake_minimum_required(VERSION 3.4.3)
# option to allow a user to specify where an LLVM install is on the system
set(LLVM_INSTALL_DIR "" CACHE STRING "An LLVM install directory.")
if("${LLVM_INSTALL_DIR}" STREQUAL "")
message(FATAL_ERROR "LLVM_INSTALL_DIR not set! Set it to the location of an LLVM install.")
endif()
# fixup paths to only use the Linux convention
string(REPLACE "\\" "/" LLVM_INSTALL_DIR ${LLVM_INSTALL_DIR})
# tell CMake where LLVM's module is
list(APPEND CMAKE_MODULE_PATH ${LLVM_INSTALL_DIR}/lib/cmake/llvm)
# include LLVM
include(LLVMConfig)
add_executable(llvm_bc_parsing_example main.c)
target_include_directories(llvm_bc_parsing_example PUBLIC ${LLVM_INCLUDE_DIRS})
target_link_libraries(llvm_bc_parsing_example PUBLIC LLVMBitReader LLVMBitWriter)
现在我们有了CMake设置,我们可以使用现有安装的LLVM,现在我们可以开始实际的C代码了!
所以要使用LLVM C API,你基本上总是需要一个头文件:
#include <llvm-c/Core.h>
和我们需要为我们的可执行文件准备两个额外的头文件,分别是bitcode reader和writer:
#include <llvm-c/BitReader.h>
#include <llvm-c/BitWriter.h>
现在我们创建main函数。这里我假设我们总是取恰好两个命令行参数,第一个是输入文件,第二个是输出文件。LLVM有一个系统,如果提供一个名为“-”的文件,这意味着从stdin读取或写入stdout,所以我决定也支持它:
if (3 != argc) {
fprintf(stderr, "Invalid command line!\n");
return 1;
}
const char *const inputFilename = argv[1];
const char *const outputFilename = argv[2];
首先我们解析输入文件。我们将从stdin或文件名中创建一个LLVM内存缓冲区对象:
LLVMMemoryBufferRef memoryBuffer;
// check if we are to read our input file from stdin
if (('-' == inputFilename[0]) && ('\0' == inputFilename[1])) {
char *message;
if (0 != LLVMCreateMemoryBufferWithSTDIN(&memoryBuffer, &message)) {
fprintf(stderr, "%s\n", message);
free(message);
return 1;
}
} else {
char *message;
if (0 != LLVMCreateMemoryBufferWithContentsOfFile(
inputFilename, &memoryBuffer, &message)) {
fprintf(stderr, "%s\n", message);
free(message);
return 1;
}
}
因此,在这段代码之后,memoryBuffer将可用来将我们的字节码文件读入LLVM Module
。因此,让我们开始创建Module
// now create our module using the memory buffer
LLVMModuleRef module;
if (0 != LLVMParseBitcode2(memoryBuffer, &module)) {
fprintf(stderr, "Invalid bitcode detected!\n");
LLVMDisposeMemoryBuffer(memoryBuffer);
return 1;
}
// done with the memory buffer now, so dispose of it
LLVMDisposeMemoryBuffer(memoryBuffer);
一旦我们有了我们的Module
,我们不再需要内存缓冲区,所以我们可以立即释放内存。这是它!我们已经成功地获取了一个LLVM bitcode文件,并将其反序列化到LLVM Module中,我们可以(至少在本文中我不会这么做!)因此,假设您已经对LLVM Module做了所有想要做的事情,并且希望再次将sucker写回bitcode文件。
该方法与读取方法正交,我们寻找特殊的文件名’ - ',并相应地处理:
// check if we are to write our output file to stdout
if (('-' == outputFilename[0]) && ('\0' == outputFilename[1])) {
if (0 != LLVMWriteBitcodeToFD(module, STDOUT_FILENO, 0, 0)) {
fprintf(stderr, "Failed to write bitcode to stdout!\n");
LLVMDisposeModule(module);
return 1;
}
} else {
if (0 != LLVMWriteBitcodeToFile(module, outputFilename)) {
fprintf(stderr, "Failed to write bitcode to file!\n");
LLVMDisposeModule(module);
return 1;
}
}
最后,我们应该做一个好公民,清理我们的垃圾,所以也删除模块:
LLVMDisposeModule(module);
这是它!现在您可以解析并编写LLVM bitcode文件。我已经将完整的示例放在了GitHub上——https://github.com/sheredom/llvm_bc_parsing_example。
也许我会在某个时候写一篇后续文章,向您介绍如何对LLVM Module执行操作的基础知识。