偶然看到有人用 LLVM 配套工具生成的 CFG 图,就想看看怎么做出来的。但是吧,LLVM 就在最近一年左右的时间内修改了生成方法,导致网络上国内外很多人记录的方法无法使用,所以写本文记录一下。
假设这里有一个test.c
源代码文件,内容是计算矩阵(代码就不贴了,你随便找个程序就行)。
首先需要使用clang
和选项-emit-llvm
生成所需的 LLVM IR 文件,需要注意的是,不同优化等级生成的图像是不一样的,所以要注意是否使用-O3
等选项(最后会放出对比)。
-S
选项这里推荐使用-S
选项使得编译流程停止在汇编器之前,因为并不需要走完整个编译流程。
clang -emit-llvm -S test.c
这时候会产生一个test.ll
的文件,存放的是 LLVM IR。
-c
选项有些人使用的是-c
(操作到对象文件这一步),
clang -emit-llvm -c test.c
这时候产生的文件是test.bc
,bc
是 bitcode(位码)的意思。
.ll
和.bc
文件有什么区别?我在《clang到底是什么?gcc和clang到底有什么区别?》中提到过,LLVM 代码和位码是等价的(LLVM代码就是编译器的中间表达 LLVM IR):
所以这二者本质上没有区别,都可以使用。
至于有些人写的是同时使用了-S
和-c
,这实在是多此一举了。
关于编译流程和 LLVM IR 的详细内容还请看我的另外两篇博客:
clang到底是什么?gcc和clang到底有什么区别?
使用gcc展示完整的编译过程(gcc预处理模式、编译模式、汇编模式、连接模式)
然后使用 LLVM 配套工具opt
将前面获取的 LLVM IR 转换成你需要的某一种图的图像描述语言。
这里演示的是生成 CFG,方法如下:
#如果是.ll
opt -passes=dot-cfg test.ll
#如果是.bc
opt -passes=dot-cfg test.bc
由于产生的文件是隐藏的,使用ls -a
可以看到一个名为.main.dot
的文件,这里面存放的就是图像描述语言(转换时,终端输出信息中会显示Writing '.main.dot'...
,你的可能会不一样):
如果你想生成其他种类的图,那么可以在LLVM’s Analysis and Transform Passes中找到支持的图(调用图、依赖图等等),然后将上面命令中的dot-cfg
替换成对应的字符串即可。
opt -passname
syntax for the new pass manager is not supported这里需要注意一点,老的一些记录中,这部分命令为opt -dot-cfg test.bc
,但是如果你现在使用这个命令,则会出现以下提示:
% opt -dot-cfg test.bc
The `opt -passname` syntax for the new pass manager is not supported, please use `opt -passes=` (or the `-p` alias for a more concise version).
See https://llvm.org/docs/NewPassManager.html#invoking-opt for more details on the pass pipeline syntax.
这个方法已经不支持了,我们需要使用opt -passes=
这种格式来进行转换,而这里的
可以在https://llvm.org/docs/Passes.html中看到。比如说这里现在为opt -passes=dot-cfg
,而不是opt -dot-cfg
。
为了方便,大部分人都需要将其转换成 PNG 等格式方便阅读(支持很多格式,但是就不支持 JPEG 格式),方法也很简单:
dot -Tpng -o main.png .main.dot
然后就可以看到这样一张图了:
如果使用-O3
进行优化,那么这里的图像如下:
希望能帮到有需要的人~