elf,out,coff 三种Linux下可执行文件的历史渊源(二)

来源:互联网 发布:范特昔淘宝店铺号 编辑:程序博客网 时间:2024/04/30 01:47

来源:Linux社区 作者:hercaffe


紧接文件头部的是可选头部,COFF 文件格式规范中规定可选头部的长度可以为 0,但在 LINUX 系统下可选头部是必须存在的。下面是 LINUX 下可选头部的数据结构:


typedef struct
{
    char   magic[2];    /* 魔数 */
    char   vstamp[2];    /* 版本号 */
    char   tsize[4];    /* 文本段长度 */
    char   dsize[4];    /* 已初始化数据段长度 */
    char   bsize[4];    /* 未初始化数据段长度 */
    char   entry[4];    /* 程序进入点 */
    char   text_start[4];       /* 文本段基地址 */
    char   data_start[4];       /* 数据段基地址 */
}
COFF_AOUTHDR;

字段 magic 为 0413 时表示 COFF 文件是可执行的,注意到可选头部中显式定义了程序进入点,标准的 COFF 文件没有明确的定义程序进入点的值,通常是从 .text 节开始执行,但这种设计并不好。

前面我们提到,COFF 格式比 a.out 格式多了一个节段表,一个节头条目描述一个节数据的细节,因此 COFF 格式能包含更多的节,或者说可以根据实际需要,增加特定的节,具体表现在 COFF 格式本身的定义以及稍早提及的 COFF 格式扩展。我个人认为,节段表的出现可能是 COFF 格式相对 a.out 格式最大的进步。下面我们将简单描述 COFF 文件中节的数据结构,因为节的意义更多体现在程序的编译和连接上,所以本文不对其做更多的描述。此外,ELF 格式和 COFF格式对节的定义非常相似,在随后的 ELF 格式分析中,我们将省略相关讨论。


struct COFF_scnhdr
{
    char s_name[8];     /* 节名称 */
    char s_paddr[4];    /* 物理地址 */
   char s_vaddr[4];    /* 虚拟地址 */
    char s_size[4];     /* 节长度 */
   char s_scnptr[4];    /* 节数据相对文件的偏移量 */
    char s_relptr[4];    /* 节重定位信息偏移量 */
    char s_lnnoptr[4];    /* 节行信息偏移量 */
    char s_nreloc[2];    /* 节重定位条目数 */
    char s_nlnno[2];    /* 节行信息条目数 */
    char s_flags[4];    /* 段标记 */
};

有一点需要注意:LINUX系统中头文件coff.h中对字段 s_paddr的注释是"physical address",但似乎应该理解为"节被加载到内存中所占用的空间长度"。字段s_flags标记该节的类型,如文本段、数据段、BSS段等。在 COFF的节中也出现了行信息,行信息描述了二进制代码与源代码的行号之间的对映关系,在调试时很有用。

参考资料 19是一份对COFF格式详细描述的中文资料,更详细的内容请参阅参考资料 20。
 
ELF文件格式分析
ELF文件有三种类型:可重定位文件:也就是通常称的目标文件,后缀为.o。共享文件:也就是通常称的库文件,后缀为.so。可执行文件:本文主要讨论的文件格式,总的来说,可执行文件的格式与上述两种文件的格式之间的区别主要在于观察的角度不同:一种称为连接视图(Linking View),一种称为执行视图(Execution View)。

首先看看ELF文件的总体布局:
ELF header(ELF头部)
Program header table(程序头表)
Segment1(段1)
Segment2(段2)
………
Sengmentn(段n)
Setion header table(节头表,可选)

段由若干个节(Section)构成,节头表对每一个节的信息有相关描述。对可执行程序而言,节头表是可选的。参考资料 1中作者谈到把节头表的所有数据全部设置为0,程序也能正确运行!ELF头部是一个关于本文件的路线图(road map),从总体上描述文件的结构。下面是ELF头部的数据结构:


typedef struct
{
    unsigned char e_ident[EI_NIDENT];     /* 魔数和相关信息 */
    Elf32_Half    e_type;                 /* 目标文件类型 */
    Elf32_Half    e_machine;              /* 硬件体系 */
    Elf32_Word    e_version;              /* 目标文件版本 */
    Elf32_Addr    e_entry;                /* 程序进入点 */
    Elf32_Off     e_phoff;                /* 程序头部偏移量 */
    Elf32_Off     e_shoff;                /* 节头部偏移量 */
    Elf32_Word    e_flags;                /* 处理器特定标志 */
    Elf32_Half    e_ehsize;               /* ELF头部长度 */
    Elf32_Half    e_phentsize;            /* 程序头部中一个条目的长度 */
    Elf32_Half    e_phnum;                /* 程序头部条目个数  */
    Elf32_Half    e_shentsize;            /* 节头部中一个条目的长度 */
    Elf32_Half    e_shnum;                /* 节头部条目个数 */
    Elf32_Half    e_shstrndx;             /* 节头部字符表索引 */
} Elf32_Ehdr;

下面我们对ELF头表中一些重要的字段作出相关说明,完整的ELF定义请参阅参考资料 6和参考资料7。

e_ident[0]-e_ident[3]包含了ELF文件的魔数,依次是0x7f、'E'、'L'、'F'。注意,任何一个ELF文件必须包含此魔数。参考资料 3中讨论了利用程序、工具、/Proc文件系统等多种查看ELF魔数的方法。e_ident[4]表示硬件系统的位数,1代表32位,2代表64位。 e_ident[5]表示数据编码方式,1代表小印第安排序(最大有意义的字节占有最低的地址),2代表大印第安排序(最大有意义的字节占有最高的地址)。e_ident[6]指定ELF头部的版本,当前必须为1。e_ident[7]到e_ident[14]是填充符,通常是0。ELF格式规范中定义这几个字节是被忽略的,但实际上是这几个字节完全可以可被利用。如病毒Lin/Glaurung.676/666(参考资料 1)设置e_ident[7]为0x21,表示本文件已被感染;或者存放可执行代码(参考资料 2)。ELF头部中大多数字段都是对子头部数据的描述,其意义相对比较简单。值得注意的是某些病毒可能修改字段e_entry(程序进入点)的值,以指向病毒代码,例如上面提到的病毒Lin/Glaurung.676/666。

一个实际可执行文件的文件头部形式如下:(利用命令readelf)


   ELF Header:
   Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
   Class:                             ELF32
   Data:                              2's complement, little endian
   Version:                           1 (current)
   OS/ABI:                            UNIX - System V
   ABI Version:                       0
   Type:                              EXEC (Executable file)
   Machine:                           Intel 80386
   Version:                           0x1
   Entry point address:               0x80483cc
   Start of program headers:          52 (bytes into file)
   Start of section headers:          14936 (bytes into file)
   Flags:                             0x0
   Size of this header:               52 (bytes)
   Size of program headers:           32 (bytes)
   Number of program headers:         6
   Size of section headers:           40 (bytes)
   Number of section headers:         34
   Section header string table index: 31
  

紧接ELF头部的是程序头表,它是一个结构数组,包含了ELF头表中字���e_phnum定义的条目,结构描述一个段或其他系统准备执行该程序所需要的信息。


typedef struct {
      Elf32_Word  p_type;    /* 段类型 */
      Elf32_Off   p_offset;        /* 段位置相对于文件开始处的偏移量 */
      Elf32_Addr  p_vaddr;      /* 段在内存中的地址 */
      Elf32_Addr  p_paddr;      /* 段的物理地址 */
      Elf32_Word  p_filesz;    /* 段在文件中的长度 */
      Elf32_Word  p_memsz;    /* 段在内存中的长度 */
      Elf32_Word  p_flags;    /* 段的标记 */
      Elf32_Word  p_align;    /* 段在内存中对齐标记 */
  } Elf32_Phdr;

在详细讨论可执行文件程序头表之前,首先查看一个实际文件的输出:


  Program Headers:
Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
PHDR           0x000034 0x08048034 0x08048034 0x000c0 0x000c0 R E 0x4
INTERP         0x0000f4 0x080480f4 0x080480f4 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /lib/ld-linux.so.2]
   LOAD           0x000000 0x08048000 0x08048000 0x00684 0x00684 R E 0x1000
   LOAD           0x000684 0x08049684 0x08049684 0x00118 0x00130 RW  0x1000
   DYNAMIC        0x000690 0x08049690 0x08049690 0x000c8 0x000c8 RW  0x4
   NOTE           0x000108 0x08048108 0x08048108 0x00020 0x00020 R   0x4

  Section to Segment mapping:
  Segment Sections...
   00    
   01     .interp
   02     .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt
.init .plt .text .fini .rodata .eh_frame
   03     .data .dynamic .ctors .dtors .jcr .got .bss
   04     .dynamic
05     .note.ABI-tag

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        080480f4 0000f4 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048108 000108 000020 00   A  0   0  4
  [ 3] .hash             HASH            08048128 000128 000040 04   A  4   0  4
  [ 4] .dynsym           DYNSYM          08048168 000168 0000b0 10   A  5   1  4
  [ 5] .dynstr           STRTAB          08048218 000218 00007b 00   A  0   0  1
  [ 6] .gnu.version      VERSYM          08048294 000294 000016 02   A  4   0  2
  [ 7] .gnu.version_r    VERNEED         080482ac 0002ac 000030 00   A  5   1  4
  [ 8] .rel.dyn          REL             080482dc 0002dc 000008 08   A  4   0  4
  [ 9] .rel.plt          REL             080482e4 0002e4 000040 08   A  4   b  4
  [10] .init             PROGBITS        08048324 000324 000017 00  AX  0   0  4
  [11] .plt              PROGBITS        0804833c 00033c 000090 04  AX  0   0  4
  [12] .text             PROGBITS        080483cc 0003cc 0001f8 00  AX  0   0  4
  [13] .fini             PROGBITS        080485c4 0005c4 00001b 00  AX  0   0  4
  [14] .rodata           PROGBITS        080485e0 0005e0 00009f 00   A  0   0 32
  [15] .eh_frame         PROGBITS        08048680 000680 000004 00   A  0   0  4
  [16] .data             PROGBITS        08049684 000684 00000c 00  WA  0   0  4
  [17] .dynamic          DYNAMIC         08049690 000690 0000c8 08  WA  5   0  4
  [18] .ctors            PROGBITS        08049758 000758 000008 00  WA  0   0  4
  [19] .dtors            PROGBITS        08049760 000760 000008 00  WA  0   0  4
  [20] .jcr              PROGBITS        08049768 000768 000004 00  WA  0   0  4
  [21] .got              PROGBITS        0804976c 00076c 000030 04  WA  0   0  4
  [22] .bss              NOBITS          0804979c 00079c 000018 00  WA  0   0  4
  [23] .comment          PROGBITS        00000000 00079c 000132 00      0   0  1
  [24] .debug_aranges    PROGBITS        00000000 0008d0 000098 00      0   0  8
  [25] .debug_pubnames   PROGBITS        00000000 000968 000040 00      0   0  1
  [26] .debug_info       PROGBITS        00000000 0009a8 001cc6 00      0   0  1
  [27] .debug_abbrev     PROGBITS        00000000 00266e 0002cc 00      0   0  1
  [28] .debug_line       PROGBITS        00000000 00293a 0003dc 00      0   0  1
  [29] .debug_frame      PROGBITS        00000000 002d18 000048 00      0   0  4
  [30] .debug_str        PROGBITS        00000000 002d60 000bcd 01  MS  0   0  1
  [31] .shstrtab         STRTAB          00000000 00392d 00012b 00      0   0  1
  [32] .symtab           SYMTAB          00000000 003fa8 000740 10     33  56  4
  [33] .strtab           STRTAB          00000000 0046e8 000467 00      0   0  1
 

对一个ELF可执行程序而言,一个基本的段是标记p_type为PT_INTERP的段,它表明了运行此程序所需要的程序解释器(/lib/ld- linux.so.2),实际上也就是动态连接器(dynamic linker)。最重要的段是标记p_type为PT_LOAD的段,它表明了为运行程序而需要加载到内存的数据。查看上面实际输入,可以看见有两个可 LOAD段,第一个为只读可执行(FLg为R E),第二个为可读可写(Flg为RW)。段1包含了文本节.text,注意到ELF文件头部中程序进入点的值为0x80483cc,正好是指向节. text在内存中的地址。段二包含了数据节.data,此数据节中数据是可读可写的,相对的只读数据节.rodata包含在段1中。ELF格式可以比 COFF格式包含更多的调试信息,如上面所列出的形式为.debug_xxx的节。在I386平台LINUX系统下,用命令file查看一个ELF可执行程序的可能输出是:a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), not stripped。


按照这个说法来说 a.out和elf两种格式得可执行文件得结构都不一样呀
 


他文章里还有一段话:
不同时期的可执行文件格式深刻的反映了技术进步的过程,技术进步通常是针对解决存在的问题和适应新的环境。早期的UNIX系统使用a.out格式,随着操作系统和硬件系统的进步,a.out格式的局限性越来越明显。新的可执行文件格式COFF在UNIX System VR3中出现,COFF格式相对a.out格式最大变化是多了一个节头表(section head table),能够在包含基础的文本段、数据段、BSS段之外包含更多的段,但是COFF对动态连接和C++程序的支持仍然比较困难。为了解决上述问题, UNIX系统实验室(UNIX SYSTEM Laboratories USL) 开发出ELF文件格式,它被作为应用程序二进制接口(Application binary Interface ABI)的一部分,其目的是替代传统的a.out格式。例如,ELF文件格式中引入初始化段.init和结束段.fini(分别对应构造函数和析构函数)则主要是为了支持C++程序。1994年6月ELF格式出现在LINUX系统上,现在ELF格式作为UNIX/LINUX最主要的可执行文件格式。当然我们完全有理由相信,在将来还会有新的可执行文件格式出现。

所以我就更觉得elf是一种更新得更效率得替代a.out技术吧
至于gcc/g++生成得a.out是不是elf


我按照图片得样子查看了个a.out,完全符合elf得数据结构
www.linuxidc.com@Ubuntu:~/io$ readelf -h a.out
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x4007f0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          6736 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         8
  Size of section headers:           64 (bytes)
  Number of section headers:         37
  Section header string table index: 34

看来a.out就像权哥说得那样只是个名字!!!!
用的就是elf



0 0
原创粉丝点击