图文并茂,讲透C语言静态链接,ELF文件篇
gcc生成执行文件过程为:
源文件(*.c文件)编译成对象文件(*.o文件);
链接程序ld,把对象文件(*.o文件)链接成可执行程序。
因此要透彻链接的过程, 需要先了解对象文件(*.o文件)是怎样构成的?
下面用个简单的例子来说明:
#include<stdio.h>int global_var=5;extern int other_file_var;int main(){ int a=1; int b=a other_file_var; return 0;}
gcc -c test.c -o test.o 生成test.o;
查看test.o内容(vim test.o):
图1
看到的是一堆乱码,因为查看方式不对, 就像mp3格式的文件需要用音乐播放器才能播放一样,对象文件(*.o)是elf格式的, 需要用objdump, readelf 工具来查看。
从elf格式的官方文档,可以了解到elf格式文件的结构如下图所示:
图2
下面将一个个部分来分析。
1. ELF文件头(Header)分析
readelf -h test.o 查看elf文件头部信息
主要字段的含义已在图中标识;
由Size of this headers可知,头部占用了64字节;
用 hexdump -n 64 test.o 查看头部64字节数据内容(16进制格式);图中红框里的数据就是test.o文件的前64字节,也就是elf头部,对比上面两图,(图1)中的魔数7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 刚好与(图2)中的前16字节(小段编码)对应,后面每个字段的含义也是一一对应的;
头部信息结构可以参考/usr/include/elf.h 里ELf64_Ehdr, 32位的可以参考ELf32_Ehdr结构
typedef struct{ unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ Elf64_Half e_type; /* Object file type */ Elf64_Half e_machine; /* Architecture */ Elf64_Word e_version; /* Object file version */ Elf64_Addr e_entry; /* Entry point virtual address */ Elf64_Off e_phoff; /* Program header table file offset */ Elf64_Off e_shoff; /* Section header table file offset */ Elf64_Word e_flags; /* Processor-specific flags */ Elf64_Half e_ehsize; /* ELF header size in bytes */ Elf64_Half e_phentsize; /* Program header table entry size */ Elf64_Half e_phnum; /* Program header table entry count */ Elf64_Half e_shentsize; /* Section header table entry size */ Elf64_Half e_shnum; /* Section header table entry count */ Elf64_Half e_shstrndx; /* Section header string table index */} Elf64_Ehdr;
由字段Start Of Section Header可知,在test.o文件的656字节处有一个'段的头部表'。
2. ELF段头部表(Section Header)分析
用readelf -S test.o查看'段的头部表',在这个表里保存了文件里所有段的属性信息,如段的名字,段在文件的开始位置, 段的长度等:
图4
由图可知
主要字段的含义已从图中标识;
段表的数据在偏离文件开始的0x290处,跟ELF Header Start Of Section Header字段保持一致;
这个段表里总共有12项,不同的项描述了不同段的属性;
offset列表示需要重定位的符号在该段中的偏移,这里表示偏离text段0xd处;
info列高4个字节是该符号在.symtab中的索引,见下图;
ndx列是该符号在段表中的索引:(1)比如global_var的索引为3,图4中索引3表示data段,
2.1 text段
text段在偏离test.o文件开头0x40字节处,长度为0x20字节,用hexdump -s 0x40 -n 0x20 test.o,查看text段的16进制内容。
图5
然后objdump -d test.o 打印出程序的反汇编代码,
图6
由上面两图可知,text部分的数据恰好是main函数的机器码,也就是text段里保存的是代码段。
2.2 data段
data段在偏离test.o文件开头0x60字节处,长度为0x4字节,用hexdump -s 0x60 -n 0x4 test.o,查看data段的16进制内容,
图7
4个字节刚好是个int的长度,里面保存的数值是5,也就是全局变量global_var的值,验证了已初始化的全局变量保存在data段。
2.3 bss段
详细讲解见:融会贯通C与汇编,以汇编的视角庖丁解牛C语言:变量篇
2.4 .rela.text (text的重定位段)
main函数里的global_var定义在别的文件,后面链接需要根据别的文件来确定它的虚拟内存地址,由于text中有需要重定位的变量,所以就有了.rela.text段。readelf -r test.o
图8
图9
2.5 .symtab 符号表
图9
也就是说global_var这个符号在data段;(2)如果是“UND“则表示该符号定义在别的文件,需要重定位,重定位信息见“2.4 .rela.text”。
图10