首页 > 编程语言 >一. 使用LLVM编译程序

一. 使用LLVM编译程序

时间:2023-07-11 19:11:14浏览次数:47  
标签:LLVM HelloLLVM clang 编译 llvm 使用 print 编译程序

1. 编译C程序:

编写 C 程序 HelloLLVM.c:

#include <stdio.h>
int main() {
 printf("Hello LLVM!\n");
}

执行编译命令:

clang HelloLLVM.c -o HelloLLVM

运行结果:

./HelloLLVM
Hello LLVM!

2. 编译 C++ 程序:

类似的,编写 C++ 程序 HelloLLVM.cpp:

#include <iostream>

using namespace std;

int main() {
 cout << "Hello LLVM!" << std::endl;
}

使用 clang++ 编译:

clang++ HelloLLVM.cpp -o HelloLLVM

运行结果:

./HelloLLVM
Hello LLVM!

3. 拆解编译过程:

我们前面展示的编译过程是通过 Clang 进而调用 LLVM 编译出最终的程序,这其中包括了多个步骤。Clang 提供了众多编译参数,通过这些参数我们可以控制编译的过程,使其只进行其中的某一个或多个步骤。

clang --help | grep 'Only run'
  -c                 Only run preprocess, compile, and assemble steps
  -E                 Only run the preprocessor
  -S                 Only run preprocess and compilation steps

我们通过以上对帮助文档的过滤可以找到拆分步骤的参数。

3.1. 预处理

假设我们有一个自定义的头文件 print.h。

void print(const char *);

在 HelloLLVM.c 中 include 这个头文件。

#include "print.h"

int main() {
    print("Hello LLVM!\n");
}

执行下面的命令:

 clang -E HelloLLVM.c -o HelloLLVM.e
 cat HelloLLVM.e

预处理的结果:

在开头是一些注释,紧接着就是 print.h 的内容,被原封不动的插入到了预处理结果中。

3.2. 生成汇编代码

原博命令:

clang -S HelloLLVM.e -o HelloLLVM.s

但是我没成功。。

3.3. 汇编

这一步将把汇编代码翻译成机器码:

clang -c HelloLLVM.c -o HelloLLVM.o
file HelloLLVM.o
HelloLLVM.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

 这一步生成的文件通常成为对象文件,更专业的说法可能是可重定位文件。之所以说可重定向是因为这个文件的符号在下一步过程中会被放在一个更大的文件中,那么符号的位置也自然会被重新确立位置。

3.4. 链接

这是我们刚才提到的 print.h 对应的 print.c 文件。

#include <stdio.h>

void print(const char * str)
{
    printf("%s", str);
}

把前面的 print 函数也编译成对象文件

clang -c print.c -o print.o
file print.o
print.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

链接过程实际上是使用 ld 命令,不过这个命令需要配合许多参数一起使用,我们很少会手动调用,一般是通过编译工具来帮助我们完成,这里我们使用 clang 来完成链接过程

clang HelloLLVM.o print.o -o HelloLLVM
file HelloLLVM
HelloLLVM: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, not stripped
 可以通过给 clang 加 --verbose 参数获得更多编译细节,你可以看到 clang 确实是使用了 ld 来完成链接过程的
clang HelloLLVM.o print.o HelloLLVM --verbose

获得编译器更多输出。

验证一下链接出来的可执行程序:

./HelloLLVM 
Hello LLVM!

3.5. 中间表示

实际上作为一般的编译器都可以拆解成以上步骤:预处理、输出汇编、汇编、链接。而 LLVM 有强大的中间表示,这种中间表示可以以物理的形式存在,而非想 gcc 只能在内存中临时存在。

不过 clang 本身并没有开放这样的功能,clang 留了一个“后门”,通过它你可以直接控制 clang 背后的引擎程序(clang命令 实际上是一个调度程序,实际的工作都由它来完成)

clang -cc1 HelloLLVM.c -emit-llvm
cat HelloLLVM.ll 

LLVM 的中间表示:

打开这个“后门”就是通过加 -cc1 参数,

clang -cclang -cc1是Clang编译器的两个不同选项,用于不同的编译步骤。

  1. clang -c:这是Clang的编译选项,用于将源代码文件编译为目标文件(二进制文件)。当你执行clang -c <source_file>时,Clang会进行以下步骤:

    • 预处理:执行宏展开、条件编译等预处理操作。
    • 编译:将预处理后的源代码编译为汇编代码。
    • 汇编:将汇编代码转换为目标文件(二进制文件),这个目标文件可以被链接器使用。

    例如,执行以下命令将source.c编译为object.o目标文件:

    clang -c source.c -o object.o
  2. clang -cc1:这是Clang的底层编译器驱动程序,用于控制Clang的内部编译过程。-cc1选项用于手动指定Clang的各个编译阶段,可以对源代码进行逐步的编译和转换。它通常与其他选项结合使用,用于进行高级的编译控制和分析。

    例如,执行以下命令将source.c进行逐步编译:

    clang -cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o source.bc -x c source.c
    

     

     

    在上述命令中,-cc1选项告诉Clang使用底层编译器驱动程序,-emit-llvm选项告诉Clang生成LLVM字节码,-o source.bc指定生成的字节码文件名为source.bc-x c指定输入源代码类型为C语言。

  总结来说,clang -c是用于将源代码编译为目标文件的简化选项,而clang -cc1是Clang底层编译器驱动程序的选项,用于手动控制编译过程和进行更高级的编译控制。

参数 -emit-llvm 表示输出 LLVM 中间表示,默认会保存在同名的 .ll 文件中。

这里我们看到,.ll 文件是一个文本形式,我们还可以将其转换成二进制形式。

lvm-as HelloLLVM.ll
file HelloLLVM.bc
HelloLLVM.bc: LLVM IR bitcode

 这个过程是可逆的,通过以下命令完成。

llvm-dis HelloLLVM.bc

中间表示也可以被编译。

clang -c HelloLLVM.ll -o HelloLLVM.o

或者

clang -c HelloLLVM.bc -o HelloLLVM.o

llc 是 llvm 的后端,可以用来把 中间表示编译成汇编。

llc HelloLLVM.bc -o HelloLLVM.s

这与前面用 clang -S 是一样的效果。

3.6. 中间表示链接

程序指令在优化的时候,单看独立的文件有时候不能很好的进行优化,所以 llvm-link 提供了把独立的 IR 文件链接在一起的功能。

我们先生成 print.c 对应的 IR 文件。

clang -cc1 -emit-llvm print.c

print.c:1:10: fatal error: 'stdio.h' file not found

#include <stdio.h>

         ^~~~~~~~~

1 error generated.

按照刚才的方式,结果报错了,原因是 我们加入了 -cc1 参数,使用了背后的引擎,没有调度工具 clang 帮助我们添加指定头文件目录的工作。但这也不难,我们通过 --verbose 看看 clang 都加了那些参数。

clang -c print.c --verbose

获得编译器的更多输出  把里面的参数复制出来,并修改 -emit-obj 为 -emit-llvm,去掉 -o print.o。

-cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -mrelax-all -disable-free -disable-llvm-verifier -discard-value-names -main-file-name print.c -mrelocation-model static -mthread-model posix -mdisable-fp-elim -fmath-errno -masm-verbose -mconstructor-aliases -munwind-tables -fuse-init-array -target-cpu x86-64 -v -dwarf-column-info -debugger-tuning=gdb -coverage-notes-file /home/cc16-04/test/print.gcno -resource-dir /usr/local/bin/../lib/clang/4.0.0 -internal-isystem /usr/local/include -internal-isystem /usr/local/bin/../lib/clang/4.0.0/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -fdebug-compilation-dir /home/cc16-04/test -ferror-limit 19 -fmessage-length 80 -fobjc-runtime=gcc -fdiagnostics-show-option -x c print.c

 

得到 print.ll 文件,把两个 IR 文件链接起来

llvm-link print.ll HelloLLVM.ll -S -o all.ll

查看 all.ll

cat all.ll

同时出现了 print 和 main 两个定义

3.7.语法分析

clang可以输出程序的抽象语法树(也可以以图象形式输出。)

clang -cc1 -ast-dump HelloLLVM.c

3.8. 生成控制流图

首先需要安装graphviz

sudo apt install graphviz

对于中间表达的*.ll文件

opt -dot-cfg all.ll

 生成了main和print各自的dot文件,以main为例

dot -Tpng .cfg.main.dot -o main.png

 得到


参考:1. https://github.com/tuoxie007/play_with_llvm/blob/master/ch02.md

   2. (24条消息) LLVM 编译器学习笔记之二十九 -- 控制流程CFG_llvm控制流图_清钟沁桐的博客-CSDN博客

标签:LLVM,HelloLLVM,clang,编译,llvm,使用,print,编译程序
From: https://www.cnblogs.com/Liuyaaa/p/17545625.html

相关文章

  • 《设计模式的运用》使用策略模式+工厂模式优化代码中的if else
    使用策略模式优化ifelse有这样一段逻辑function{for{if()if()if(if())...}}公司有的祖传的代码,是一段规则校验,校验的越多,每一个请求都会进来校验,加上后来的开发人员也......
  • xpath使用
    ###xpath使用```python页面中定位元素(标签),两种通用方式# -css选择器#-xpath:XPath即为XML路径语言(XMLPathLanguage),它是一种用来确定XML文档中某部分位置的语言#xpath语法#div 选取div标签#/ 从根节点选取#// 从匹配选择的当前节点选择文档中......
  • gitlab使用runner来实现CI/CD
    1:安装runner比如,我们需要在192.168.3.129服务器上来实现自动部署,那我们就在这台服务器上安装runner在gitlab后台,比如tn项目,那我们进入项目,在设置中,找到CI/CD点击展开,新建项目runner 选择项目信息,Linux、标签自己填写、下面的勾记得勾选一下(运行未打标签的作业),后面的可写可......
  • 使用whisper批量生成字幕(whisper.cpp)
    前言最近发现了whisper这个语音生成字幕的本地工具,但是whisper速度不算快,然后在github上发现了whisper.cpp这个项目,执行速度更快,还可以在命令行使用,这样就可以自己定制了。命令行压缩包下载命令行下载地址:https://github.com/Const-me/Whisper/releases下载【cli.zip】,解压即......
  • "Tarfs"是一个内存文件系统,它使用TAR(Tape Archive)文件格式来实现在内存中创建一个虚拟
    "Tarfs"是一个内存文件系统,它使用TAR(TapeArchive)文件格式来实现在内存中创建一个虚拟的文件系统。TAR文件格式是一种常见的存档文件格式,用于将多个文件和目录组合成单个文件。Tarfs通过将TAR文件加载到内存中,并在内存空间中模拟文件和目录结构,实现了一个简单的文件系统。它允许......
  • 创建属于自己的github、使用git提交、更新代码至github、写好readme
    1.在github上创建一个Repository点击github网站,你可以用你的邮箱先注册一个账号。点击New,转到创建一个repository的界面,如下图所示,你可以填写你的Repositoryname、description、选择是否公开、增添一个默认的Reademe等等,一般都可以选择上。2.使用git提交、更新代码至git......
  • 使用input标签的时候报错,提示Form elements must have labels: Element has no title
    使用input标签的时候报错,提示Formelementsmusthavelabels:ElementhasnotitleattributeElementhasnoplaceholderattribute大概就是下面这样其实规范化一下,加个label就可以了......
  • wpf的动态Tab的例子,使用Prism
    引用Prism.Core,Prism.Wpf和Prism.Unity修改App.xaml的类型替换为 PrismApplication 修改App.xaml.cs:///<summary>///InteractionlogicforApp.xaml///</summary>publicpartialclassApp:PrismApplication{protectedoverride......
  • [ESP] 使用Ayla API Reference配网和连Ayla云
    示例用的文档及链接USDevDashboard(查看oem-id和oem-key)https://dashboard-dev.aylanetworks.com/AylaAPIReference(绑定用户,设备和Ayla云)https://docs.aylanetworks.com/referenceAyla_demo的官方文档(构建,运行步骤)https://docs.aylanetworks.com/docs/integr......
  • OxyPlot曲线图控件的使用
    官网:https://github.com/oxyplot/oxyplot官方文档:https://oxyplot.readthedocs.io/en/latest/ Nuget包平台Nuget包版本WPFOxyPlot.Wpf2.1.2WindowsFormsOxyPlot.WindowsForms2.1.2AvaloniaOxyPlot.Avalonia2.1.0   ......