1. 什么是目标文件?
目标文件是经过预处理,编译,汇编后生成的文件。
目标文件经过链接后变为可执行文件。
目标文件与可执行文件格式几乎一模一样,linux 中统称为 ELF 文件。
2. 目标文件是什么样的呢?
|.text段| 执行语句编译生成的机器代码 |
|.data段|已初始化的全局变量和局部静态变量|
| .bss |未初始化的全局变量和局部静态变量 |
3. 目标文件这样分段存放有什么好处呢 ?
第一, 不同的段有不同的权限,比如 .data 段是可读写的, .text段是只读的,可以防止程序被无意修改。
第二,CPU 缓存被设计为数据缓存与指令缓存分离,所以程序的数据与指令分离可提高CPU的命中率,提高效率。
第三,省内存,当同一个程序被运行多次时,代码段都是一样的,只会被装载一次。
4. 题外话:如何通过一段程序确定一台机器是大端还是小端?
也许你会想到下面这段小程序:
int main()
{
union
{
int var1;
char var2;
} aa;
aa.var1 = 0x00000001;
if (aa.var2 == 1)
{
printf("this mechine is small endian");
}
else if (aa.var2 == 0)
{
printf("this mechine is big endian");
}
return 0;
}
这个是OK的,但其实 还有一种方法是你可以随便写个小程序,比如 helloworld。
#include<stdio.h>
int main()
{
printf("hello world\r\n");
return 0;
}
然后编译:gcc -c hello.c -o hello.o 产生目标文件。
readelf hello.o -h:输出信息中会包含机器大小端。
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2’s complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
…
5. 目标文件中的重定位表
使用objdump 指令查看一下编译 程序生成的目标文件:
#include <stdio.h>
int global_int_var = 10;
int global_uninit;
void callfunc()
{
printf("hello world");
}
int main()
{
static int a = 2;
static int b;
callfunc();
return 0;
}
编译生成目标文件:gcc -c test.c -o test.o
读取目标文件各个段信息:readelf -S test.o
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
000000000000002a 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 000002d0
0000000000000048 0000000000000018 I 10 1 8
[ 3] .data PROGBITS 0000000000000000 0000006c
0000000000000008 0000000000000000 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 00000074
0000000000000004 0000000000000000 WA 0 0 4
[ 5] .rodata PROGBITS 0000000000000000 00000074
000000000000000c 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 00000080
000000000000002e 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000ae
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 000000b0
0000000000000058 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000318
0000000000000030 0000000000000018 I 10 8 8
[10] .symtab SYMTAB 0000000000000000 00000108
0000000000000180 0000000000000018 11 11 8
[11] .strtab STRTAB 0000000000000000 00000288
0000000000000048 0000000000000000 0 0 1
[12] .shstrtab STRTAB 0000000000000000 00000348
0000000000000061 0000000000000000 0 0 1
.rela.text: type 是 RELA, 是一个重定位表。所有的重定位信息都存储在重定位表中,而且, rela.text是针对 text段的重定位表,helloworld 中 printf()函数需要重定位。
如果有全局变量,或者从其他模块中 extern 全局变量,会有相应的 .rela.data 段。
链接过程的核心任务是重定位,而重定位的目标就是 存储在 重定位表中的object。
-
如何确定源代码中各个符号处在目标文件的哪一段呢?
使用指令 readelf -s hello.o
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS test.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 5
6: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 b.2183
7: 0000000000000004 4 OBJECT LOCAL DEFAULT 3 a.2182
8: 0000000000000000 0 SECTION LOCAL DEFAULT 7
9: 0000000000000000 0 SECTION LOCAL DEFAULT 8
10: 0000000000000000 0 SECTION LOCAL DEFAULT 6
11: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 global_int_var
12: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM global_uninit
13: 0000000000000000 21 FUNC GLOBAL DEFAULT 1 callfunc
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
15: 0000000000000015 21 FUNC GLOBAL DEFAULT 1 main
解读: -
ABS 这一列标示 后面跟的符号所在的 session。例如: main ABS =1.
在 “readelf -S test.o” show 出的结果看, .text 段的 下标是1. .data段的下标是 3.
所以,main 在 .text段, global_int_var 在 .data 段。 -
printf 因为是外部引用函数,所以 ABS= UND,需要 在链接过程中重定位。
-
global_uninit ABS=COM, 标示 global_uninnit 是弱符号的。未初始化的变量都是弱符号,弱符号允许同一个变量被声明多次,且类型不同,最终选择占用空间大的符号。