zoukankan      html  css  js  c++  java
  • ELF文件格式

    转载原文:《ELF文件格式

    英文文档:《ELF-64 Object File Format

    ELF文件格式

    ELF (Executable and Linkable Format)是一种为可执行文件,目标文件,共享链接库和内核转储(core dumps)准备的标准文件格式。 Linux和很多类Unix操作系统都使用这个格式。 让我们来看一下64位ELF文件格式的结构以及内核源码中有关于它的一些定义。

    一个ELF文件由以下三部分组成:

    • ELF头(ELF header) - 描述文件的主要特性:类型,CPU架构,入口地址,现有部分的大小和偏移等等;

    • 程序头表(Program header table) - 列举了所有有效的段(segments)和他们的属性。 程序头表需要加载器将文件中的节加载到虚拟内存段中;

    • 节头表(Section header table) - 包含对节(sections)的描述。

    现在让我们对这些部分有一些更深的了解。

    ELF头(ELF header)

    ELF头(ELF header)位于文件的开始位置。 它的主要目的是定位文件的其他部分。 文件头主要包含以下字段:

    • ELF文件鉴定 - 一个字节数组用来确认文件是否是一个ELF文件,并且提供普通文件特征的信息;
    • 文件类型 - 确定文件类型。 这个字段描述文件是一个重定位文件,或可执行文件,或...;
    • 目标结构;
    • ELF文件格式的版本;
    • 程序入口地址;
    • 程序头表的文件偏移;
    • 节头表的文件偏移;
    • ELF头(ELF header)的大小;
    • 程序头表的表项大小;
    • 其他字段...

    你可以在内核源码种找到表示ELF64 header的结构体 elf64_hdr

    typedef struct elf64_hdr {
        unsigned char    e_ident[EI_NIDENT];
        Elf64_Half e_type;
        Elf64_Half e_machine;
        Elf64_Word e_version;
        Elf64_Addr e_entry;
        Elf64_Off e_phoff;
        Elf64_Off e_shoff;
        Elf64_Word e_flags;
        Elf64_Half e_ehsize;
        Elf64_Half e_phentsize;
        Elf64_Half e_phnum;
        Elf64_Half e_shentsize;
        Elf64_Half e_shnum;
        Elf64_Half e_shstrndx;
    } Elf64_Ehdr;

     这个结构体定义在 elf.h

    节(sections)

    所有的数据都存储在ELF文件的节(sections)中。 我们通过节头表中的索引(index)来确认节(sections)。 节头表表项包含以下字段:

    • 节的名字;
    • 节的类型;
    • 节的属性;
    • 内存地址;
    • 文件中的偏移;
    • 节的大小;
    • 到其他节的链接;
    • 各种各样的信息;
    • 地址对齐;
    • 这个表项的大小,如果有的话;

    而且,在linux内核中结构体 elf64_shdr 如下所示:

    typedef struct elf64_shdr {
        Elf64_Word sh_name;
        Elf64_Word sh_type;
        Elf64_Xword sh_flags;
        Elf64_Addr sh_addr;
        Elf64_Off sh_offset;
        Elf64_Xword sh_size;
        Elf64_Word sh_link;
        Elf64_Word sh_info;
        Elf64_Xword sh_addralign;
        Elf64_Xword sh_entsize;
    } Elf64_Shdr;

    elf.h

    程序头表(Program header table)

    在可执行文件或者共享链接库中所有的节(sections)都被分为多个段(segments)。 程序头是一个结构的数组,每一个结构都表示一个段(segments)。 它的结构就像这样:

    typedef struct elf64_phdr {
        Elf64_Word p_type;
        Elf64_Word p_flags;
        Elf64_Off p_offset;
        Elf64_Addr p_vaddr;
        Elf64_Addr p_paddr;
        Elf64_Xword p_filesz;
        Elf64_Xword p_memsz;
        Elf64_Xword p_align;
    } Elf64_Phdr;

    在内核源码中。

    elf64_phdr 定义在相同的 elf.h 文件中.

    EFL文件也包含其他的字段或结构。 你可以在 Documentation 中查看。 现在我们来查看一下 vmlinux 这个ELF文件。

    vmlinux

    vmlinux 也是一个可重定位的ELF文件。 我们可以使用 readelf 工具来查看它。 首先,让我们看一下它的头部:

    $ readelf -h  vmlinux
    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:               0x1000000
      Start of program headers:          64 (bytes into file)
      Start of section headers:          381608416 (bytes into file)
      Flags:                             0x0
      Size of this header:               64 (bytes)
      Size of program headers:           56 (bytes)
      Number of program headers:         5
      Size of section headers:           64 (bytes)
      Number of section headers:         73
      Section header string table index: 70

    我们可以看出 vmlinux 是一个64位可执行文件。 我们可以从 Documentation/x86/x86_64/mm.txt 读到相关信息:

    ffffffff80000000 - ffffffffa0000000 (=512 MB)  kernel text mapping, from phys 0

    之后我们可以在 vmlinux ELF文件中查看这个地址:

    $ readelf -s vmlinux | grep ffffffff81000000
         1: ffffffff81000000     0 SECTION LOCAL  DEFAULT    1 
     65099: ffffffff81000000     0 NOTYPE  GLOBAL DEFAULT    1 _text
     90766: ffffffff81000000     0 NOTYPE  GLOBAL DEFAULT    1 startup_64

    值得注意的是,startup_64 例程的地址不是 ffffffff80000000, 而是 ffffffff81000000。 现在我们来解释一下。

    我们可以在 arch/x86/kernel/vmlinux.lds.S 看见如下的定义 :

      . = __START_KERNEL;
        ...
        ...
        ..
        /* Text and read-only data */
        .text :  AT(ADDR(.text) - LOAD_OFFSET) {
            _text = .;
            ...
            ...
            ...
        }

    其中,__START_KERNEL 定义如下:

    #define __START_KERNEL        (__START_KERNEL_map + __PHYSICAL_START)

    从这个文档中看出,__START_KERNEL_map 的值是 ffffffff80000000 以及 __PHYSICAL_START 的值是 0x1000000。 这就是 startup_64的地址是 ffffffff81000000的原因了。

    最后我们通过以下命令来得到程序头表的内容:

    readelf -l vmlinux
    
    Elf file type is EXEC (Executable file)
    Entry point 0x1000000
    There are 5 program headers, starting at offset 64
    
    Program Headers:
      Type           Offset             VirtAddr           PhysAddr
                     FileSiz            MemSiz              Flags  Align
      LOAD           0x0000000000200000 0xffffffff81000000 0x0000000001000000
                     0x0000000000cfd000 0x0000000000cfd000  R E    200000
      LOAD           0x0000000001000000 0xffffffff81e00000 0x0000000001e00000
                     0x0000000000100000 0x0000000000100000  RW     200000
      LOAD           0x0000000001200000 0x0000000000000000 0x0000000001f00000
                     0x0000000000014d98 0x0000000000014d98  RW     200000
      LOAD           0x0000000001315000 0xffffffff81f15000 0x0000000001f15000
                     0x000000000011d000 0x0000000000279000  RWE    200000
      NOTE           0x0000000000b17284 0xffffffff81917284 0x0000000001917284
                     0x0000000000000024 0x0000000000000024         4
    
     Section to Segment mapping:
      Segment Sections...
       00     .text .notes __ex_table .rodata __bug_table .pci_fixup .builtin_fw
              .tracedata __ksymtab __ksymtab_gpl __kcrctab __kcrctab_gpl
              __ksymtab_strings __param __modver 
       01     .data .vvar 
       02     .data..percpu 
       03     .init.text .init.data .x86_cpu_dev.init .altinstructions
              .altinstr_replacement .iommu_table .apicdrivers .exit.text
              .smp_locks .data_nosave .bss .brk

    这里我们可以看出五个包含节(sections)列表的段(segments)。 你可以在生成的链接器脚本 - arch/x86/kernel/vmlinux.lds 中找到所有的节(sections)。

    就这样吧。 当然,它不是ELF(Executable and Linkable Format)的完整描述,但是如果你想要知道更多,可以参考这个文档 - 这里

    Elf_Ehdr 字段详解

    typedef struct elf64_hdr {
    /* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
     unsigned char e_ident[EI_NIDENT];
     Elf64_Half e_type;
     Elf64_Half e_machine;
     Elf64_Word e_version;
    /* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
     Elf64_Addr e_entry;
     Elf64_Off e_phoff;
     Elf64_Off e_shoff;
     Elf64_Word e_flags;
    /* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
     Elf64_Half e_ehsize;
     Elf64_Half e_phentsize;
     Elf64_Half e_phnum;
     Elf64_Half e_shentsize;
    /* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
     Elf64_Half e_shnum;
     Elf64_Half e_shstrndx;
    } Elf64_Ehdr;
    • e_ident:          这个成员,是ELF文件的第一个成员,该成员是个数字,根据上面的宏可以看出,这个程序是个16字节的数据.该成员的前4个字节依次是 0x7F,0x45,0x4c,0x46,也 就是"177ELF"。这是ELF文件的标志,任何一个ELF文件这四个字节都完全相同。第四个字节表示ELF格式,1:32位2:64位;第五个字节表示数据编码格式,1:小端模式 2:大端模式;第六个字节表示文件版本,该值目前必须为1;第七个字节表示操作系统标识:;第八个字节表示ABI版本;第九个字节表示e_ident中从哪开始之后未使用。
    • e_type:            这个成员是ELF文件的类型: 1:表示此文件是重定位文件;2:表示可执行文件;3:表示此文件是一个动态连接库。
    • e_machine:     这个成员表示机器版本.具体定义参与elf.h (篇幅问题,太长了)
    • e_version:       这个成员表示ELF文件版本,为 1
    • e_entry:          这个成员表示可执行文件的入口虚拟地址。此字段指出了该文件中第一条可执 行机器指令在进程被正确加载后的内存地址!ELF可执行文件只能被加载到固定位 置.
    • e_phoff:          这个成员表示程序头(Program Headers)在ELF文件中的偏移量。如果程序头 不存在此值为0。
    • e_shoff:           这个成员表示节头(Section Headers:)在ELF文件中的偏移量。如果节头不存 在此值为0。
    • e_flags:           这个成员表示处理器标志.
    • e_ehsize:        这个成员描述了“ELF头”自身占用的字节数。
    • e_phentsize:   该成员表示程序头中的每一个结构占用的字节数。程序头也叫程序头表,可以 被看做一个在文件中连续存储的结构数组,数组中每一项是一个结构,此字段 给出了这个结构占用的字节大小。
    • e_phoff:           指出程序头在ELF文件中的起始偏移。
    • e_phnum:        此字段给出了程序头中保存了多少个结构。如果程序头中有3个结构则程序头 在文件中占用了3×e_phentsize个字节的大小。
    • e_shentsize:   节头中每个结构占用的字节大小。节头与程序头类似也是一个结构数组,关于 这两个结构的定义将分别在讲述程序头和节头的时候给出。
    • e_shnum:        节头中保存了多少个结构。
    • e_shstrndx:     这是一个整数索引值。节头可以看作是一个结构数组,用这个索引值做为此数 组的下标,它在节头中指定的一个结构进一步给出了一个“字符串表”的信息,而这 个字符串表保存着节头中描述的每一个节的名称,包括字符串表自己也是其中的一 个节。
  • 相关阅读:
    Silverlight 数据绑定 (1):怎样实现数据绑定
    DynamicPopulateExtender 控件调 WebService 的500错误
    [翻译]Linq 的 7 个技巧简化程序操作
    [Silverlight] 一个易犯的错误:关于调用 WCF 服务
    Silverlight 数据绑定 (2):Source to Target
    KB kb KB大小写
    C# winform 程序中响应键盘事件
    异常“企图释放并非呼叫方所拥有的多用户终端运行程序”的处理
    php完美截取中文字符函数mb_substr
    php面试题(三)
  • 原文地址:https://www.cnblogs.com/kuliuheng/p/10579512.html
Copyright © 2011-2022 走看看