LLVM 命令行使用简述
- 2019-03-09
- Phosphorus15
llvm
,全称Low-Level Virtual Machine
,顾名思义,就是“底层虚拟机”。作为一个功能完备的编程语言后端,已经被clang
、ghc
等编译器支持(其实clang
是完全依赖于llvm的)。通过一套LLVM IR
中间字节码/语言,前端和后端(以及优化器/JIT等中间件)得以共享一套简洁易用的表示。
关于llvm
的架构以及与传统编译系统的区别,这里有一篇非常好的文章。我在此也不多做阐述了。
安装
因为这里没有需求,我并没有下载llvm
的源码进行编译,仅仅只是把相关的软件包和工具集进行了安装。
体验llvm
,最直接的方式就是先安装clang
(这也是官网上推荐的方式 :wink: )
1 | sudo apt install clang |
clang
和gcc
的用法差别不大,对应的也有支持C++
的clang++
。
写一个测试程序
1 |
|
用clang
编译
1 | phosphorus15@ubuntu:~/ir$ clang aplusb.c -o aplusb.bin |
也是一个挺正常的C编译器嘛。因为clang
是用llvm
作为后端的,此时系统里其实已经有了很多llvm
的相关库了。下面,我们安装llvm
主体和主要组件
1 | sudo apt install llvm-runtime # 运行时 |
基本使用
前面提到LLVM IR
及其字节码是llvm
架构中前后端通用的形式,因为clang
是以llvm
为后端,不难想见,我们可以让clang
生成LLVM IR
供我们参考。命令如下:
1 | phosphorus15@ubuntu:~/ir$ clang -emit-llvm aplusb.c -c -o aplusb.bc |
这样,目录下就多出了一个aplusb.bc
,需要注意的是,*.bc
文件是已经被编码之后的字节码形式,并不是LLVM IR
代码,如果直接查看,是不能看到任何有意义的内容的。
此时,如果file
这个生成的*.bc
文件,会有如下结果
1 | phosphorus15@ubuntu:~/ir$ file aplusb.bc |
显示这是一个字节码文件。
和Java虚拟机
一样,LLVM
也可以直接执行字节码,使用lli
命令就能运行一个字节码文件
1 | phosphorus15@ubuntu:~/ir$ lli aplusb.bc |
那么,有了字节码文件,如何查看相应的LLVM IR
呢?这时就要用到llvm-dis
命令,在bash
下键入
1 | phosphorus15@ubuntu:~/ir$ llvm-dis < aplusb.bc > aplusb.ll |
这样,就生成了一个包含LLVM IR
的*.ll
文件,查看文件,有以下内容:
1 | ; ModuleID = '<stdin>' |
暂且不关注这些IR
内容的意义(其实我写到这里也不太清楚:joy:),先继续看看llvm
的其它组件。
既然可以把字节码反编译成IR
,那相对的,应该也是可以进行编译的,我们有llvm-as
命令进行编译(其实应该叫编码?)操作
1 | phosphorus15@ubuntu:~/ir$ llvm-as < aplusb.ll > aplusb1.bc |
可以看到,llvm-as
生成的aplusb1.bc
字节码和原来的aplusb.bc
具有相同的MD5
校验和,因此可以初步判断两个文件是完全一致的。
从llvm
的机制就知道,到字节码,一切都还没有结束,llc
命令可以让我们更进一步,通过IR
字节码生成目标平台下的汇编文件。
1 | phosphorus15@ubuntu:~/ir$ llc aplusb.bc -o aplusb.s |
默认生成的汇编是当前平台的,如果想要生成别的目标平台的汇编文件,修改参数就能很轻易地改变llc
的目标平台
1 | phosphorus15@ubuntu:~/ir$ llc -march=arm aplusb.bc -o aplusb-arm.s # 生成 arm 平台的汇编 |
我们可以看看两个文件的main
代码段当中一些很明显的对比(其实就是想凑凑字数:smile:)
aplusb - x86_64
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20main: # @main
.cfi_startproc
# BB#0:
pushq %rbp
.Ltmp0:
.cfi_def_cfa_offset 16
.Ltmp1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
.Ltmp2:
.cfi_def_cfa_register %rbp
subq $32, %rsp
...
callq __isoc99_scanf
...
movl $.L.str.1, %edi
xorl %eax, %eax
callq printf
...
retqaplusb - arm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16main: @ @main
.fnstart
@ BB#0:
push {r11, lr}
mov r11, sp
sub sp, sp, #32
...
bl __isoc99_scanf
...
str r3, [sp, #4]
bl printf
mov r0, #0
mov sp, r11
pop {r11, lr}
mov pc, lr
.align 2
因为架构的差异,很多指令和操作的实现方式都风格迥异,但由此我们也能从侧面看出llvm
的强大能力——把来自不同前端编译器(意味着不同语言、子语言)的IR
输出成不同平台的汇编。其弹性和兼容性都是十分可观的。
当得到汇编文件以后,就能够直接进行编译了,可以使用一些常见的汇编器,也可以直接用gcc
来完成汇编和链接。
1 | phosphorus15@ubuntu:~/ir$ gcc aplusb.s -o aplusb.native |
由此,已经大致介绍完了大部分常用的相关命令,更多内容可以参考官方文档。