ELF文件格式学习

ELF文件格式是Linux系统中用于编译出的可执行文件的标准格式。它由文件头、程序头表、主体内容和节区头表四部分组成。文件头包含了诸如文件类型、目标体系结构、入口地址等关键信息。节区头表详细列出了程序的不同节,如.text(代码)、.data(数据)等。程序头表则描述了在内存中如何装载这些节。通过`readelf`工具,我们可以分析ELF文件的各个组成部分,理解程序在内存中的布局和运行机制。
摘要由CSDN通过智能技术生成

这里介绍一下ELF文件格式,在linux下,我们用gcc编译出来的可执行文件的格式都是按照ELF文件格式摆放的,为了了解可执行文件怎么运行的,以及装载程序对他怎么进行装载的,我们很有必要了解一下他。

我们先看一下这张图,可以发现我们大概一个将一个ELF格式的文件分成四个部分:1.ELF文件的文件头。2.程序头表。3.主体内容。4.节头表。我们一个一个的看,先看文件头。

我们在linux下输入这条指令就可以查看一个具体elf文件文件头的信息。

readelf -h  文件名

这是我在我的虚拟机上随便查到的结果,我们可以获得这些信息。

对应这个结构体里存的数据。

#define EI_NIDENT 16
typedef struct{
    /*ELF的一些标识信息,固定值*/
    unsigned char e_ident[EI_NIDENT];
    /*目标文件类型:1-可重定位文件,2-可执行文件,3-共享目标文件等*/
    Elf32_Half e_type;
    /*文件的目标体系结构类型:3-intel 80386*/
    Elf32_Half e_machine;
    /*目标文件版本:1-当前版本*/
    Elf32_Word e_version;
    /*程序入口的虚拟地址,如果没有入口,可为0*/
    Elf32_Addr e_entry;
    /*程序头表(segment header table)的偏移量,如果没有,可为0*/
    Elf32_Off e_phoff;
    /*节区头表(section header table)的偏移量,没有可为0*/
    Elf32_Off e_shoff;
    /*与文件相关的,特定于处理器的标志*/
    Elf32_Word e_flags;
    /*ELF头部的大小,单位字节*/
    Elf32_Half e_ehsize;
    /*程序头表每个表项的大小,单位字节*/
    Elf32_Half e_phentsize;
    /*程序头表表项的个数*/
    Elf32_Half e_phnum;
    /*节区头表每个表项的大小,单位字节*/
    Elf32_Half e_shentsize;
    /*节区头表表项的数目*/
    Elf32_Half e_shnum;
    /*某些节区中包含固定大小的项目,如符号表。对于这类节区,此成员给出每个表项的长度字节数。*/
    Elf32_Half e_shstrndx;
}Elf32_Ehdr;

参考文献:https://blog.csdn.net/u014587123/article/details/115276998

其中有几个字段我觉得比较重要,1是e_entry,这个是可执行程序的入口地址,也就是main函数在内存中运行的虚拟地址,e_phoff和e_shoff都是偏移地址,是相对于我们这个文件的第一个字节的。

我们先解释两个概念,第一:Section头表,就是节头表,因为汇编程序在写的时候是以节作为划分的,也就是我们这个程序的经过编译与链接之后会被分成很多节,然后记录每一节的信息就是节头表。

readelf -S  文件名

通过上面的指令可以看见每一节的具体信息。

[号] 名称              类型             地址              偏移量
       大小              全体大小          旗标   链接   信息   对齐
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000000318  00000318
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.gnu.propert NOTE             0000000000000338  00000338
       0000000000000020  0000000000000000   A       0     0     8
  [ 3] .note.gnu.build-i NOTE             0000000000000358  00000358
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .note.ABI-tag     NOTE             000000000000037c  0000037c
       0000000000000020  0000000000000000   A       0     0     4
  [ 5] .gnu.hash         GNU_HASH         00000000000003a0  000003a0
       0000000000000024  0000000000000000   A       6     0     8
  [ 6] .dynsym           DYNSYM           00000000000003c8  000003c8
       0000000000000240  0000000000000018   A       7     1     8
  [ 7] .dynstr           STRTAB           0000000000000608  00000608
       00000000000001a1  0000000000000000   A       0     0     1
  [ 8] .gnu.version      VERSYM           00000000000007aa  000007aa
       0000000000000030  0000000000000002   A       6     0     2
  [ 9] .gnu.version_r    VERNEED          00000000000007e0  000007e0
       0000000000000060  0000000000000000   A       7     2     8
  [10] .rela.dyn         RELA             0000000000000840  00000840
       00000000000000c0  0000000000000018   A       6     0     8
  [11] .rela.plt         RELA             0000000000000900  00000900
       00000000000001b0  0000000000000018  AI       6    24     8
  [12] .init             PROGBITS         0000000000001000  00001000
       000000000000001b  0000000000000000  AX       0     0     4
  [13] .plt              PROGBITS         0000000000001020  00001020
       0000000000000130  0000000000000010  AX       0     0     16
  [14] .plt.got          PROGBITS         0000000000001150  00001150
       0000000000000010  0000000000000010  AX       0     0     16
  [15] .plt.sec          PROGBITS         0000000000001160  00001160
       0000000000000120  0000000000000010  AX       0     0     16
  [16] .text             PROGBITS         0000000000001280  00001280
       0000000000000535  0000000000000000  AX       0     0     16
  [17] .fini             PROGBITS         00000000000017b8  000017b8
       000000000000000d  0000000000000000  AX       0     0     4
  [18] .rodata           PROGBITS         0000000000002000  00002000
       0000000000000041  0000000000000000   A       0     0     4
  [19] .eh_frame_hdr     PROGBITS         0000000000002044  00002044
       000000000000006c  0000000000000000   A       0     0     4
  [20] .eh_frame         PROGBITS         00000000000020b0  000020b0
       00000000000001a8  0000000000000000   A       0     0     8
  [21] .init_array       INIT_ARRAY       0000000000003d20  00002d20
       0000000000000008  0000000000000008  WA       0     0     8
  [22] .fini_array       FINI_ARRAY       0000000000003d28  00002d28
       0000000000000008  0000000000000008  WA       0     0     8
  [23] .dynamic          DYNAMIC          0000000000003d30  00002d30
       0000000000000200  0000000000000010  WA       7     0     8
  [24] .got              PROGBITS         0000000000003f30  00002f30
       00000000000000d0  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000004000  00003000
       0000000000000010  0000000000000000  WA       0     0     8
  [26] .bss              NOBITS           0000000000004010  00003010
       0000000000000010  0000000000000000  WA       0     0     8
  [27] .comment          PROGBITS         0000000000000000  00003010
       000000000000002b  0000000000000001  MS       0     0     1
  [28] .symtab           SYMTAB           0000000000000000  00003040
       0000000000000858  0000000000000018          29    47     8
  [29] .strtab           STRTAB           0000000000000000  00003898
       0000000000000408  0000000000000000           0     0     1
  [30] .shstrtab         STRTAB           0000000000000000  00003ca0
       000000000000011a  0000000000000000           0     0     1

注意,我们可以看到我们这个程序一共被分成30节,里面有很多我们很熟悉的,比如名字叫.text的那节,我们的代码就放在里面。表头的地址表示的运行时会被放到内存的每一节开头的虚地址。偏移量表示相对于文件开头第一个字节我们这一节偏移了多少。以下是节头表每一项存的信息。

typedef struct{
    /*节区名称*/
    Elf32_Word sh_name;
    /*节区类型:PROGBITS-程序定义的信息,NOBITS-不占用文件空间(bss),REL-重定位表项*/
    Elf32_Word sh_type;
    /*每一bit位代表一种信息,表示节区内的内容是否可以修改,是否可执行等信息*/
    Elf32_Word sh_flags;
    /*如果节区将出现在进程的内存影响中,此成员给出节区的第一个字节应处的位置*/
    Elf32_Addr sh_addr;    
    /*节区的第一个字节与文件头之间的偏移*/
    Elf32_Off sh_offset;
    /*节区的长度,单位字节,NOBITS虽然这个值非0但不占文件中的空间*/
    Elf32_Word sh_size;
    /*节区头部表索引链接*/
    Elf32_Word sh_link;
    /*节区附加信息*/
    Elf32_Word sh_info;
    /*节区带有地址对齐的约束*/
    Elf32_Word sh_addralign;
    /*某些节区中包含固定大小的项目,如符号表,那么这个成员给出其固定大小*/
    Elf32_Word sh_entsize;
}Elf32_Shdr;

这段代码转自链接:https://blog.csdn.net/u014587123/article/details/115276998

然后是程序头表,我们的代码同时也可以被分段,我们在进程镜像里面看到的就是被分段的程序,其依据就是这个。我们可以用以下指令查ELF的分段情况。

readelf -l 文件名

以下是我随便找的程序的部分结果截图

其他字段都和节头表哪里差不多,我们要注意这个PhysAddr这个字段,他是物理地址没错。但我们早知道我知道我们在用readelf这指令的时候这个可执行文件还只是一个文件,他并没有被运行进入内存形成一个进程,所以这个PhysAddr是gcc建议装载程序将我们这些段放到内存的物理地址,但装载程序会不会放在这就不知道了(大概率不会)。下面是段头表每一项的信息。

typedef struct
{
    /*segment的类型:PT_LOAD= 1 可加载的段*/
    Elf32_Word p_type;
    /*从文件头到该段第一个字节的偏移*/
    Elf32_Off p_offset;
    /*该段第一个字节被放到内存中的虚拟地址*/
    Elf32_Addr p_vaddr;
    /*在linux中这个成员没有任何意义,值与p_vaddr相同*/
    Elf32_Addr p_paddr;
    /*该段在文件映像中所占的字节数*/
    Elf32_Word p_filesz;
    /*该段在内存映像中占用的字节数*/
    Elf32_Word p_memsz;
    /*段标志*/
    Elf32_Word p_flags;
    /*p_vaddr是否对齐*/
    Elf32_Word p_align;
} Elf32_phdr;

这段代码转自链接:https://blog.csdn.net/u014587123/article/details/115276998

所以我们知道,只要一个ELF文件被生成,每一节和段的虚拟地址就是被定好的,所以每个变量的虚拟地址也是被定好的,但是物理地址要运行的时候才能确定。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值