目标文件里有什么?
编译器编译源代码后生成的文件叫目标文件,
那么目标文件存放的到底是什么?
或者我们的源代码在经过编译以后是怎么存储的?
让我们剥开目标文件的层层外壳,去探索它最本质的内容。
目标文件从结构上讲,它是已经编译后的可执行文件格式,只是还没有经过链接的过程,其中可能有些符号或有些地址还没有被调整。其实他本身就是按照可执行文件格式存储的,只是根真正的可执行文件在结构上稍有不同
目标文件就是源代码编译后但未进行链接的那些中间文件
可执行文件格式涵盖了程序的编译、链接、装载和执行的各个方面。了解它的结构并深入剖析它对于认识系统、了解背后的机理大有好处。
目标文件的格式
- PE
- ELF
*COFF
不光是可执行文件按照可执行文件格式存储。
按照可执行文件格式存储的文件有
- 动态链接库
- 静态链接库
- 可执行文件
ELF 标准把系统中按照 ELF 格式的文件归为如下 - Linux 的 .o 可重定位文件
- /bin/bash 文件 可执行文件
- Linux .so 共享目标文件
这种文件包含了代码和数据,可以在以下两种情况下使用。
一种是链接器可以使用这种文件跟其他的可重定位文件和共享目标文件,产生新的目标文件。
第二种是动态链接器可以将几个这种共享目标文件与可执行文件结合,作为进程映像的一部分来运行。
*核心转储文件 Linux 下的core dump。
可以在 Linux 下使用 file 命令来查看相应的文件格式。
61964:~ hahaha$ file /Users/hahaha/Desktop/hi/libbluetooth.so
/Users/hahaha/Desktop/hi/libbluetooth.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, BuildID[md5/uuid]=04f69a9e32c4a40bfed145a687344691, stripped
目标文件是什么样的?
我们能猜到,目标文件中的内容至少有编译后的机器指令代码、数据。没错,除了这些内容意外,目标文件中还包括了链接时所需要的一些信息,比如符号表、调试信息、字符串等。一般目标文件将这些信息按照不同的属性,以 "节" (Section) 的形式存储,有时候也叫"段" (Segment)
ELF 文件
- 文件头
描述整个文件的文件属性,包括文件是否可执行、是静态链接还是动态链接及入口地址(如果是可执行文件)、 目标硬件、目标操作系统等信息,
文件头还包括一个段表 (Section Table),段表其实是一个描述文件中各个段的数组。段表描述了文件中各个段在文中的便移位置及段的属性等,从段表里面可以得到各个段的所有信息。
- 各个段的内容
文件头后面就是各个段的内容,比如
代码段保存的就是程序的指令,
数据段保存的就是程序的静态变量等。
- .text 段
一般 C语言的编译后执行语句都编译成机器代码,保存在 .text 段;
- .data 段
已初始化的全局变量和局部静态变量都保存在 .data 段;
- .bss
未初始化的全局变量和局部静态变量一般放在一个叫 ".bss" 的段里。我们知道未初始化的全局变量和静态变量默认值都为0, 本来它们也可以放在 .data 段的,但是因为它们都是0,所以为它们在 .data 段分配空间并且存放数据0是没有必要的。程序运行的时候它们的确要占内存空间的,并且可执行文件必须记录所有未初始化的全局变量和局部变量的大小总和,记为 .bss 段。所以 .bss 段只是为未初始化的全局变量和局部静态变量预留位置而已,它并没有内容,所以它在文件中也不占据空间。
总体来说,程序源代码被编译以后主要分成两种段: 程序指令和程序数据。代码段属于程序指令,而数据段和.bss段属于程序数据。
为什么要把指令和数据的存放分开?
- 一方面
进程 - 另一方面
CPU 缓存 - 第三个原因
其实也是最重要的原因,共享资源节省大量内存
挖掘 SimpleSection.o
代码
/*
* SimpleSection.c
* Linux:
* gcc -c SimpleSection.c
*
* Windows:
* cl SimpleSection.c /c /Za
*/
int printf ( const char* format, ... );
int global_init_var = 84;
int global_uinit_var;
void func1 ( int i )
{
printf ( "%d\n", i);
}
int main(void)
{
static int static_var = 85;
static int static_var2;
int a = 1;
int b;
func1( static_var + static_var2 + a + b );
return a;
}
hahaha:hi hahaha$ gcc -c SimpleSection.c # (参数 -c 表示只编译不链接 )
hahaha:hi hahaha$ ls -l /Users/hahaha/Desktop/hi/SimpleSection.o
-rw-r--r-- 1 hahaha INTERNAL\Domain Users 1252 8 20 14:19 /Users/hahaha/Desktop/hi/SimpleSection.o
我们得到了一个 1252 字节(该文件大小可能会因为编译器版本及机器平台不同而发生变化) 的 SimpleSection.o 目标文件。
用 binutils 的工具 objdump 来查看 object 内部的结构。
hahaha:hi hahahau$ objdump -h SimpleSection.o
SimpleSection.o: file format Mach-O 64-bit x86-64
Sections:
Idx Name Size Address Type
0 __text 00000068 0000000000000000 TEXT
1 __data 00000008 0000000000000068 DATA
2 __cstring 00000004 0000000000000070 DATA
3 __bss 00000004 0000000000000120 BSS
4 __compact_unwind 00000040 0000000000000078 DATA
5 __eh_frame 00000068 00000000000000b8 DATA
有一个专门的命令叫做 "size",它可以用来查看 ELF 文件的代码段、数据段和 BSS段的长度
hahaha:hi hahaha$ size SimpleSection.o
__TEXT __DATA __OBJC others dec hex
212 12 0 64 288 120
代码段
objdump 的
"-s" 参数可以将所有段的内容以十六进制的方式打印出来,
“-d” 参数可以将所有包含指令的段反汇编。
hahaha:hi v_liuxiaoju$ objdump -s -d SimpleSection.o
SimpleSection.o: file format Mach-O 64-bit x86-64
Disassembly of section __TEXT,__text:
_func1:
0: 55 pushq %rbp
1: 48 89 e5 movq %rsp, %rbp
4: 48 83 ec 10 subq $16, %rsp
8: 48 8d 05 61 00 00 00 leaq 97(%rip), %rax
f: 89 7d fc movl %edi, -4(%rbp)
12: 8b 75 fc movl -4(%rbp), %esi
15: 48 89 c7 movq %rax, %rdi
18: b0 00 movb $0, %al
1a: e8 00 00 00 00 callq 0 <_func1+0x1f>
1f: 89 45 f8 movl %eax, -8(%rbp)
22: 48 83 c4 10 addq $16, %rsp
26: 5d popq %rbp
27: c3 retq
28: 0f 1f 84 00 00 00 00 00 nopl (%rax,%rax)
_main:
30: 55 pushq %rbp
31: 48 89 e5 movq %rsp, %rbp
34: 48 83 ec 10 subq $16, %rsp
38: c7 45 fc 00 00 00 00 movl $0, -4(%rbp)
3f: c7 45 f8 01 00 00 00 movl $1, -8(%rbp)
46: 8b 05 00 00 00 00 movl (%rip), %eax
4c: 03 05 00 00 00 00 addl (%rip), %eax
52: 03 45 f8 addl -8(%rbp), %eax
55: 03 45 f4 addl -12(%rbp), %eax
58: 89 c7 movl %eax, %edi
5a: e8 00 00 00 00 callq 0 <_main+0x2f>
5f: 8b 45 f8 movl -8(%rbp), %eax
62: 48 83 c4 10 addq $16, %rsp
66: 5d popq %rbp
67: c3 retq
Contents of section __text:
0000 554889e5 4883ec10 488d0561 00000089 UH..H...H..a....
0010 7dfc8b75 fc4889c7 b000e800 00000089 }..u.H..........
0020 45f84883 c4105dc3 0f1f8400 00000000 E.H...].........
0030 554889e5 4883ec10 c745fc00 000000c7 UH..H....E......
0040 45f80100 00008b05 00000000 03050000 E...............
0050 00000345 f80345f4 89c7e800 0000008b ...E..E.........
0060 45f84883 c4105dc3 E.H...].
Contents of section __data:
0068 54000000 55000000 T...U...
Contents of section __cstring:
0070 25640a00 %d..
Contents of section __bss:
<skipping contents of bss section at [0120, 0124)>
Contents of section __compact_unwind:
0078 00000000 00000000 28000000 00000001 ........(.......
0088 00000000 00000000 00000000 00000000 ................
0098 30000000 00000000 38000000 00000001 0.......8.......
00a8 00000000 00000000 00000000 00000000 ................
Contents of section __eh_frame:
00b8 14000000 00000000 017a5200 01781001 .........zR..x..
00c8 100c0708 90010000 24000000 1c000000 ........$.......
00d8 28ffffff ffffffff 28000000 00000000 (.......(.......
00e8 00410e10 8602430d 06000000 00000000 .A....C.........
00f8 24000000 44000000 30ffffff ffffffff $...D...0.......
0108 38000000 00000000 00410e10 8602430d 8........A....C.
0118 06000000 00000000
- Contents of section_text 就是 .text的数据以十六进制方式打印出来的内容。
- 最左面一位是偏移量
- 中间一位是十六进制内容
- 最右面一列是 .text 段的 ASCII 码形式。
对照上面的反汇编结果,可以明显地看到,
.text 段里所包含的正是 SimpleSection.c 里两个函数 fun1() 和 main() 的指令。
.text 段的第一个字节 "ox55"就是 "func1()"函数的第一条指令 pushq %rbp 指令,
而最后一个字节 0xc3 正是 main() 函数的最后一条指令 "retq"
数据段和只读数据段
.data 保存的是那些已经****