码迷,mamicode.com
首页 > 其他好文 > 详细

ELF文件结构描述

时间:2018-04-16 13:01:02      阅读:159      评论:0      收藏:0      [点我收藏+]

标签:特殊符号   问题   AC   结构   使用   off   接口   location   技术   


ELF目标文件格式最前部ELF文件头(ELF Header),它包含了描述了整个文件的基本属性,比如ELF文件版本、目标机器型号、程序入口地址等。其中ELF文件与段有关的重要结构就是段表(Section Header Table)

文件头

我们可以使用readelf命令来详细查看elf文件,代码如清单3-2所示:
技术分享图片
从上面输出的结构可以看到:ELF文件头定义了ELF魔数、文件机器字节长度、数据存储方式、版本、运行平台等。

ELF文件头结构及相关常数被定义在“/usr/include/elf.h”,因为ELF文件在各种平台下都通用,ELF文件有32位版本和64位版本的ELF文件的文件头内容是一样的,只不过有些成员的大小不一样。它的文件图也有两种版本:分别叫“Elf32_Ehdr”“Elf64_Ehdr”

typedef struct {
    unsigned char e_ident[16];  
    Elf32_Half e_type;
    Elf32_Half e_machine;
    Elf32_Word e_version;
    Elf32_Addr e_entry;
    Elf32_Off e_phoff;
    Elf32_Off e_shoff;
    Elf32_Word e_flags;
    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;

技术分享图片

技术分享图片

段表

段表就是保存ELF文件中各种各样段的基本属性的结构。段表是ELF除了文件以外的最重要结构体,它描述了ELF的各个段的信息,ELF文件的段结构就是由段表决定的。编译器、链接器和装载器都是依靠段表来定位和访问各个段的属性的。段表在ELF文件中的位置由ELF文件头的“e_shoff”成员决定的,比如SimpleSection.o中,段表位于偏移0x118。
技术分享图片

重定位表

我们注意到:SimpleSection.o中有一个叫做".rel.text"的段,它的类型是(sh_type)为“SHT_REL”,也就是说它是一个重定位表(Relocation Table)。正如我们开始所说的,链接器在处理目标文件时,须要对目标文件中某些部位进行重定位,即代码段和数据段中哪些对绝对地址的引用的位置。这些重定位的信息都记录在ELF文件的重定位表里面,对于每个须要重定位代码段或数据段,都会有一个相应的重定位表。

字符串表

ELF文件中用到了许多的字符串,比如段名,变量名等。因为字符串的长度往往是不定的,所以用固定的结构来表示它比较困难。一种常见的做法是把字符串集中起来存放到一个表,然后使用字符串在表中的偏移来引用字符串。
通常用这种方式,在ELF文件中引用字符串只需给一个数字下标即可,不用考虑字符串的长度问题。一般字符串标在ELF文件中国也以段的方式保存,常见的段名为“.strtab”或“.shstrtab”。这两个字符串分别表示为字符串表和段表字符串表。
只有分析ELF文件头,就可以得到段表和段表字符串表的位置,从而解析整个ELF文件。

链接的接口-符号

链接的过程的本质就是要把多个不同目标文件之间相互“粘”到一起。或者说像玩具积木一样,可以拼装成一个整体。为了使不同目标文件之间能够相互黏合,这些目标文件之间必须有固定的规则才行,就像积木模块必须有凹凸部分才能相互黏合。在链接中,目标文件之间相互拼合实际上是目标文件之间对地址的引用,即对函数和变量的地址的引用。
比如目标文件B要用到目标文件中的函数“foo”,那么我们就成目标文件A定义(Define)了函数“foo”,称目标文件B引用(Reference)了目标文件A中的函数“foo”。这两个概念也同样适用于变量。每个函数或变量都有自己独特的名字,才能避免链接过程中不同变量和函数之间的混淆。在链接中,我们将函数和变量统称为符号(Symbol),函数名或变量名就是符号名(Symbol Name)。
我们可以将符号看作是链接中的粘合剂,整个链接过程正是基于符号才能够完成。链接过程中很关键的一部分是符号的管理,每一个目标文件都会有一个相应的符号表(Symbol Table),这个表里记录了目标文件所用到的所有符号。每个定义的符号有一个对应的值,叫做符号值(Symbol Value),对于变量和函数来说,符号值就是它们的地址,除了函数和变量之外,还存在着其他几种不常用的符号。我们将符号表中的所有符号进行分类,它们有可能是下面这些类型中的几种:

  • 定义在本目文件中的全局符号,可以被其他目标文件引用,比如SimpleSection.o里面的“func1”、“main”和“global_init_val”。
  • 在本目标文件中引用的全局符号,却没有定义在本目标文件,这一般叫做外部符号(External Symbol),也就是我们前面所讲的符号引用。比如SimpleSection.o里面的“printf”
  • 段名,这种符号往往是由编译器产生,它的值就是该段的起始地址。
  • 局部符号,这类符号只在编译单元内部可见。
  • 行号信息。
    对于我们来说,最值得关注的是全局符号,即上面的第一类和第二类。

ELF符号表结构

ELF文件中的符号表往往是文件中的一个段,段名一般叫做“.symtab”。符号表的结构很简单,它是一个Elf32_Sym结构(32位ELF文件)的数组,每个Elf32_Sym结构对应一个符号。这个数组的第一个元素,也就是下标0的元素为无效的“未定义”符号。Elf32_Sym的结构对应如下:

typedef struct {
    Elf32_Word st_name;
    Elf32_Addr st_value;
    Elf32_Word st_size;
    unsigned char st_info;
    unsigned char st_other;
    Elf32_Half st_shndx;
} Elf32_Sym;
st_name 符号名。这个成员包含了该符号名在字符串表中的下标
st_value 符号对应的值。这个值跟符号有关,可能是一个绝对值
st_size 符号大小
st_info 符号类型和绑定信息
st_other 该成员目前为0,没用
st_shndx 符号所在段

特殊符号

当我们使用ld作为链接器产生可执行文件,它会为我们定义很多特殊符号。这些符号并没有在你的程序中定义,但是你可以直接声明并引用它,我们称之为特殊符号。其实这些符号是被定义在链接器脚本中的,我们无须定义它们,但可以声明它们并且使用它们。链接器在程序最终连接成可执行文件将其解析成正确的值,注意,只有使用ld链接生成最终可执行文件的时候这些符号才会存在。
几个很具有代表性的特殊符号如下:

  • __executable_start,该符号为程序的起始地址
  • __etext或_etext或etext,该符号为代码段结束地址,即代码段最末尾的地址
  • _edata或edata,该符号为数据段结束地址,即数据段的最末尾地址。
  • _end或end,该符号为程序的结束地址。
  • 以上地址都为程序被装载的虚拟地址。
    我们可以在程序中直接使用这些符号。

符号修饰和函数签名

在早期,编译器编译源代码产生目标文件时,符号名与相应的变量和函数名字一样的。比如在一个汇编源代码中包含了一个函数foo,那么汇编器将它编译成目标文件后,foo在目标文件中所对应的符号名也是foo。后来的UNIX平台和C语言发明时,已经存在了相当多的使用汇编编写的库和目标文件。这样就产生了一个问题,那就是如果一个c程序要使用这些库的话,C语言不可以使用这些库中定义的函数和变量的名字作为符号名,否则将会跟现有的目标文件冲突。比如有个用汇编语言编写的库定义了一个函数叫做main,那么我们在C语言里面就不可以定义一个main函数或变量了。同样的道理,如果一个C语言的目标文件要用到一个使用Fortran语言编写的目标文件,我们也必须防止它们的名称冲突。
为了防止类似的符号名冲突,UNIX的C语言就规定,C语言源代码文件中的所有全局变量和函数经过编译后,相对应的符号名加上“”。而Fortran语言的源代码经过编译以后,所有符号名前加上“”,后面也加上“_”
这种方式虽然能够减少冲突的概率,但还是有可能造成冲突。于是C++开始考虑到这个问题,增加了namespace来解决多模块的符号冲突问题。

ELF文件结构描述

标签:特殊符号   问题   AC   结构   使用   off   接口   location   技术   

原文地址:https://www.cnblogs.com/linhaostudy/p/8855238.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!