#(1)典型的ELF可重定位目标文件(由一个ELF头,若干节和一个节头部表组成),如下图所示
摘自百度图片。
#(2)实例源程序
main.c
int sum(int *a, int n);//声明sum函数,入口参数是一个整型指针变量a和一个整型变量n,返回值为整型
int array[2] = {1, 2};//定义了一个含两个元素的整型全局数组array,并完成初始化
int main()
{
int val = sum(array, 2);//调用sum函数实现数组元素求和,利用返回值为局部变量val赋值
return val;//返回所求和
}
sum.c
int sum(int *a, int n)//定义sum函数
{
int i, s = 0;//定义局部变量i和s
for (i = 0; i < n; i++) {
s += a[i];//做n次循环,将数组中每个元素加到s中
}
return s;//返回所求和
}
#(3)使用指令gcc -c main.c sum.c生成可重定位目标文件main.o和sum.o
#(4)使用指令readelf -h main.o查看ELF头
如上图所示ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。其中7f 45 4c 46表示这是ELF文件。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。其中包括ELF头大小(64B),目标文件的类型(REL即可重定位文件),机器类型(x86-64),节头部表的文件偏移(720B),节头部表中条目的大小(64B),节头部表中条目的数量(12)等信息。
#(5)使用指令readelf -S main.o查看节头部表的信息
不同节的位置和大小是由节头部表描述的,其中目标文件中每个节都有一个固定大小的条目。
下面介绍一下ELF头和节头部表之间的各个节
.text:已编译程序的机器代码;
.rodata:只读数据;
.data:已初始化的全局和静态C变量;
.bss:未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量;
.symtab:是一个符号表,它存放程序中定义和引用的函数和全局变量的信息;
.rel.text:关于指令的重定位信息;
.rel.data:关于数据的重定位信息;
.debug:是一个调试符号表,其条目是程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的C源文件;
.line:原始C源程序的行号和.text节中机器指令之间的映射;
.strtab:是一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部表中的节名字。
#(6)使用指令readelf -s main.o查看符号表信息
如上图所示,全局符号main是一个位于.text节(Ndx==1表示.text节)中偏移量为0(即value值)处的33字节函数;
全局符号array是一个位于.data节(Ndx==3表示.data节)中偏移量为0处的8字节目标;外部符号sum是该文件中的一个引用,不存在本文件的任何节中(即Ndx==UND)。其他条目是链接器内部使用的局部符号。
#(7)使用指令objdump -d main.o查看该重定位目标文件反汇编情况
如上图所示,可重定位目标文件地址从0开始。由于还未进行链接,第14行调用sum函数未跳转到任何位置。
#(8)使用指令ld -o prog main.o sum.o完成链接,并生成可执行目标文件prog;再使用指令objdump -d prog查看prog的反汇编情况