想好好熟悉一下llvm开发一个新后端都要干什么,于是参考了老师的系列文章:
代码在这里(还没来得及准备,先用网盘暂存一下):
链接: https://pan.baidu.com/s/1yLAtXs9XwtyEzYSlDCSlqw?pwd=vd6s 提取码: vd6s
目录
2.6 Cpu0MachineFunctionInfo.cpp/.h
2.9 Cpu0TargetObjectFile.cpp/.h
2.10 MCTargetDesc/Cpu0BaseInfo.h
一、准备知识
1.1 链接器与编译器
首先,显而易见链接器和编译器肯定不是一个东西。编译器的作用是中端优化以及后端转化成目标架构相关的汇编文件或者目标文件,到这里编译器就算是完成了他的工作了。之后就需要链接器了,链接器的作用是将生成的目标文件与其他的应用库和系统库一起链接成可执行文件,传统链接器在这一过程中主要做了两个工作,一是段合并,链接器会将不同文件的相似段进行合并,另一个就是符号重定位,链接器会对文件进行第二次扫描,利用第一次扫描的符号表信息,对符号引用地址的地方进行地址的更新,这个就是符号的解析以及重定位过程。
注意,这里我们说的是传统链接器,因为现在的链接器都支持LTO(链接时优化),其实相当于将编译器部分的一些中端优化和后端的工作在链接阶段做,链接阶段所需的所有文件是可见的,因此能够在一个更高的视野上做一个全局的优化,所以现代链接器的功能绝不仅仅是上述两点。
本章是做Cpu0架构重定位的适配,因此本章简单介绍一下当前linux系统上的两种重定位机制。我们用下边的这个例子:
# extern.c
int extern_aaa = 555;
int extern_func() {
return 666;
}
#test.c
extern int extern_aaa;
int global_bbb = 111;
static int static_ccc = 222;
extern int extern_func();
int global_func() {
return 333;
}
static int static_func() {
return 444;
}
int main() {
int ddd1 = extern_aaa;
int ddd2 = global_bbb;
int ddd3 = static_ccc;
int ddd4 = extern_func();
int ddd5 = global_func();
int ddd6 = static_func();
return 0;
}
1.2 静态重定位
静态重定位一般由操作系统中的重定位装入程序完成。重定位装入程序根据当前内存的分配情况,按照分配区域的起始地址逐一调整目标程序指令中的地址部分。目标程序在经过重定位装入程序加工之后,不仅进入到分配给自己的绝对地址空间中,而且程序指令中的地址部分全部进行了修正,反映出了自己正确的存储位置,保证了程序的正确运行。
特点:即在程序装入内存的过程中完成,是指在程序开始运行前,程序中的各个地址有关的项均已完成重定位,地址变换通常是在装入时一次完成的,以后不再改变,故成为静态重定位。
优点:无需硬件支持。
缺点:程序重定位之后就不能在内存中搬动了;要求程序的存储空间是连续的,不能把程序放在若干个不连续的区域中。
clang -fno-PIC -g -c extern.c -o extern.o
clang -fno-PIC -g -c test.c -o test.o
clang -no-pie -g extern.o test.o -o test.out
clang默认是使用动态重定位的,因此我们使用no-pic选项来关闭PIC,同时打开-g选项方便我们看到更多的符号以及调试,我的机器的clang版本是15,不同版本的clang可能会有一些区别。
首先我们先通过objdump -d命令先看一下test.o中的内容
test.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <global_func>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: b8 4d 01 00 00 mov $0x14d,%eax
9: 5d pop %rbp
a: c3 retq
b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
0000000000000010 <main>:
10: 55 push %rbp
11: 48 89 e5 mov %rsp,%rbp
14: 48 83 ec 20 sub $0x20,%rsp
18: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
1f: 8b 04 25 00 00 00 00 mov 0x0,%eax
26: 89 45 f8 mov %eax,-0x8(%rbp)
29: 8b 04 25 00 00 00 00 mov 0x0,%eax
30: 89 45 f4 mov %eax,-0xc(%rbp)
33: 8b 04 25 00 00 00 00 mov 0x0,%eax
3a: 89 45 f0 mov %eax,-0x10(%rbp)
3d: b0 00 mov $0x0,%al
3f: e8 00 00 00 00 callq 44 <main+0x34>
44: 89 45 ec mov %eax,-0x14(%rbp)
47: e8 00 00 00 00 callq 4c <main+0x3c>
4c: 89 45 e8 mov %eax,-0x18(%rbp)
4f: e8 1c 00 00 00 callq 70 <static_func>
54: 89 45 e4 mov %eax,-0x1c(%rbp)
57: 8b 45 f8 mov -0x8(%rbp),%eax
5a: 03 45 f4 add -0xc(%rbp),%eax
5d: 03 45 f0 add -0x10(%rbp),%eax
60: 03 45 ec add -0x14(%rbp),%eax
63: 03 45 e8 add -0x18(%rbp),%eax
66: 03 45 e4 add -0x1c(%rbp),%eax
69: 48 83 c4 20 add $0x20,%rsp
6d: 5d pop %rbp
6e: c3 retq
6f: 90 nop
0000000000000070 <static_func>:
70: 55 push %rbp
71: 48 89 e5 mov %rsp,%rbp
74: b8 bc 01 00 00 mov $0x1bc,%eax
79: 5d pop %rbp
7a: c3 retq
1f、29、33三条指令就是对于extern_aaa、global_bbb、static_ccc三个变量的调用,我们能够看到当前是用0来占位的。我们能够看到他们最终都被存到了rbp寄存器一定偏移的栈中了,因此局部变量通过当前函数栈桢的基地址(rbp)就可以直接拿到,不存在重定
LLVM后端开发实战

最低0.47元/天 解锁文章
1804

被折叠的 条评论
为什么被折叠?



