LLVM学习笔记
【Getting Started with LLVM Core Libraries】P38 3.6 实验 编写你的第一个LLVM项目编写Makefile
以下为书中第一段代码:以下为书中第二段代码:以下为书中第三段代码:
编写代码
以下为书中代码:mysource.cpp
执行结果:目前不知道如何解决
【Getting Started with LLVM Core Libraries】P38 3.6 实验 编写你的第一个LLVM项目
在本节中,我们将展示如何使用LLVM库来编写你的第一个项目。在前面的章节中,我们介绍了如何使用LLVM工具来生成与程序相对应的中间语言文件,即位码文件。现在我们将创建一个程序,该程序能够读取此位码文件并打印其中定义的函数名称,以及它们的基本块数量,从而显示LLVM库的易用性。
编写Makefile
链接LLVM库需要使用长命令行,如果没有构建系统的帮助,想写出这些命令行是不切实际的。在下面的代码中,我们展示了一个Makefile文件(基于在DragonEgg中使用的代码)来完成这个任务,同时解释所提到的每个部分。
如果复制并粘贴此代码,将会丢失制表符。请记住,Makefile依赖于制表符来制定定义规则的命令,因此,应该手动插入制表符。
以下为书中第一段代码:
LLVM_CONFIG?
=llvm-config
ifndef VERBOSE
QUIET:
=@
endif
SRC_DIR?
=$(PWD)
LDFLAGS+
=$(shell $(LLVM_CONFIG) --ldflags
)
COMMON_FLAGS
=-Wall -Wextra
CXXFLAGS+
=$(COMMON_FLAGS) $(shell $(LLVM_CONFIG) --cxxflags
)
CPPFLAGS+
=$(shell $(LLVM_CONFIG) --cppflags
) -I
$(SRC_DIR)
第一部分定义将用作编译器标志的第一个Makefile变量。第一个变量决定llvm-config程序的位置,在这里,它需要在你的路径中。llvm-config工具是一个LLVM程序,它可以打印构建需要与LLVM库链接的外部项目的各种有用信息。 例如,定义在C++编译器中使用的标志集时,请注意,我们要求Make启动llvm-config --cxxflags shell命令行,该命令将打印用于编译LLVM项目的C++标志集。这样,我们就使得项目源码的编译与LLVM源码兼容。最后一个变量定义要传递给编译器预处理器的标志集。
以下为书中第二段代码:
HELLO
=helloworld
HELLO_OBJECTS
=hello.o
default:
$(HELLO)
%.o
: $(SRC_DIR)/%.cpp
@echo Compiling
$*.cpp
$(QUIET)$(CXX) -c
$(CPPFLAGS) $(CXXFLAGS) $
<
$(HELLO) : $(HELLO_OBJECTS)
@echo Linking
$@
$(QUIET)$(CXX) -o
$@ $(CXXFLAGS) $(LDFLAGS) $^
'$(LLVM_CONFIG) --libs bitreader core support'
在第二个片段中,我们定义了Makefile规则。第一个规则总是默认的,我们用它构建hello-world可执行文件。第二个是通用规则,它将所有C++文件编译成目标文件,我们将预处理器标志和C++编译器标志传递给它。我们还使用$(QUIET)变量来省略屏幕上出现的完整命令行,但是如果你想要一个详细的构建日志,则可以在运行GNU Make时定义VERBOSE。 最后一个规则链接所有目标文件(在这里只有一个)来构建与LLVM库链接的项目可执行文件。这部分工作是由链接器完成的,但是一些C++标志也可能会生效。因此,我们将C++和链接器标志都传递给命令行。我们用'command'结构来完成此操作,它指示shell用'command'的输出替换这部分内容。在我们的例子中,命令是llvm-config --libs bitreader core support。--libs标志向llvm-config请求提供用于链接到所请求的LLVM库的链接器标志列表。这里,我们请求libLLVMBitReader、libLLVMCore和libLLVMSupport。 由llvm- config返回的标志列表是一系列-l链接器参数,如-lLLVMCore -lLLVMSupport。
但请注意,传递给链接器的参数顺序很重要,并且要求你将依赖于其他库的参数放在前面。例如,由于libLLVMCore使用libLLVMSupport提供的通用功能,因此正确的顺序是-lLLVMCore -lLLVMSupport。
顺序很重要,因为一个库就是一个目标文件的集合,在将项目与库链接时,链接器只选择到目前为止已知的目标文件来解析所见到的未定义符号。因此,如果它正在处理命令行参数中的最后一个库,并且该库恰好使用了已经处理过的库中的符号,则大多数链接器(包括GNU ld)将不会返回去包括有可能缺失的目标文件,从而导致构建失败。 如果你想避免这个问题,并强制链接器迭代访问每个库,直到所有必要的目标文件都被解析,则必须在库列表的开始和结束处使用--start-group和--end-group标志,但这可能会减慢链接器速度。在构建完整的依赖关系图时,为了避免因为要弄清楚链接器参数的顺序而头疼,可以简单使用llvm-config --libs,让它为你做这些工作,就像我们之前做的那样。 Makefile文件的最后一部分定义了一条清理规则以删除编译器生成的所有文件,使我们可以从头开始重新启动构建。清理规则的格式如下:
以下为书中第三段代码:
clean::
$(QUIET)rm -f
$(HELLO) $(HELLO_OBJECTS)
编写代码
下面展示这个流程的完整代码。它相对较短,因为它建立在LLVM流程基础设施上,后者替我们完成了大部分工作。
以下为书中代码:mysource.cpp
#include "llvm/Bitcode/ReaderWriter.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/raw_os_ostream.h"
#include "llvm/Support/system_error.h"
#include <iostream>
using namespace llvm
;
static cl
::opt
<std
::string
> FileName(cl
::Positional
, cl
::desc("Bitcode file"), cl
::Required
);
int main(int argc
, char** argv
)
{
cl
::ParseCommandLineOptions(argc
, argv
, "LLVM hello world\n");
LLVMContext context
;
std
::string error
;
OwningPtr
<MemoryBuffer
> mb
;
MemoryBuffer
::getFile(FileName
, mb
);
Model
*m
= ParseBitcodeFile(mb
.get(), context
, &error
);
if (m
== 0) {
std
::cerr
<< "Error reading bitcode: " << error
<< std
::end
;
return -1;
}
raw_os_ostream
O(std
::cout
);
for (Module
::const_iterator i
= m
->getFunctionList().begin(), e
= m
->getFunctionList().end(); i
!= e
; ++i
) {
if (!i
->isDeclaration()) {
O
<< i
->getName() << " has " << i
->size() << " basic block(s).\n";
}
}
return 0;
}
我们的程序使用cl命名空间中的LLVM工具(cl代表命令行)来实现我们的命令行接口。我们只需调用ParseCommandLineOptions函数并声明cl::opt <string>类型的全局变量,以显示我们的程序接收单个参数,并且该参数是包含位码文件名的string类型。 之后,我们实例化一个LLVMContext对象,以存放与LLVM编译相关的所有数据,从而使LLVM是线程安全的。MemoryBuffer类为内存块定义一个只读接口,ParseBitcodeFile函数将使用这个对象来读取我们输入文件的内容,并解析文件中LLVM IR的内容。在检查完错误并确保一切正常后,我们遍历该文件中模块的所有函数。LLVM模块的概念类似于翻译单元,其中包含所有编码到位码文件中的内容,也是LLVM层次结构中的最高实体,在它后面是函数,然后是基本块,最后是指令。如果函数只是一个声明,则丢弃它,因为我们想查找函数定义。当我们找到这些函数定义时,将打印它们的名称和它包含的基本块的数量。 如果编译此程序,并使用-help运行,可以查看已为你的程序准备好的LLVM命令行功能。之后,查找要转换为LLVM IR的C或C++文件,然后将其转换并使用程序进行分析:
clang -c -emit-llvm mysource.cpp -o mysource.bc
helloworld mysource.bc
执行结果:目前不知道如何解决
hello.cpp:1:10: fatal error:
'llvm/Bitcode/ReaderWriter.h' file not found
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
clang-10: /home/wll/llvm/llvm/tools/clang/lib/Parse/ParseExprCXX.cpp:588: clang::ExprResult clang::Parser::tryParseCXXIdExpression
(clang::CXXScopeSpec
&, bool, clang::Token
&): Assertion `SS.isEmpty
() && "undeclared non-type annotation should be unqualified"' failed.
Stack dump:
0. Program arguments: /usr/local/bin/clang-10 -cc1 -triple x86_64-unknown-linux-gnu -emit-llvm-bc -emit-llvm-uselists -disable-free -main-file-name hello.cpp -mrelocation-model static -mthread-model posix -mframe-pointer=all -fmath-errno -masm-verbose -mconstructor-aliases -munwind-tables -fuse-init-array -target-cpu x86-64 -dwarf-column-info -debugger-tuning=gdb -resource-dir /usr/local/lib/clang/10.0.0 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/c++/7.5.0 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/x86_64-linux-gnu/c++/7.5.0 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/x86_64-linux-gnu/c++/7.5.0 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/c++/7.5.0/backward -internal-isystem /usr/local/include -internal-isystem /usr/local/lib/clang/10.0.0/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -fdeprecated-macro -fdebug-compilation-dir /home/wll/firstLLVM -ferror-limit 19 -fmessage-length 0 -fgnuc-version=4.2.1 -fobjc-runtime=gcc -fcxx-exceptions -fexceptions -fdiagnostics-show-option -fcolor-diagnostics -faddrsig -o hello.bc -x c++ hello.cpp
1. /usr/local/include/llvm/ADT/SmallString.h:53:28: at annotation token
2. /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/c++/7.5.0/ostream:41:1: parsing namespace 'std
'
3. /usr/local/include/llvm/ADT/SmallString.h:20:1: parsing namespace 'std::llvm
'
4. /usr/local/include/llvm/ADT/SmallString.h:25:1: parsing struct/union/class body 'std::llvm::SmallString
'
5. /usr/local/include/llvm/ADT/SmallString.h:51:37: parsing function body 'std::llvm::SmallString::assign
'
6. /usr/local/include/llvm/ADT/SmallString.h:51:37: in compound statement ('{}'
)
clang-10: error: unable to execute command: Aborted
(core dumped
)
clang-10: error: clang frontend
command failed due to signal
(use -v to see invocation
)
clang version 10.0.0
(http://llvm.org/git/clang.git 65acf43270ea2894dffa0d0b292b92402f80c8cb
) (http://llvm.org/git/llvm.git 2c4ca6832fa6b306ee6a7010bfb80a3f2596f824
)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /usr/local/bin
clang-10: note: diagnostic msg: PLEASE submit a bug report to https://bugs.llvm.org/ and include the crash backtrace, preprocessed source, and associated run script.
clang-10: note: diagnostic msg: Error generating preprocessed source
(s
).
如果要进一步了解可以从函数中提取的内容,请参阅LLVM Doxygen文档中关于llvm::Function类的内容,网址为http://llvm.org/docs/doxygen/html/classllvm_1_1Function.html。作为一个练习,请尝试扩展这个例子,以打印每个函数的参数列表。