• ELF文件解析(一):Segment和Section


    ELF 是Executable and Linking Format的缩写,即可执行和可链接的格式,是Unix/Linux系统ABI (Application Binary Interface)规范的一部分。

    Unix/Linux下的可执行二进制文件、目标代码文件、共享库文件和core dump文件都属于ELF文件。

    下面的图来自于文档 Executable and Linkable Format (ELF),描述了ELF文件的大致布局。

    左边是ELF的链接视图,可以理解为是目标代码文件的内容布局。右边是ELF的执行视图,可以理解为可执行文件的内容布局。
    注意目标代码文件的内容是由section组成的,而可执行文件的内容是由segment组成的。

    要注意区分段(segment)和节(section)的概念,这两个概念在后面会经常提到。
    我们写汇编程序时,用.text,.bss,.data这些指示,都指的是section,比如.text,告诉汇编器后面的代码放入.text section中。
    目标代码文件中的section和section header table中的条目是一一对应的。section的信息用于链接器对代码重定位。

    而文件载入内存执行时,是以segment组织的,每个segment对应ELF文件中program header table中的一个条目,用来建立可执行文件的进程映像。
    比如我们通常说的,代码段、数据段是segment,目标代码中的section会被链接器组织到可执行文件的各个segment中。
    .text section的内容会组装到代码段中,.data, .bss等节的内容会包含在数据段中。

    在目标文件中,program header不是必须的,我们用gcc生成的目标文件也不包含program header。
    一个好用的解析ELF文件的工具是readelf。对我本机上的一个目标代码文件sleep.o执行readelf -S sleep.o,输出如下:

    There are 12 section headers, starting at offset 0x270:
    
    Section Headers:
      [Nr] Name              Type             Address           Offset
           Size              EntSize          Flags  Link  Info  Align
      [ 0]                   NULL             0000000000000000  00000000
           0000000000000000  0000000000000000           0     0     0
      [ 1] .text             PROGBITS         0000000000000000  00000040
           0000000000000015  0000000000000000  AX       0     0     1
      [ 2] .rela.text        RELA             0000000000000000  000001e0
           0000000000000018  0000000000000018   I       9     1     8
      [ 3] .data             PROGBITS         0000000000000000  00000055
           0000000000000000  0000000000000000  WA       0     0     1
      [ 4] .bss              NOBITS           0000000000000000  00000055
           0000000000000000  0000000000000000  WA       0     0     1
      ... ... ... ...
      [11] .shstrtab         STRTAB           0000000000000000  00000210
           0000000000000059  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)
    

    readelf -S是显示文件中的Section信息,sleep.o中共有12个section, 我们省略了其中一些Section的信息。
    可以看到,除了我们熟悉的.text, .data, .bss,还有其它Section,这等我们以后展开讲Section的时候还会专门讲到。
    看每个Section的Flags我们也可以得到一些信息,比如.text section的Flags是AX,表示要分配内存,并且是可执行的,这一节是代码无疑了。
    .data 和 .bss的Flags的Flags都是WA,表示可写,需分配内存,这都是数据段的特征。

    使用readelf -l可以显示文件的program header信息。我们对sleep.o执行readelf -l sleep.o。会输出There are no program headers in this file.
    program header和文件中的segment一一对应,因为目标代码文件中没有segment,program header也就没有必要了。

    可执行文件的内容组织成segment,因此program header table是必须的。
    section header不是必须的,但没有strip过的二进制文件中都含有此信息。
    对本地可执行文件sleep执行readelf -l sleep,输出如下:

    Elf file type is DYN (Shared object file)
    Entry point 0x1040
    There are 11 program headers, starting at offset 64
    
    Program Headers:
      Type           Offset             VirtAddr           PhysAddr
                     FileSiz            MemSiz              Flags  Align
      PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                     0x0000000000000268 0x0000000000000268  R      0x8
      INTERP         0x00000000000002a8 0x00000000000002a8 0x00000000000002a8
                     0x000000000000001c 0x000000000000001c  R      0x1
          [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
      LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                     0x0000000000000560 0x0000000000000560  R      0x1000
      LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                     0x00000000000001d5 0x00000000000001d5  R E    0x1000
      LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000
                     0x0000000000000110 0x0000000000000110  R      0x1000
      LOAD           0x0000000000002de8 0x0000000000003de8 0x0000000000003de8
                     0x0000000000000248 0x0000000000000250  RW     0x1000
      DYNAMIC        0x0000000000002df8 0x0000000000003df8 0x0000000000003df8
                     0x00000000000001e0 0x00000000000001e0  RW     0x8
      NOTE           0x00000000000002c4 0x00000000000002c4 0x00000000000002c4
                     0x0000000000000044 0x0000000000000044  R      0x4
      GNU_EH_FRAME   0x0000000000002004 0x0000000000002004 0x0000000000002004
                     0x0000000000000034 0x0000000000000034  R      0x4
      GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                     0x0000000000000000 0x0000000000000000  RW     0x10
      GNU_RELRO      0x0000000000002de8 0x0000000000003de8 0x0000000000003de8
                     0x0000000000000218 0x0000000000000218  R      0x1
    
     Section to Segment mapping:
      Segment Sections...
       00
       01     .interp
       02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
       03     .init .plt .text .fini
       04     .rodata .eh_frame_hdr .eh_frame
       05     .init_array .fini_array .dynamic .got .got.plt .data .bss
       06     .dynamic
       07     .note.ABI-tag .note.gnu.build-id
       08     .eh_frame_hdr
       09
       10     .init_array .fini_array .dynamic .got
    

    如输出所示,文件中共有11个segment。只有类型为LOAD的段是运行时真正需要的。
    除了段信息,还输出了每个段包含了哪些section。比如第二个LOAD段标志为R(只读)E(可执行)的,它的编号是03,表示它包含哪些section的那一行内容为:
    03 .init .plt .text .fini
    可以发现.text包含在其中,这一段就是代码段。
    再比如第三个LOAD段,索引是04,标志为R(只读),但没有可执行的属性,它包含的section有.rodata .eh_frame_hdr .eh_frame,其中rodata表示只读的数据,也就是程序中用到的字符串常量等。
    最后一个LOAD段,索引05,标志RW(可读写),它包含的节是.init_array .fini_array .dynamic .got .got.plt .data .bss,可以看到.data和.bss都包含其中,这段是数据段无疑。

    今天先讲到这里,后面的内容这样组织:

    • 首先讲一下Elf文件的header,因为文件一开始几十个字节就是Elf header的数据,这个数据结构包含了很多信息,还能告诉我们program header table, section header table在文件中什么位置。
    • 接下来会讲一下如何解读section header table,以及section的数据如何组织的。
    • 然后会讲program header table,以及segment的数据组织。section是如何组织成段的,这一点我们也要弄请求。
    • 最后我们会讲程序如果被loader加载到内存中,生成进程映像的。
      欢迎继续关注。
  • 相关阅读:
    promise的终止调用方法:
    (五)浅谈测试用例
    (四)一个bug的生命周期
    (三)趣谈软件需求分析
    (二)软件测试分类
    (一) 软件测试实质
    【转载—“光荣之路”公众号】Bug预防体系(上千bug分析后总结的最佳实践)
    <MFC>FILE的操作
    <CAN>测试的原理
    <C++>消息循环
  • 原文地址:https://www.cnblogs.com/jiqingwu/p/elf_format_research_01.html
Copyright © 2020-2023  润新知