2020-08-20 2020-08-17 2020-08-14

2020-08-20  本文已影响0人  胖渣大橘子

目标文件里有什么?

编译器编译源代码后生成的文件叫目标文件,
那么目标文件存放的到底是什么?
或者我们的源代码在经过编译以后是怎么存储的?
让我们剥开目标文件的层层外壳,去探索它最本质的内容。

目标文件从结构上讲,它是已经编译后的可执行文件格式,只是还没有经过链接的过程,其中可能有些符号或有些地址还没有被调整。其实他本身就是按照可执行文件格式存储的,只是根真正的可执行文件在结构上稍有不同

目标文件就是源代码编译后但未进行链接的那些中间文件

可执行文件格式涵盖了程序的编译、链接、装载和执行的各个方面。了解它的结构并深入剖析它对于认识系统、了解背后的机理大有好处。

目标文件的格式

不光是可执行文件按照可执行文件格式存储。
按照可执行文件格式存储的文件有

这种文件包含了代码数据,可以在以下两种情况下使用。
一种是链接器可以使用这种文件跟其他的可重定位文件共享目标文件,产生新的目标文件。
第二种是动态链接器可以将几个这种共享目标文件可执行文件结合,作为进程映像的一部分来运行。
*核心转储文件 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),段表其实是一个描述文件中各个段的数组。段表描述了文件中各个段在文中的便移位置及段的属性等,从段表里面可以得到各个段的所有信息。

文件头后面就是各个段的内容,比如
代码段保存的就是程序的指令,
数据段保存的就是程序的静态变量等。

一般 C语言编译后执行语句都编译成机器代码,保存在 .text 段;

已初始化的全局变量局部静态变量都保存在 .data 段;

未初始化的全局变量和局部静态变量一般放在一个叫 ".bss" 的段里。我们知道未初始化的全局变量和静态变量默认值都为0, 本来它们也可以放在 .data 段的,但是因为它们都是0,所以为它们在 .data 段分配空间并且存放数据0是没有必要的。程序运行的时候它们的确要占内存空间的,并且可执行文件必须记录所有未初始化的全局变量局部变量的大小总和,记为 .bss 段。所以 .bss 段只是为未初始化的全局变量和局部静态变量预留位置而已,它并没有内容,所以它在文件中也不占据空间。

总体来说,程序源代码被编译以后主要分成两种段: 程序指令和程序数据。代码段属于程序指令,而数据段和.bss段属于程序数据。

为什么要把指令和数据的存放分开?

挖掘 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     

对照上面的反汇编结果,可以明显地看到,
.text 段里所包含的正是 SimpleSection.c 里两个函数 fun1() 和 main() 的指令。
.text 段的第一个字节 "ox55"
就是 "func1()"函数的第一条指令 pushq %rbp 指令,
而最后一个字节 0xc3 正是 main() 函数的最后一条指令 "retq"

数据段和只读数据段

.data 保存的是那些已经****

上一篇 下一篇

猜你喜欢

热点阅读