1.2 目标文件的格式

通过分析目标文件格式初步了解下ELF文件格式。

一、 可执行文件的格式

PC平台 可执行文件的格式(Executable) 主要以下两种:

  1. Windows下的 PE(Portable Executalbe) 格式(或称PE-COFF)
  2. Linux下的 ELF(Executable Linkable Format) 格式 目标文件和可执行文件结构很相似,只是文件里有些符号表和地址还没有调整。

注意一点, 动态连接库(DLL, Dynamic Linking Library) (Windows的.dll和Linux的.so)和 静态链接库(Static Linking Library) (Windows的.lib和Linux的.a)文件也按照可执行文件的格式存储。

二、 ELF文件标准:

ELF文件类型 说明 实例
核心转储文件(Core Dump File) 当进程意外终止时,系统可以将地址空间的内容及终止时的一些信息转储到核心转储文件 Linux下的core dump
可重定位文件(Relocatable File) 可被链接成可执行文件或共享目标文件,静态链接可归为这类 Linux的.o, Windows的.obj
可执行文件(Executable File) 直执行的程序 liunx下的程序,Windows下的.exe
共享目标文件(Shared Object File) 一、链接器可以使用这种文件跟其他可重定位文件和共享目标文件链接,产生新的目标文件;二、动态链接器将几种共享目标文件与可执行文件结合,作为进程映像的一部分来运行 Linux的.so, Windows的DLL

三、 目标文件格式

Linux下目标文件是符合ELF标准的可重定位文件。目标文件包含以下结构:

  1. 文件头 File Header: 描述了整个文件的属性:可执行、静态链接还是动态链接及地址入口、目标硬件、目标操作系统等信息;还包括一个段表,里面记录文件中各个段的信息。
  2. 代码段 .code/.text: 放置源程序
  3. 数据段
    • .data: 放置全局变量和局部静态变量
    • .bss: 未初始化的全局变量和局部变量,因为默认都是0,所以没必要把他们写在文件里,只要记录在内存中预留的空间就可以了。所以他们在文件中不占空间。
  4. 其它段:比如.rodata段,存放字符串常量、全局const变量

数据和代码分离的好处:

  1. 保护段代码不被改写
  2. 利用局部性原理提高缓存命中率
  3. 共享代码段,提高内存利用率

四、 具体分析

分析一段源程序:

int printf( const char* format, ...);

int global_init_var = 10;
int global_unint_var;

void fun1(int i)
{
    printf("%d\
", i);
}

int main(int argc, char *argv[])
{
    static int static_var = 85;
    static int static_var2;

    int a = 11;
    int b;

    fun1(static_var + static_var2 + a + b);

    return a;
}

首先把这段程序编译成目标文件:

  • gcc -c simpleSection.c

然后使用objdump命令查看目标文件结构和内容:

$ objdump -h simpleSection.o

simpleSection.o:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         0000004f  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000008  00000000  00000000  00000084  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000004  00000000  00000000  0000008c  2**2
                  ALLOC
  3 .rodata       00000004  00000000  00000000  0000008c  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .comment      00000012  00000000  00000000  00000090  2**0
                  CONTENTS, READONLY
  5 .note.GNU-stack 00000000  00000000  00000000  000000ad  2**0
                  CONTENTS, READONLY

通过以下命令可查看目标文件的十六进制形式(-s)和反汇编(-d):

$ objdump -s -d simpleSection.o

simpleSection.o:     file format elf32-i386

Contents of section .text:
 0000 5589e583 ec188b45 08894424 04c70424  U......E..D$...$
 0010 00000000 e8fcffff ffc9c355 89e583e4  ...........U....
 0020 f083ec20 c7442418 0b000000 8b150400  ... .D$.........
 0030 0000a100 0000008d 04020344 24180344  ...........D$..D
 0040 241c8904 24e8fcff ffff8b44 2418c9c3  $...$......D$...
Contents of section .data:
 0000 0a000000 55000000                    ....U...
Contents of section .rodata:
 0000 25640a00                             %d..
Contents of section .comment:
 0000 00474343 3a202844 65626961 6e20342e  .GCC: (Debian 4.
 0010 342e352d 38292034 2e342e35 00        4.5-8) 4.4.5.

Disassembly of section .text:

00000000 :
   0:	55                   	push   %ebp
   1:	89 e5                	mov    %esp,%ebp
   3:	83 ec 18             	sub    $0x18,%esp
   6:	8b 45 08             	mov    0x8(%ebp),%eax
   9:	89 44 24 04          	mov    %eax,0x4(%esp)
   d:	c7 04 24 00 00 00 00 	movl   $0x0,(%esp)
  14:	e8 fc ff ff ff       	call   15 
  19:	c9                   	leave
  1a:	c3                   	ret

0000001b 
: 1b: 55 push %ebp 1c: 89 e5 mov %esp,%ebp 1e: 83 e4 f0 and $0xfffffff0,%esp 21: 83 ec 20 sub $0x20,%esp 24: c7 44 24 18 0b 00 00 movl $0xb,0x18(%esp) 2b: 00 2c: 8b 15 04 00 00 00 mov 0x4,%edx 32: a1 00 00 00 00 mov 0x0,%eax 37: 8d 04 02 lea (%edx,%eax,1),%eax 3a: 03 44 24 18 add 0x18(%esp),%eax 3e: 03 44 24 1c add 0x1c(%esp),%eax 42: 89 04 24 mov %eax,(%esp) 45: e8 fc ff ff ff call 46 4a: 8b 44 24 18 mov 0x18(%esp),%eax 4e: c9 leave 4f: c3 ret

1. 代码段

前一段代码中.text就是代码段十六进制形式的内容,后面就是反汇编的内容。总供4f字节。

2. 数据段和只读数据段

.data保存了已经初始化的全局变量和局部静态变量。 在本段程序中分别是global_init_var和static_var,分别是4个字节共8个字节。

.rodata段存放只读数据,一般是程序里的只读变量(如const)和字符串常量。 在本段程序中是prinf函数的“%d\ ”字符串,一共4个ASCII字符(包括\\0),所以共4个字节。

注:MSVC编译器不会把字符串常量放在.rodata段,而是直接放在.data段。

3. BSS段

.bss段存放的是未初始化的全局变量和局部静态变量。 在本段程序中应该是global_uninit_var和static_var2共8个字节。

但是gcc把局部静态变量static_var2放在了.bss段,而全局变量global_uninit_var却没有放在任何段,只是一个未定义的"COMMON 符号"。 等到目标文件链接时,再把全局未初始化变量存放在.bss段。

注:有些编译器为了节省磁盘空间,把初始化为0的全局变量和局部静态变量也放在bss段。编译器还有一些其它优化方法。

4. 其它段

段名 说明
.init .fini 程序初始化与终结代码段
.comment 存放编译器版本信息
.debug 调试信息
.dynamic 动态链接信息
.hash 符号哈希表
.line 调试时的行号信息
.note 额外的调试信息,比如程序公司名,版本号
.strtab String Table. 字符串表, 用于存储ELF文件中用到的各种字符串
.symtab Symbol Table. 符号表
.shstrtab Section String Table. 段名表
.plt .got 动态链接的跳转表和全局入口表

注:

  1. 用户可以自己在ELF文件中自定义段,可以存放如图片和音乐等东西。可以使用objcopy工具制作,使用objdump查看。
  2. 有些情况希望把变量或部分代码放到指定段中实现特定功能,GCC提供一个扩展机制,就是在前面加上"__attribute__((section("name")))"属性,如:" __attribute__((section("FOO"))) void foo() {} "

五、 命令总结

file hello.o
objdump -h hello.o
readelf
size hello.o

发表评论