从源代码到可执行目标文件要经过以下几个步骤:
main.c-----预处理(cpp)-------->main.i------编译(ccl)----->main.s-----汇编(as)----->main.o-----链接(ld)----->main(可执行文件)
可重定位目标文件是.o文件,包含二进制的代码和数据,其形式可以在编译时和其它的可重定位目标文件合并为可执行目标文件,每个.o文件由对应.c文件生成,每个.o文件代码和数据的地址都是从0开始。各个系统的目标文件格式各不相同,现在x86-64 Liinux和Unix系统使用可执行可链接格式(Executable and Linlable Format,ELF)。
符号是指全局变量或用static修饰的变量(注意局部变量不是符号),在链接时链接器会做两件事情:符号解析和重定位。符号解析时编译器将程序中定义的符号放在符号表中(symbol table),链接器将每个符号引用和都与一个确定的符号定义建立关联。重定位是将多个代码段和数据段合并为一个单独的代码段和数据段,计算每个符号在虚拟空间中的绝对地址,将可执行文件中符号引用处的地址修改为重定位后的地址信息。
ELF中各节存储的信息如下:
ELF头:包括十六字节标识信息、文件类型(.o,exec,.so)、机器类型、节头表的偏移、节头表的表项大小以及表项个数。
.text:已编译程序的机器代码。
.rodate:只读数据,比如printf语句中的格式串和switch的跳转表。
.data:已经初始化的全局变量和静态C变量。
.bss:未初始化的全局变量和静态C变量,以及所有被初始化为0的全局或静态变量,仅是占位符,不占据任何实际磁盘空间。区分初始化和非初始化是为了空间效率。
.symtab:一个符号表,它存放在程序中定义和引用的函数和全局变量静态变量的信息,不包括局部变量。
.rel.text:一个.text节中位置的列表,当链接器把这个目标文件和其他文件组合时,需修改这些位置。
.rel.data:被模块引用或定义的所有全局变量的重定位信息。
.debug:一个调试符号(gcc -g)。
.line:原始C源程序中的行号和.text节中机器指令之间的映射。
.strtab:一个字符串表包含symtab和debug节中符号及节名。
Section header table(节头表):每个节的节名、偏移和大小。
下面着重解析main.o和sum.o可重定位目标文件的ELF格式(main函数里调用sum函数):
main.c源代码
/* main.c */
/* $begin main */
int sum(int *a, int n);
int array[2] = {1, 2};
int main()
{
int val = sum(array, 2);
return val;
}
/* $end main */
sum.c源代码
/* sum.c */
/* $begin sum */
int sum(int *a, int n)
{
int i, s = 0;
for (i = 0; i < n; i++) {
s += a[i];
}
return s;
}
/* $end sum */
首先将main.c和sum.c文件转为main.o和sum.o文件在Linux中用下列命令:
$gcc -c main mian.c
$gcc -c sum sum.c
于是在源文件的目录下就能看到main.o和sum.o文件,接下来在命令行中输入readelf -h 文件名就能看到ELF的头部信息啦!
此处插入一个小知识:readelf一般用于查看ELF格式的文件信息,想要看某个命令的参数只要man一下即可,下面就是我用man查看的readelf的参数呀!
NAME
readelf - Displays information about ELF files.
SYNOPSIS
readelf [-a|--all]
[-h|--file-header] //头部信息
[-l|--program-headers|--segments]
[-S|--section-headers|--sections] //节头信息
[-g|--section-groups]
[-t|--section-details]
[-e|--headers]
[-s|--syms|--symbols] //符号信息
[--dyn-syms]
[-n|--notes]
[-r|--relocs]
[-u|--unwind]
[-d|--dynamic]
[-V|--version-info]
[-A|--arch-specific]
[-D|--use-dynamic]
[-x <number or name>|--hex-dump=<number or name>]
[-p <number or name>|--string-dump=<number or name>]
[-R <number or name>|--relocated-dump=<number or name>]
[-z|--decompress]
[-c|--archive-index]
上面注释的都是要重点讲解的,有兴趣的小伙伴可以去看看其它的命令的作用呦(一定要有好奇心!)
/*sum.c 的ELF头部信息 在Linux中输入命令readelf -h sum.o*/
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 //魔数:文件开头的几个字节通常用来确定文件的类型和格式
类别: ELF64
数据: 2 补码,小端序 (little endian) //数据以小端模式存储
版本: 1 (current)
OS/ABI: UNIX - System V //操作系统平台
ABI 版本: 0
类型: REL (可重定位文件) //目标文件的类型
系统架构: Advanced Micro Devices X86-64 //机器结构类型
版本: 0x1
入口点地址: 0x0 //程序执行的入口地址
程序头起点: 0 (bytes into file) //程序头表的起始位置
Start of section headers: 576 (bytes into file) //节头表的起始位置
标志: 0x0 //程序执行时的第一条指令
本头的大小: 64 (字节) //程序头表的长度
程序头大小: 0 (字节)
Number of program headers: 0
节头大小: 64 (字节) //节头表的长度
节头数量: 11
字符串表索引节头: 10
ps:加载文件时可用魔数确认文件类型是否正确。
/*sum.c 的节头信息 Linux中输入的命令是readelf-S sum.c*/
There are 11 section headers, starting at offset 0x240:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000045 0000000000000000 AX 0 0 1
[ 2] .data PROGBITS 0000000000000000 00000085
0000000000000000 0000000000000000 WA 0 0 1
[ 3] .bss NOBITS 0000000000000000 00000085
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .comment PROGBITS 0000000000000000 00000085
000000000000002b 0000000000000001 MS 0 0 1
[ 5] .note.GNU-stack PROGBITS 0000000000000000 000000b0
0000000000000000 0000000000000000 0 0 1
[ 6] .eh_frame PROGBITS 0000000000000000 000000b0
0000000000000038 0000000000000000 A 0 0 8
[ 7] .rela.eh_frame RELA 0000000000000000 000001d0
0000000000000018 0000000000000018 I 8 6 8
[ 8] .symtab SYMTAB 0000000000000000 000000e8
00000000000000d8 0000000000000018 9 8 8
[ 9] .strtab STRTAB 0000000000000000 000001c0
000000000000000b 0000000000000000 0 0 1
[10] .shstrtab STRTAB 0000000000000000 000001e8
0000000000000054 0000000000000000 0 0 1
节头表描述每个节的节名,在文件中的偏移量、大小、访问属性、对齐方式等。
/*main.o的符号表信息 在Linux命令中输入readelf -s main.o*/
Symbol table '.symtab' contains 12 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 6
6: 0000000000000000 0 SECTION LOCAL DEFAULT 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 5
8: 0000000000000000 8 OBJECT GLOBAL DEFAULT 3 array
9: 0000000000000000 33 FUNC GLOBAL DEFAULT 1 main
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND sum
8:array是main.o中.data偏移量为0的符号,是全局变量数组中有两个元素占8B。
9:main是main.o中.text偏移量为0的符号,是全局函数占33B.
11:sum是未定义的符号,是在其它模块定义的全局符号。
最后,贴图为证: