ELF文件之ELF程序头表 (Program Header Table)详解

程序头表(Program Header Table)是ELF文件中描述段(segments)如何映射到内存中的关键结构。

程序头表基本概念

程序头表由多个程序头(Program Header)组成,每个程序头描述一个段(segment)的信息。这些段告诉操作系统如何将程序加载到内存中执行。

程序头表的主要属性:

只在可执行文件和共享库中存在(目标文件没有)

e_phoff指定在文件中的偏移量

包含e_phnum个条目

每个条目大小为e_phentsize字节

程序头结构

每个程序头是一个Elf64_Phdr结构(32位是Elf32_Phdr),包含以下字段:

typedef struct {
    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;

以helloworld为例分析

编译一个简单的helloworld程序并分析其程序头表:

// hello.c
#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}

编译:gcc -o hello hello.c

使用readelf -l hello查看程序头表:

Elf file type is DYN (Position-Independent Executable file)
Entry point 0x1060
There are 13 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x00000000000002d8 0x00000000000002d8  R      0x8
  INTERP         0x0000000000000318 0x0000000000000318 0x0000000000000318
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x00000000000005c8 0x00000000000005c8  R      0x1000
  LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                 0x00000000000001f5 0x00000000000001f5  R E    0x1000
  LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000
                 0x0000000000000158 0x0000000000000158  R      0x1000
  LOAD           0x0000000000002db0 0x0000000000003db0 0x0000000000003db0
                 0x0000000000000258 0x0000000000000260  RW     0x1000
  DYNAMIC        0x0000000000002dc0 0x0000000000003dc0 0x0000000000003dc0
                 0x00000000000001f0 0x00000000000001f0  RW     0x8
  NOTE           0x0000000000000338 0x0000000000000338 0x0000000000000338
                 0x0000000000000020 0x0000000000000020  R      0x8
  NOTE           0x0000000000000358 0x0000000000000358 0x0000000000000358
                 0x0000000000000044 0x0000000000000044  R      0x4
  GNU_PROPERTY   0x0000000000000338 0x0000000000000338 0x0000000000000338
                 0x0000000000000020 0x0000000000000020  R      0x8
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x0000000000002db0 0x0000000000003db0 0x0000000000003db0
                 0x0000000000000250 0x0000000000000250  R      0x1
  LOAD           0x0000000000004000 0x0000000000004000 0x0000000000004000
                 0x00000000000003c8 0x00000000000003c8  RW     0x1000

关键段类型解释

PHDR: 程序头表本身的位置和大小

INTERP: 指定程序解释器(动态链接器路径)

LOAD: 可加载段,包含代码或数据

        第一个LOAD: 只读数据(如.rodata,通常映射到较低的虚拟地址)

        第二个LOAD: 可执行代码(.text)

        第三个LOAD: 只读数据(通常映射到更高的虚拟地址)

        第四个LOAD: 可读写数据(.data, .bss)

DYNAMIC: 动态链接信息

NOTE: 附加信息

GNU_STACK: 控制栈属性

GNU_RELRO: 指定重定位后只读的内存区域

段标志含义

R: 可读

W: 可写

E: 可执行

内存映射过程

当加载hello程序时,操作系统:

1.读取程序头表,找到所有LOAD类型的段

2.将这些段按指定的虚拟地址映射到内存

        文件偏移0x1000映射到虚拟地址0x1000(代码段)

        文件偏移0x2000映射到虚拟地址0x2000(只读数据)

        文件偏移0x2db0映射到虚拟地址0x3db0(数据段)

3.初始化.bss段(MemSiz > FileSiz的部分清零)

4.加载动态链接器(由INTERP段指定)

5.将控制权转移到入口点(0x1060)

实际应用中的观察

1.代码段:包含main函数和printf的调用代码

        标志为R E(可读可执行)

通常位于第二个LOAD段

2.字符串常量:"Hello, World!\n"位于只读数据段

        标志为R(只读)

        通常位于第一个或第三个LOAD段

3.数据段:包含全局变量等

        标志为RW(可读写)

        通常位于第四个LOAD段


通过分析程序头表,我们可以了解程序的内存布局和加载方式,这对于理解程序执行、调试和安全分析都非常重要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值