#include
int global_init_var = 123;
int global_uninit_var;
void show( int i )
{
printf( "%d\n", i );
}
int main()
{
static int static_var = 456;
static int static_var2;
int a = 1;
int b;
show(a);
return -1;
}
gcc -c hello.c -o hello.o
目标文件跟可执行文件的内容与结构很相似,所以一般跟可执行文件格式一起采用一种格式存储。
在Windows下,我们可以统称它们为PECOFF文件格式。
在Linux下,我们可以将它们统称为ELF文件。
其他不太常见的可执行文件格式还有Intel/Microsoft的OMF(Object Module Format)、Unix a.out格式和MS-DOS .COM格式等。
不光是可执行文件(Windows的.exe和Linux下的ELF可执行文件)按照可执行文件格式存储。动态链接库(DLL,Dynamic Linking Library)(Windows的.dll和Linux的.so)及静态链接库(Static Linking Library)(Windows的.lib和Linux的.a)文件都按照可执行文件格式存储。它们在Windows下都按照PE-COFF格式存储,Linux下按照ELF格式存储。静态链接库稍有不同,它是把很多目标文件捆绑在一起形成一个文件,再加上一些索引,你可以简单地把它理解为一个包含有很多目标文件的文件包。
$file hello.o
一般目标文件将这些信息按不同的属性,以“节”(Section)的形式存储,有时候也叫“段”(Segment).
在一般情况下,它们都表示一个一定长度的区域,基本上不加以区别,唯一的区别是在ELF的链接视图和装载视图的时候,后面会专门提到。
$objdump -h hello.o
$objdump -x hello.o
如上图,目标文件的格式是ELF,从图中可以看到,ELF文件的开头是一个“文件头”,它描述了整个文件的文件属性,包括文件是否可执行、是静态链接还是动态链接及入口地址(如果是可执行文件)、目标硬件、目标操作系统等信息,文件头还包括一个段表(Section Table),段表其实是一个描述文件中各个段的数组。段表描述了文件中各个段在文件中的偏移位置及段的属性等,从段表里面可以得到每个段的所有信息。文件头后面就是各个段的内容,比如代码段保存的就是程序的指令,数据段保存的就是程序的静态变量
段 | 段名 | 解释 |
---|---|---|
.code或.text | 代码段 | 程序源代码编译后的机器指令经常被放在代码段(Code Section)里 |
.data | 数据段 | 全局变量和局部静态变量数据经常放在数据段(Data Section)。让我们 |
.bss | 数据段 | 未初始化的全局变量和局部静态变量一般放在一个叫.“bss”的段里。 |
.rodata | 数据段 | 只读数据段,const的全局变量,#define定义的常量,以及诸如“Hello World”的字符串常量 |
.comment | 注释信息段 | 没太懂 |
.note.GNU-stack | 编译信息 | 编译器的版本信息段 |
.en_frame | 调试信息 | 调试时,栈回溯时用到 |
const修饰的全局变量在常量区;const修饰的局部变量只是为了防止修改,没有放入常量区(代码区.text)。
未初始化的全局变量和局部静态变量默认值都为0,本来它们也可以被放在.data段的,但是因为它们都是0,所以为它们在.data段分配空间并且存放数据0是没有必要的。程序运行的时候它们的确是要占内存空间的,并且可执行文件必须记录所有未初始化的全局量和局部静态变量的大小总和,记为.bss段。所以.bss段只是为未初始化的全局变量和局部静态变量预留位置而已,它并没有内容,所以它在文件中也不占据空间。
数据段,.bss、.rdata、.data
导出数据段,.edata
导入数据段,.idata
段是导入数据,.idata
.edata段包含了应用程序或DLL的导出数据。在这个段出现的时候,它会包含一个到达导出信息的导出目录。
$objdump -x hello.o
$gcc -c hello.c -o hello.o
[dev1@localhost test01]$ gcc -c hello.c -o hello.o
[dev1@localhost test01]$ objdump -s -d hello.o
hello.o: 文件格式 elf64-x86-64
Contents of section .text:
0000 554889e5 4883ec10 897dfc8b 45fc89c6 UH..H....}..E...
0010 bf000000 00b80000 0000e800 000000c9 ................
0020 c3554889 e54883ec 10c745fc 01000000 .UH..H....E.....
0030 8b45fc89 c7e80000 0000b8ff ffffffc9 .E..............
0040 c3 .
Contents of section .data:
0000 7b000000 c8010000 {.......
Contents of section .rodata:
0000 25640a00 %d..
Contents of section .comment:
0000 00474343 3a202847 4e552920 342e382e .GCC: (GNU) 4.8.
0010 35203230 31353036 32332028 52656420 5 20150623 (Red
0020 48617420 342e382e 352d3434 2900 Hat 4.8.5-44).
Contents of section .eh_frame:
0000 14000000 00000000 017a5200 01781001 .........zR..x..
0010 1b0c0708 90010000 1c000000 1c000000 ................
0020 00000000 21000000 00410e10 8602430d ....!....A....C.
0030 065c0c07 08000000 1c000000 3c000000 .\..........<...
0040 00000000 20000000 00410e10 8602430d .... ....A....C.
0050 065b0c07 08000000 .[......
Disassembly of section .text:
0000000000000000 <show>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: 89 7d fc mov %edi,-0x4(%rbp)
b: 8b 45 fc mov -0x4(%rbp),%eax
e: 89 c6 mov %eax,%esi
10: bf 00 00 00 00 mov $0x0,%edi
15: b8 00 00 00 00 mov $0x0,%eax
1a: e8 00 00 00 00 callq 1f <show+0x1f>
1f: c9 leaveq
20: c3 retq
0000000000000021 <main>:
21: 55 push %rbp
22: 48 89 e5 mov %rsp,%rbp
25: 48 83 ec 10 sub $0x10,%rsp
29: c7 45 fc 01 00 00 00 movl $0x1,-0x4(%rbp)
30: 8b 45 fc mov -0x4(%rbp),%eax
33: 89 c7 mov %eax,%edi
35: e8 00 00 00 00 callq 3a <main+0x19>
3a: b8 ff ff ff ff mov $0xffffffff,%eax
3f: c9 leaveq
40: c3 retq
[dev1@localhost test01]$
跟前面我们了解到的“.text”段长度相符合,最左面一列是偏移量,中间4列是十六进制内容,最右面一列是.text段的ASCII码形式。对照下面的反汇编结果,可以很明显地看到,.text段里所包含的正是hello.c里两个函数 func1() 和 main() 的指令
有时候编译器会把字符串常量放到“.data”段,而不会单独放在“.rodata”段。
objdump -x -s -d hello.o
[dev1@localhost test01]$ objdump -x -s -d hello.o
hello.o: 文件格式 elf64-x86-64
hello.o
体系结构:i386:x86-64,标志 0x00000011:
HAS_RELOC, HAS_SYMS
起始地址 0x0000000000000000
节:
Idx Name Size VMA LMA File off Algn
0 .text 00000041 0000000000000000 0000000000000000 00000040 2**0
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000008 0000000000000000 0000000000000000 00000084 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000004 0000000000000000 0000000000000000 0000008c 2**2
ALLOC
3 .rodata 00000004 0000000000000000 0000000000000000 0000008c 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 0000002e 0000000000000000 0000000000000000 00000090 2**0
CONTENTS, READONLY
5 .note.GNU-stack 00000000 0000000000000000 0000000000000000 000000be 2**0
CONTENTS, READONLY
6 .eh_frame 00000058 0000000000000000 0000000000000000 000000c0 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 hello.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .rodata 0000000000000000 .rodata
0000000000000000 l O .bss 0000000000000004 static_var2.2184
0000000000000004 l O .data 0000000000000004 static_var.2183
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 g O .data 0000000000000004 global_init_var
0000000000000004 O *COM* 0000000000000004 global_uninit_var
0000000000000000 g F .text 0000000000000021 show
0000000000000000 *UND* 0000000000000000 printf
0000000000000021 g F .text 0000000000000020 main
Contents of section .text:
0000 554889e5 4883ec10 897dfc8b 45fc89c6 UH..H....}..E...
0010 bf000000 00b80000 0000e800 000000c9 ................
0020 c3554889 e54883ec 10c745fc 01000000 .UH..H....E.....
0030 8b45fc89 c7e80000 0000b8ff ffffffc9 .E..............
0040 c3 .
Contents of section .data:
0000 7b000000 c8010000 {.......
Contents of section .rodata:
0000 25640a00 %d..
Contents of section .comment:
0000 00474343 3a202847 4e552920 342e382e .GCC: (GNU) 4.8.
0010 35203230 31353036 32332028 52656420 5 20150623 (Red
0020 48617420 342e382e 352d3434 2900 Hat 4.8.5-44).
Contents of section .eh_frame:
0000 14000000 00000000 017a5200 01781001 .........zR..x..
0010 1b0c0708 90010000 1c000000 1c000000 ................
0020 00000000 21000000 00410e10 8602430d ....!....A....C.
0030 065c0c07 08000000 1c000000 3c000000 .\..........<...
0040 00000000 20000000 00410e10 8602430d .... ....A....C.
0050 065b0c07 08000000 .[......
Disassembly of section .text:
0000000000000000 <show>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: 89 7d fc mov %edi,-0x4(%rbp)
b: 8b 45 fc mov -0x4(%rbp),%eax
e: 89 c6 mov %eax,%esi
10: bf 00 00 00 00 mov $0x0,%edi
11: R_X86_64_32 .rodata
15: b8 00 00 00 00 mov $0x0,%eax
1a: e8 00 00 00 00 callq 1f <show+0x1f>
1b: R_X86_64_PC32 printf-0x4
1f: c9 leaveq
20: c3 retq
0000000000000021 <main>:
21: 55 push %rbp
22: 48 89 e5 mov %rsp,%rbp
25: 48 83 ec 10 sub $0x10,%rsp
29: c7 45 fc 01 00 00 00 movl $0x1,-0x4(%rbp)
30: 8b 45 fc mov -0x4(%rbp),%eax
33: 89 c7 mov %eax,%edi
35: e8 00 00 00 00 callq 3a <main+0x19>
36: R_X86_64_PC32 show-0x4
3a: b8 ff ff ff ff mov $0xffffffff,%eax
3f: c9 leaveq
40: c3 retq
[dev1@localhost test01]$
下一篇文章,详细的展示下全局变量、局部变量、常量、static、const,分别存储在哪儿?
我们已经通过SimpleSection.o的结构大致了解了ELF文件的轮廓,接着就来看看ELF文件的结构格式。
ELF目标文件格式的最前部是ELF文件头(ELF Header),它包含了描述整个文件的基本属性,比如ELF文件版本、目标机器型号、程序入口地址等。
[dev1@localhost test01]$ readelf -h hello.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: 984 (bytes into file)
标志: 0x0
本头的大小: 64 (字节)
程序头大小: 0 (字节)
Number of program headers: 0
节头大小: 64 (字节)
节头数量: 13
字符串表索引节头: 12
从上面输出的结果可以看到,ELF的文件头中定义了ELF魔数、文件机器字节长度、数据存储方式、版本、运行平台、ABI版本、ELF重定位类型、硬件平台、硬件平台版本、入口地址、程序头入口和长度、段表的位置和长度及段的数量等。
我们知道ELF文件中有很多各种各样的段,这个段表(Section Header Table)就是保存这些段的基本属性的结构。
前文中我们使用了“objudmp -h”来查看ELF文件中包含的段,结果是SimpleSection里面看到了总共有6个段,分别是 “.code”、“.data”、“.bss ”、“.rodata ”、“. comment ”和“. note.GNU-stack ”。实际上的情况却有所不同,“objdump -h”命令只是把ELF文件中关键的段显示了出来,而省略了其他的辅助性的段,比如:符号表、字符串表、段名字符串表、重定位表等。我们可以使用readelf工具来查看ELF文件的段,它显示出来的结果才是真正的段表结构:
注意:这是大写的-S
[dev1@localhost test01]$ readelf -S hello.o
共有 13 个节头,从偏移量 0x3d8 开始:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000041 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 000002f8
0000000000000048 0000000000000018 I 10 1 8
[ 3] .data PROGBITS 0000000000000000 00000084
0000000000000008 0000000000000000 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 0000008c
0000000000000004 0000000000000000 WA 0 0 4
[ 5] .rodata PROGBITS 0000000000000000 0000008c
0000000000000004 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 00000090
000000000000002e 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000be
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 000000c0
0000000000000058 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000340
0000000000000030 0000000000000018 I 10 8 8
[10] .symtab SYMTAB 0000000000000000 00000118
0000000000000180 0000000000000018 11 11 8
[11] .strtab STRTAB 0000000000000000 00000298
000000000000005d 0000000000000000 0 0 1
[12] .shstrtab STRTAB 0000000000000000 00000370
0000000000000061 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
常用段名 | Value |
---|---|
.text | 代码段,存放代码和局部const变量 |
.rela.text | 重定位表,重定位的信息 |
.data | 保存的是那些已经初始化了的全局静态变量和局部静态变量 |
.bbs | 存放的是未初始化的全局变量和局部静态变量 |
.rodata | read only data 。存放的是只读数据,比如字符串常量,全局const变量。 |
.comment | 存放的是编译器的版本信息,比如字符串: |
.note | 额外的编译器信息。比如程序的公司名,发布版本号等。 |
.en_frame | 调用栈信息 |
.rela…en_frame | 重定位的调用栈信息 |
.systab | symbol table 符号表 |
.strtab | string table ,字符串表,用于存储ELE文件中用到的字符串 |
.shstrtab | section string table 段名表 |
链接器在处理目标文件时,须要对目标文件中某些部位进行重定位,即代码段和数据段中那些对绝对地址的引用的位置。这些重定位的信息都记录在ELF文件的重定位表里面,对于每个须要重定位的代码段或数据段,都会有一个相应的重定位表。
“.data”段则没有对绝对地址的引用,它只包含了几个常量,所以hello.o中没有针对“.data”段的重定位表“.rel.data”
常见的段名为“.strtab”或“.shstrtab”。这两个字符串表分别为字符串表(String Table)和段表字符串表(Section Header String Table)。
我们可以使用很多工具来查看ELF文件的符号表,比如readelf、objdump、nm等,比如使用“nm”来查看“hello.o”的符号结果如下
[dev1@localhost test01]$ nm hello.o
0000000000000000 D global_init_var
0000000000000004 C global_uninit_var
0000000000000021 T main
U printf
0000000000000000 T show
0000000000000004 d static_var.2183
0000000000000000 b static_var2.2184
[dev1@localhost test01]$
[dev1@localhost test01]$ readelf -a hello.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: 984 (bytes into file)
标志: 0x0
本头的大小: 64 (字节)
程序头大小: 0 (字节)
Number of program headers: 0
节头大小: 64 (字节)
节头数量: 13
字符串表索引节头: 12
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000041 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 000002f8
0000000000000048 0000000000000018 I 10 1 8
[ 3] .data PROGBITS 0000000000000000 00000084
0000000000000008 0000000000000000 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 0000008c
0000000000000004 0000000000000000 WA 0 0 4
[ 5] .rodata PROGBITS 0000000000000000 0000008c
0000000000000004 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 00000090
000000000000002e 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000be
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 000000c0
0000000000000058 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000340
0000000000000030 0000000000000018 I 10 8 8
[10] .symtab SYMTAB 0000000000000000 00000118
0000000000000180 0000000000000018 11 11 8
[11] .strtab STRTAB 0000000000000000 00000298
000000000000005d 0000000000000000 0 0 1
[12] .shstrtab STRTAB 0000000000000000 00000370
0000000000000061 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
There are no section groups in this file.
本文件中没有程序头。
重定位节 '.rela.text' 位于偏移量 0x2f8 含有 3 个条目:
偏移量 信息 类型 符号值 符号名称 + 加数
000000000011 00050000000a R_X86_64_32 0000000000000000 .rodata + 0
00000000001b 000e00000002 R_X86_64_PC32 0000000000000000 printf - 4
000000000036 000d00000002 R_X86_64_PC32 0000000000000000 show - 4
重定位节 '.rela.eh_frame' 位于偏移量 0x340 含有 2 个条目:
偏移量 信息 类型 符号值 符号名称 + 加数
000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0
000000000040 000200000002 R_X86_64_PC32 0000000000000000 .text + 21
The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported.
Symbol table '.symtab' contains 16 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 4 OBJECT LOCAL DEFAULT 4 static_var2.2184
7: 0000000000000004 4 OBJECT LOCAL DEFAULT 3 static_var.2183
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_init_var
12: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM global_uninit_var
13: 0000000000000000 33 FUNC GLOBAL DEFAULT 1 show
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
15: 0000000000000021 32 FUNC GLOBAL DEFAULT 1 main
No version information found in this file.
[dev1@localhost test01]$
如果我们在GCC编译时加上“-g”参数,编译器就会在产生的目标文件里面加上调试信息,我们通过readelf等工具可以看到,目标文件里多了很多“debug”相关的段:
[dev1@localhost test01]$ gcc -c hello.c -o hello.o -g
[dev1@localhost test01]$ objdump -h hello.o
hello.o: 文件格式 elf64-x86-64
节:
Idx Name Size VMA LMA File off Algn
0 .text 00000041 0000000000000000 0000000000000000 00000040 2**0
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000008 0000000000000000 0000000000000000 00000084 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000004 0000000000000000 0000000000000000 0000008c 2**2
ALLOC
3 .rodata 00000004 0000000000000000 0000000000000000 0000008c 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .debug_info 00000129 0000000000000000 0000000000000000 00000090 2**0
CONTENTS, RELOC, READONLY, DEBUGGING
5 .debug_abbrev 000000a9 0000000000000000 0000000000000000 000001b9 2**0
CONTENTS, READONLY, DEBUGGING
6 .debug_aranges 00000030 0000000000000000 0000000000000000 00000262 2**0
CONTENTS, RELOC, READONLY, DEBUGGING
7 .debug_line 00000041 0000000000000000 0000000000000000 00000292 2**0
CONTENTS, RELOC, READONLY, DEBUGGING
8 .debug_str 00000119 0000000000000000 0000000000000000 000002d3 2**0
CONTENTS, READONLY, DEBUGGING
9 .comment 0000002e 0000000000000000 0000000000000000 000003ec 2**0
CONTENTS, READONLY
10 .note.GNU-stack 00000000 0000000000000000 0000000000000000 0000041a 2**0
CONTENTS, READONLY
11 .eh_frame 00000058 0000000000000000 0000000000000000 00000420 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
参考
1、《程序员的自我修养链接装载与库》