一、编译过程简介
g++ -o hello hello.cpp
上述过程包括了四个步骤:预编译、编译、汇编、链接
1、预编译
g++ -E -o hello.i hello.cpp
主要处理#开始的预编译命令
1)#define: 将所有的#define删除,并在源文件中展开
2)#if 、#ifdef、#elif、#endif条件预编译指令:决定该代码是否需要编译
3)#include预编译指令,递归地(被包含的文件也有可能包含其他文件)将被包含的文件插入到该预编译指令的位置
4)删除所有注释
5)添加行号,用于显示错误需要的行号
2.编译
g++ -S -o hello.s hello.i 得到汇编文件
3.汇编
g++ -c -o hello.o hello.s 得到目标文件
4.链接
主要处理各个目标文件相互引用的部分,在独立编译的时候无法确定引用的标识符的地址,因此在链接的时候将引用的标识符的地址修正,包括:地址和空间分配、符号决议和重定位。
二、ELF文件介绍
#include<iostream>
using namespace std;
int main(){
int a=0;
cout<<a<<endl;
}
readelf -h main.o //查看elf文件头
文件头描述了整个文件属性,包括魔数、数据存储方式(大端、小端)、程序入口地址、段表位置、段表字符串表在段表里的下标、硬件平台等
魔数前几个十六进制用来确定ELF文件的类型(COEF、ELF等,不同平台会根据魔数是否正确来确认是否能够加载),后面几个十六进制确定了大小端、32位还是64位程序。
g++ -c -o main.o main.cpp
readelf -S main.o //显示.o文件的头部段表
1)段表为数组,每个元素为一个结构体,包括10个成员变量,Name,Type等
2)所有包含.rela开头的为重定位表,.rela.text为.text段的重定位表,main中调用了cout(对绝对地址的引用)因此需要重定位,而data则不需要,所以没有.rela.data段
3).bss段存放未初始化的全局变量和局部静态变量,在elf文件中实际不存在,只会在段表中声明.bss段的大小、偏移等,能够表示所有未初始化的全局变量和局部变量的size和,会在程序运行的时候将对应的空间清空。
4) common块,由于弱符号可以重复定义,因此编译的时候一个文件中的弱符号用shm_common字段来表示,在链接的时候选择size更大作为该符号值
5).strtab为字符表,将普通字符串存在这里,利用偏移来获取对应的字符串
.shstrtab段表字符串表,保存段表用到的字符串
.symtab为符号表,为一个结构数组,每个结构表示一个符号
1).text代码段
objdump -s main.o //以十六进制显示内容
objdump -d main.o //反汇编
2).data和.rodata数据段和只读数据段
.data保存已经初始化的全局变量和静态变量,原函数没有这种变量,因此.data区域大小为0
.rodata保存只读数据(const修饰)和字符串
3).bss存放未初始化的全局变量和静态变量
三、静态链接
三、符号
所有的变量、函数均可以看成是符号,符号值为其地址。符号可以分成以下几类:
1)本目标文件的全局符号,可以被其他文件引用
2)本文件引用的其他文件定义的全局符号,外部符号,如cout
3)段名,值为该段的起始地址
4)局部符号在编译单元内可见
5)行号
readelf -s main.o //查看符号表
链接主要做了两件事情,一是空间与地址分配,二是符号解析与重定位。由于文件是分别编译,因此.o文件的段地址用0X0000来代替,链接后会将相似段落合并,会给每个段分配地址,将所有.o文件的符号表联合成全局符号表。一个.o文件引用了外部变量,需要重定位,每个段都有重定位表.rel开头,重定位表为一个结构数组,每个结构表示一个重定位入口,表示该入口在所属段的偏移,会去查找全局符号表,找到对应的符号值进行重定位。