本文章旨在解析ELF文件类型中的可重定位文件,ELF类型还有剩余的两个可执行文件和共享目标文件日后更新
文章使用的例程如下,最简单的hello.c
#include <stdio.h>
int main()
{
printf("hello, world\n");
return 0;
}
首先我们先生成可重定位目标文件
gcc -c hello.c
先放一个可重定位目标文件的结构的图
接下来我们一一地来看一遍这些节都是什么
首先看一下ELF的文件大体的信息
选取几个解释一下
- Type: REL (Relocatable file) : 可以看出.o文件的类型为可重定位文件;
- Number of program headers: 0:可以看出可重定位文件的program header table的长度为0。因为在可重定位文件里不需要program header table;
- Entry point address:0x0同上,由于可重定位文件不能直接执行,因此其入口地址为0(默认值);
- Size of this header: 64: ELF文件头大小为64 byte。
- start of section headers:从ELF文件起始地址偏移672个字节处是section header table(节头表)的起始地址,section header table中共有13项,每项的大小为64 byte;
再看看ELF文件各部分,即看看各个section。
section的信息是由section header table描述的,我们可以通过以下命令查看section header table:
off为在目标文件中的偏移,flags代表运行时的访问权限align顾名思义时对齐方法,link和info用于储存和链接相关的节的信息,entsize指节中每个表项的长度,0为不固定
zzz@ubuntu:~/Desktop$ readelf -S hello.o
There are 13 section headers, starting at offset 0x2c8:
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
0000000000000017 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000218
0000000000000030 0000000000000018 I 10 1 8
[ 3] .data PROGBITS 0000000000000000 00000057
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 00000057
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 00000057
000000000000000d 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 00000064
000000000000002c 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 00000090
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 00000090
0000000000000038 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000248
0000000000000018 0000000000000018 I 10 8 8
[10] .symtab SYMTAB 0000000000000000 000000c8
0000000000000120 0000000000000018 11 9 8
[11] .strtab STRTAB 0000000000000000 000001e8
0000000000000029 0000000000000000 0 0 1
[12] .shstrtab STRTAB 0000000000000000 00000260
0000000000000061 0000000000000000 0 0 1
各个section的详细内容如下:
- text段保存了可执行代码经过编译的机器代码
- .data和.bss并没有占据任何空间,原因是代码中并未定义局部变量或者全局变量。实际上.data存放已初始化的全局和静态c变量 .bss存放未初始化的全局和静态c变量和初始化为0的全局和静态c变量;
- .rodata:存放只读数据,在该程序中占据0xd个字节的空间,存放的就是我们的C代码中唯一的一个需要保存到.rodata段的字符串常量”hello, world”。这段还会存放switch的跳转表
- .commont存放GCC版本信息;
- .strtab指的是原C文件中的文件名和函数名等信息;
- .shstrtab指的是section header string table,其中保存了各个section的名字;
- .symtab保存了符号表,其中包括了.strtab里面定义的三个符号;什么是符号详见我的上一篇博客。除了包含.strtab外,符号表中还包含了一些section的符号表条目,这些条目给链接的时候需要和其他可重定位文件或者库的对应的section合并时提供了必要的信息。
我们先解释一下什么是符号表再看结果,举个例子
在我们的hello.o中,其.symtab的内容如下:
ndx指示所在节索引数,即在那个节。其中UND为未定义,ABS为不用重定位。
bind:为本地/全局
type:有很多,节 文件 函数 等
`zzz@ubuntu:~/Desktop$ readelf -s hello.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 hello.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 0 SECTION LOCAL DEFAULT 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 8
8: 0000000000000000 0 SECTION LOCAL DEFAULT 6
9: 0000000000000000 23 FUNC GLOBAL DEFAULT 1 main
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts
文章末尾我们将可执行和可重定位略作对比,帮助我们更好更深地理解