Elf第二讲,ELF程序头

一丶简介

1.1 简介

ELF 程序头是对二进制文件中段的描述,是程序装载必须的一部分。段(segment) 是在内核装载时被解析的。主要作用就是描述磁盘上可执行文件的内存布局以及如何映射到内存中。可以通过引用原始的ELF头中名为: **e_phoff**(程序头表的偏移量)的偏移量来得到程序头表。

二丶程序头

2.1 程序头结构

2.1.1 结构体

在32位下,ELF程序头结构为:

typedef struct {
    Elf32_Word    p_type;        /* segment type */
    Elf32_Off    p_offset;    /* segment offset */
    Elf32_Addr    p_vaddr;    /* virtual address of segment */
    Elf32_Addr    p_paddr;    /* physical address - ignored? */
    Elf32_Word    p_filesz;    /* number of bytes in file for seg. */
    Elf32_Word    p_memsz;    /* number of bytes in mem. for seg. */
    Elf32_Word    p_flags;    /* flags */
    Elf32_Word    p_align;    /* memory alignment */
} Elf32_Phdr;

64位如下:

typedef struct {
    Elf64_Half    p_type;        /* entry type */
    Elf64_Half    p_flags;    /* flags */
    Elf64_Off    p_offset;    /* offset */
    Elf64_Addr    p_vaddr;    /* virtual address */
    Elf64_Addr    p_paddr;    /* physical address */
    Elf64_Xword    p_filesz;    /* file size */
    Elf64_Xword    p_memsz;    /* memory size */
    Elf64_Xword    p_align;    /* memory & file alignment */
} Elf64_Phdr;

其实核心字段就是 p_type p_offset p_vaddr 以及p_flags

其余的不会影响到我们。 这个结构主要描述了elf在被加载的时候内存要如何映射。

2.1.2 字段含义以及取值

字段 含义
p_type 描述了段的而类型
p_offset 描述了从文件到该段的文件偏移
p_vaddr 描述了段在内存中的偏移
p_paddr 描述了物理地址相关,在应用层无作用。
p_filesz p_offset描述了段在文件中的偏移。那么此成员就描述了在文件中所占的大小,可以为0
p_memsz 同上,描述了内存中映像所占的字节数。 可以为0
p_flags 此成员描述了段的标志
p_align 描述了对齐。对于可加载的段 p_vaddr和p_offset取值必须是合适的。此成员给出了段在文件中和内存中如何对齐。数值 0 1 标识不需要对齐。否则就必须是2的倍数。 p_vaddr和p_offset在取模后应该相等。

分别说一下取值结构:

  • p_type取值

    typedef enum <Elf32_Word> {
        PT_NULL                     = 0x0,
        PT_LOAD                     = 0x1,         //表示段是可以被加载到内存中执行的
        PT_DYNAMIC                  = 0x2,
        PT_INERP                    = 0x3,
        PT_NOTE                     = 0x4,
        PT_SHLIB                    = 0x5,
        PT_PHDR                     = 0x6,
        PT_TLS                      = 0x7,
        PT_NUM                      = 0x8,
        PT_LOOS                     = 0x60000000,
        PT_GNU_EH_FRAME             = 0x6474e550,
        PT_GNU_STACK                = 0x6474e551,
        PT_GNU_RELRO                = 0x6474e552,
        PT_LOSUNW                   = 0x6ffffffa,
        PT_SUNWBSS                  = 0x6ffffffa,
        PT_SUNWSTACK                = 0x6ffffffb,
        PT_HISUNW                   = 0x6fffffff,
        PT_HIOS                     = 0x6fffffff,
        PT_LOPROC                   = 0x70000000,
        PT_HIPROC                   = 0x7fffffff,
        // ARM Sections
        PT_SHT_ARM_EXIDX            = 0x70000001,
        PT_SHT_ARM_PREEMPTMAP       = 0x70000002,
        PT_SHT_ARM_ATTRIBUTES       = 0x70000003,
        PT_SHT_ARM_DEBUGOVERLAY     = 0x70000004,
        PT_SHT_ARM_OVERLAYSECTION   = 0x70000005
    } p_type32_e;
    typedef p_type32_e p_type64_e;
    

p_align取值:

typedef enum <Elf32_Word> {
    PF_None             = 0x0,    表示没有属性
    PF_Exec             = 0x1,    段可执行
    PF_Write            = 0x2,    可写
    PF_Write_Exec       = 0x3,    可写可执行
    PF_Read             = 0x4,    只读
    PF_Read_Exec        = 0x5,    读和执行
    PF_Read_Write       = 0x6,    读写
    PF_Read_Write_Exec  = 0x7     读写执行
} p_flags32_e;

2.1.3 PT_LOAD段

p_type描述了段的类型。 一个可执行文件至少要有一个PT_LOAD类型的段。 这类程序头描述的是可装载的段,

也就是说,这种类型的段会被装载或者映射到内存中。

一般来说,一个动态链接的ELF可执行文件通常包含两个可装载的段。 段类型都为PT_LOAD

  • 1.一个是存放程序代码的text段
  • 2.另一个是存放全局变量和动态链接信息的data段。

上面两个段则会根据p_align的对齐值在内存中对齐。并且映射到内存中。

linux 例子实战:

Elf第二讲,ELF程序头_字段

可以看到只要是LOAD的段都会按照对齐方式加载到内存中

提示:

​ 一般来说,TEXT段也称为代码段,权限一般都是可读可执行的。 对应取值就是PF_READ_EXEC

data段一般就是读写权限。 可以修改p_flags来让我们的程序权限增大。

2.1.4 PT_PHDR段

此段一般位于elf文件的第一个段。PT_PHDR段保存了程序头表本身的位置和大小。 phdr表保存了所有的phdr对文件(以及内存镜像)中段的描述信息。

可以使用

readelf -l <filename> 来查看phdr表的描述信息

如下:

Elf 文件类型为 DYN (共享目标文件)
Entry point 0x1070
There are 12 program headers, starting at offset 52

程序头:
Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
PHDR           0x000034 0x00000034 0x00000034 0x00180 0x00180 R   0x4
INTERP         0x0001b4 0x000001b4 0x000001b4 0x00013 0x00013 R   0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD           0x000000 0x00000000 0x00000000 0x00430 0x00430 R   0x1000
LOAD           0x001000 0x00001000 0x00001000 0x00284 0x00284 R E 0x1000
LOAD           0x002000 0x00002000 0x00002000 0x00144 0x00144 R   0x1000
LOAD           0x002ef4 0x00003ef4 0x00003ef4 0x0012c 0x00130 RW  0x1000
DYNAMIC        0x002efc 0x00003efc 0x00003efc 0x000f0 0x000f0 RW  0x4
NOTE           0x0001c8 0x000001c8 0x000001c8 0x00078 0x00078 R   0x4
GNU_PROPERTY   0x0001ec 0x000001ec 0x000001ec 0x00034 0x00034 R   0x4
GNU_EH_FRAME   0x002018 0x00002018 0x00002018 0x0003c 0x0003c R   0x4
GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
GNU_RELRO      0x002ef4 0x00003ef4 0x00003ef4 0x0010c 0x0010c R   0x1

Section to Segment mapping:
段节...
00
01     .interp
02     .interp .note.gnu.build-id .note.gnu.property .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.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.gnu.build-id .note.gnu.property .note.ABI-tag
08     .note.gnu.property
09     .eh_frame_hdr
10
11     .init_array .fini_array .dynamic .got

2.1.4 PT_INTERP 段 link段

PT_INTERP 只将位置和大小信息存放在了一个以null(0)为终止符号的字符串中。 其作用就是指明程序解释器的位置。

如下:

Elf第二讲,ELF程序头_字段_02