经过多年的发展,LLVM事实上已经对大部分语言进行了支持,其完备的功能和好的模块化和轻耦合的特性得到了很多人的认可,但是在很多传统领域,实际上的编译器还是gcc(基础设施),大部分人如果想使用LLVM对gcc进行替换时,会遇到一些麻烦。本文想从一个普通开发者的角度去解释一些传统gcc编译领域使用clang来进行替换的一些实际步骤。
我想根据project的特点,把LLVM替换gcc的编译过程分为以下三个类型(不完全是,简单分类):
1. 测试型的project
对于这种类型,一般就是几条简单的gcc或者g++编译命令,这种project建议完全复制,粘贴时将gcc替换为clang就能解决问题,大部分gcc支持option,clang都进行了支持,甚至支持的更好,一般用户很少能写出gcc支持而clang不支持的命令
2. 传统configure类型的project
对于这种类型的project,是目前一般用户见到的最多的。我这里以一个开源的bird-2.0.8来进行说明。(首先说明,非网络专业用户,仅测试使用)某度百科上的词条介绍是:BIRD是一个类UNIX系统的动态路由守护进程。它支持当代互联网中所用所有路由协议,如BGP、OSPF、RIP和这些协议的IPv6的变种(除OSPFv3目前尚在发展)。其实做什么的,我们并不关心,我们只关心如何得到一个正确的编译结果。
要想知道如何得到一个正确的结果,正常使用gcc搞这类project的流程是使用configure进行配置,然后make,后边make install进行安装,其中configure是用来生成Makefile文件的,也会进行很多配置的检测。这里我采用的思路是首先用默认配置来一遍,然后再换编译器。
从这里路径下载代码后,解压进入source文件夹。https://bird.network.cz/download/bird-2.0.8.tar.gz
./configure
会遇到
的问题,按照提示安装libreadline或者直接使用
./configure --disable-client make -j 32
就可以发现编译完成了,非常简单。
现在我们开始换编译器。先看下configure文件,发现有如下的介绍:
也就是说刚才在配置的时候,通过设置CC,CPP等环境变量就可以实现切换编译器的过程,我这里采用最暴力的方法,直接替换(其实也差不了太多)。
找到Makefile文件,查找CC的变量直接替换为CC=clang, 然后make,发现问题:
LD -pthread -flto=4 -g -o bird obj/conf/cf-parse.tab.o obj/conf/cf-lex.o obj/conf/conf.o obj/filter/filter.o obj/filter/data.o obj/filter/f-util.o obj/filter/tree.o obj/filter/trie.o obj/filter/inst-gen.o obj/lib/bitmap.o obj/lib/bitops.o obj/lib/checksum.o obj/lib/event.o obj/lib/flowspec.o obj/lib/idm.o obj/lib/ip.o obj/lib/lists.o obj/lib/mac.o obj/lib/md5.o obj/lib/mempool.o obj/lib/net.o obj/lib/patmatch.o obj/lib/printf.o obj/lib/resource.o obj/lib/sha1.o obj/lib/sha256.o obj/lib/sha512.o obj/lib/slab.o obj/lib/slists.o obj/lib/strtoul.o obj/lib/tbf.o obj/lib/timer.o obj/lib/xmalloc.o obj/nest/a-path.o obj/nest/a-set.o obj/nest/cli.o obj/nest/cmds.o obj/nest/iface.o obj/nest/locks.o obj/nest/neighbor.o obj/nest/password.o obj/nest/proto.o obj/nest/rt-attr.o obj/nest/rt-dev.o obj/nest/rt-fib.o obj/nest/rt-show.o obj/nest/rt-table.o obj/proto/bfd/bfd.o obj/proto/bfd/io.o obj/proto/bfd/packets.o obj/proto/babel/babel.o obj/proto/babel/packets.o obj/proto/bgp/attrs.o obj/proto/bgp/bgp.o obj/proto/bgp/packets.o obj/proto/mrt/mrt.o obj/proto/ospf/dbdes.o obj/proto/ospf/hello.o obj/proto/ospf/iface.o obj/proto/ospf/lsack.o obj/proto/ospf/lsalib.o obj/proto/ospf/lsreq.o obj/proto/ospf/lsupd.o obj/proto/ospf/neighbor.o obj/proto/ospf/ospf.o obj/proto/ospf/packet.o obj/proto/ospf/rt.o obj/proto/ospf/topology.o obj/proto/perf/perf.o obj/proto/pipe/pipe.o obj/proto/radv/packets.o obj/proto/radv/radv.o obj/proto/rip/packets.o obj/proto/rip/rip.o obj/proto/rpki/rpki.o obj/proto/rpki/packets.o obj/proto/rpki/tcp_transport.o obj/proto/rpki/ssh_transport.o obj/proto/rpki/transport.o obj/proto/static/static.o obj/sysdep/linux/netlink.o obj/sysdep/unix/io.o obj/sysdep/unix/krt.o obj/sysdep/unix/log.o obj/sysdep/unix/main.o obj/sysdep/unix/random.o clang-7: error: unsupported argument '4' to option 'flto=' Makefile:159: recipe for target 'bird' failed make: *** [bird] Error 1
我这里使用的LLVM 7.0,所以会提示clang-7,后边意思就是clang-7不支持gcc的-flto=4的option,man gcc查看该option的用法,是一个用于link级别的选项,再man clang就能发现clang在这个选项,要求使用 -flto -O2这种标准的用法,这里编译替换就好:
CFLAGS=$(CPPFLAGS) -g -O2 -pthread -fno-strict-aliasing -fno-strict-overflow -flto -Wall -Wextra -Wstrict-prototypes -Wno-parentheses -Wno-pointer-sign -Wno-missing-field-initializers
既然是LD提示的错误,直接给LDFLAG修改就好。
再次编译,会出现/usr/bin/ld: /home/daily_learning/oldLLVM/build/bin/../lib/LLVMgold.so: error loading plugin: /home/daily_learning/oldLLVM/build/bin/../lib/LLVMgold.so: cannot open shared object file: No such file or directory
也就是LLVMgold.so找不到的问题,到那个文件下,发现确实没有这个so文件,这是因为加了flto选项需要引入LLVMgold库,这个库是需要放在源码中进行编译的,网上的教程很多,不再赘述,解决掉这里后,发现就能得到和gcc一样的结果。整个替换过程比较简单。
相对直接用于test的project来说,一般project的configure和编译过程相对比较复杂,整个编译流程也分为了预编译、编译、链接三个过程,预编译一般没有问题,编译过程中可能遇到option的问题,链接过程可能遇到库的兼容性问题,相对来说,有点复杂也需要一点经验。但是对于想入手学习的人来说,还是非常有必要的。
对于想要进行学习交流的来说,很多时候需要获得的不是最终的链接结果,需要的是中间的bc文件,这个时候其实完全不需要考虑什么-c的编译过程中添加什么-emit-llvm什么选项这些复杂的问题,直接在CFLAGS后边添加-save-temps,你就可以获得中间文件了,虽然有点多。
3. 大型project
这部分其实和上边差不多,不过这种功能一般就不能暴力修改Makefile文件来进行了,需要预先配置编译器和环境变量。我这里给出一个我使用的环境变量,其他人可以对照修改(不保证完全够):
export LLVM_HOME=/home/daily_learning/oldLLVM export PATH=/home/local/bin:$LLVM_HOME/build/bin:$PATH export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$LLVM_HOME/build/lib export C_INCLUDE_PATH=$C_INCLUDE_PATH:$LLVM_HOME/build/include export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:$LLVM_HOME/build/include
说实话,非常想给出一个完整的例子。主要问题是,一个真实的project,想要全流程的介绍和切换,一般是稍微需要一点时间和耐心的,而且在短的篇幅内把遇到的问题都说明白,也非常考验project自身,所以找一个合适的project非常重要,待有机会再进行补充这部分内容。