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 文件头部信息

图3
- 主要字段的含义已在图中标识;
- 由 Size of this headers 可知,头部占用了64字节;
- 用 hexdump -n 64 test.o 查看头部64字节数据内容(16进制格式);

图4
图中红框里的数据就是test.o文件的前64字节,也就是elf头部,对比上面两图,(图3)中的魔数7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 刚好与(图4)中的前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
由图可知
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
站在C语言的肩膀上学汇编(1):栈
站在C语言的肩膀上学汇编(2):栈
站在C语言的肩膀上学汇编(3):栈
融会贯通C与汇编,以汇编的视角庖丁解牛C语言:变量篇
创造不易,如果帮助到了您,求点赞。谢谢!