美文网首页
程序编译链接(三)-- ELF文件格式

程序编译链接(三)-- ELF文件格式

作者: wayyyy | 来源:发表于2018-10-03 01:20 被阅读0次
ELF文件头

ELF目标文件格式的最前部是ELF文件头,它包好了描述整个文件的基本属性,比如ELF文件版本,目标机器型号,程序的入口地址。紧接着是ELF文件的各个段。

readelf -h test.o

readelf 是一个工具。


elf.png

ELF文件头中定义了ELF魔数文件机器字节长度数据存储方式版本运行平台ABI版本ELF重定位类型硬件平台硬件平台版本入口地址程序头入口和长度段表的位置和长度段的数量

ELF文件头结构及相关常数被定义在/usr/include/elf.h里,ELF文件有32位版本和64位版本,文件头也有这两种结构,分别叫Elf32_EhdrElf64_Ehdr

typedef uint16_t Elf32_Half;     /* 2字节 */
typedef uint32_t Elf32_Word;     /* 4字节 */
typedef uint32_t Elf32_Off;      /* 4字节 */
typedef uint32_t Elf32_Addr;     /* 4字节 */

struct Elf32_Ehdr
{
    unsigned char e_ident[16];    /* Magic number and other info */
    Elf32_Half    e_type;         /* Object file type */
    Elf32_Half    e_machine;      /* Architecture */
    Elf32_Word    e_version;      /* Object file version */
    Elf32_Addr    e_entry;        /* Entry point virtual address */
    Elf32_Off     e_phoff;        /* Program header table file offset */
    Elf32_Off     e_shoff;        /* Section header table file offset */
    Elf32_Word    e_flags;        /* Processor-specific flags */
    Elf32_Half    e_ehsize;       /* ELF header size in bytes */
    Elf32_Half    e_phentsize;    /* Program header table entry size */
    Elf32_Half    e_phnum;        /* Program header table entry count */
    Elf32_Half    e_shentsize;    /* Section header table entry size */
    Elf32_Half    e_shnum;        /* Section header table entry count */
    Elf32_Half    e_shstrndx;     /* Section header string table index */
} ;
  • unsigned char e_ident[16]


    e_ident.png
  • e_type

    ELF 目标文件类型 取值 意义
    ET_NONE 0 未知目标文件格式
    ET_REL 1 可重定位文件
    ET_EXEC 2 可执行文件
    ET_DYN 3 动态共享目标文件
    ET_CORE 4 core文件,程序崩溃时内存映像的转储格式
    ET_LOPROC 0xff00 未知目标文件格式
    ET_HIPROC 0xff00 未知目标文件格式
  • e_machine
    e_machine 占 2 个字节,用来描述 ELF 目标文件的体系结构类型,也就是说该文件在哪种硬件平台上运行。

  • e_version
    占用 4字节,表示版本信息。

  • e_entry
    占用4字节,指明操作系统运行该程序时,将控制权转交到的虚拟地址,也就是程序的入口地址。可重定位文件一般没有入口地址,则这个为0。

  • e_phoff
    占用 4字节,指明程序头表(program header table)在文件内的偏移量。

  • e_shoff
    占用4字节,用来指明节头表(section header table)在文件内的字节偏移量。

  • e_flags
    占用4字节。

  • e_ehsize
    占用2字节,说明 ELF header 的字节大小。

  • e_phentsize
    占用2字节,用来指明程序头表中每个条目的字节大小,即每个用来描述段信息的数据结构的大小,也就是后面要介绍的struct Elf32_Phdr。

  • e_phenum
    占用2字节,用来指明程序头表中条目的数量,也就是段的个数。

  • e_shentsize
    占用2字节,用来指明节头表中每个条目的字节大小,即每个用来描述节信息的数据结构的大小。

  • e_shnum
    占用2字节,用来指明节头表中条目的数量,也就是节的个数。

  • e_shrtrndx
    说明 string name table 在节头部表中的索引。

Section

objdump -h命令只是把ELF文件中关键的节显示了出来,而省略了其他辅助性的节,可以使用readelf工具来查看所有的节。

readelf -S test.o
image.png

readelf 输出的结果就是ELF文件节表的内容,节表的结构比较简单就是一个以Elf32_Shdr结构体为元素的数组,数组元素个数等于段的个数。Elf32_Shdr又被称为节描述符。

typedef uint16_t Elf32_Half;     /* 2字节 */
typedef uint32_t Elf32_Word;     /* 4字节 */
typedef uint32_t Elf32_Off;      /* 4字节 */
typedef uint32_t Elf32_Addr;     /* 4字节 */

struct Elf32_Shdr
{
    Elf32_Word  sh_name;
    Elf32_Word  sh_type;    
};
  • sh_name
    记录section名,section名位于一个.shstrtab的字符串表,sh_name是section名字符串在.shstrtab中的偏移。
  • sh_type
    节的类型。
    常量 含义
    SHT_NULL 0 无效
    SHT_PROGBITS 1 程序节
    SHT_SYMTAB 2 符号表
  • sh_flags
    section 的标志位。该标志位表示该section在进程虚拟地址空间中的属性,比如是否可写,是否可执行。

    常量 含义
    SHF_WRITE 1 表示该section在进程中可写
    SHF_ALLOC 2 表示该section在进程空间中必须要分配空间
    SHF_EXECINSTR 3 表示该section在进程空间中可执行
  • sh_addr
    section 的虚拟地址.

  • sh_offset
    section 的偏移

  • sh_size
    section 的长度

  • sh_link sh-info


重定位表

链接器在处理目标文件类型时,需要对目标文件中某些部位进行重定位,即代码段和数据段中那些对绝对地址的引用的位置。这些重定位信息都记录在ELF文件的重定位表里面。对于每一个需要重定位的代码段和数据段,都会有一个相应的重定位表。比如.rel.text就是针对.text段的重定位表。


字符串表

链接的接口-符号

在连接中,目标文件之间相互拼合实际上是目标文件之间对地址的引用,比如目标文件B用到了目标文件A中的函数foo,则目标文件A定义了函数foo,目标文件B引用了目标文件A中的函数foo。在链接过程中,将函数和变量统称为符号

每一个目标文件都会有一个相应的符号表,这个表里面记录了目标文件所用到的所有符号,每个定义的符号有一个对应的值,叫做符号值,对于变量和函数来说,符号值就是他们的地址。除了函数和变量外,还存在其他几种不常用的符号。

将符号进行分类,它们可能是:

  • 定义在本目标文件的全局符号,可以被其他目标文件引用。

  • 在本目标文件中引用的全局符号,却没有定义在本目标文件中,一般称为外部符号

  • section名

  • 局部符号,这类符号只在编译单元内部可见,比如static_varstatic_var2

  • 行号信息

对于链接来说,只关注全局符号,即上面分类中的第一类和第二类。其他局部符号,行号等,对于其他目标文件来说,是不可见的。

可以用nm工具查看符号。

$ nm test.o
  00000000 T func1
  00000000 D global_init_var
  00000004 C global_uninit_var
  0000001b T main
           U printf
  00000004 d static_var.1701
  00000000 b static_var2.1702

ELF 符号表结构

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

typedef uint16_t Elf32_Half;     /* 2字节 */
typedef uint32_t Elf32_Word;     /* 4字节 */
typedef uint32_t Elf32_Off;      /* 4字节 */
typedef uint32_t Elf32_Addr;     /* 4字节 */

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_index;
} Elf32_Sym;
  • st_name
    指定该符号名在字符串表中的下标。

  • st_value
    符号相对应的值,这个值和符号有关,可能是一个绝对值,也可能是一个地址。

  • st_size
    符号大小,对于包含数据的符号,这个值是该数据类型的大小。

  • st_info
    该成员低4位标识符号的类型,高28位标识符号绑定信息。

    • 符号绑定信息
      宏定义 含义
      STB_LOCAL 0 局部符号,对于目标文件的外部不可见
      STB_GLOBAL 1 全局符号,外部可见
      STB_WEAK 2 弱引用,区分弱符号和强符号
    • 符号类型
      宏定义 含义
      STT_NOTYPE 0 未知类型符号
      STT_OBJECT 1 该符号是一个数据对象,比如变量,数组等
      STT_FUNC 2 该符号是一个函数
      STT_SECTION 3 该符号表示一个段,这种符号必须是 STB_LOCAL的
      STT_FILE 4 该符号表示文件名,一般都是该目标文件所对应的源文件名,
  • st_other
    暂未使用。

  • st_shndx
    符号所在的段,如果符号定义在本目标文件中,那么这个成员表示符号所在的段在段表中的下标。如果符号不是定义在本目标文件中或者特殊符号,那么其值将如下表所示:

    宏定义 含义
    SHN_ABS 0xfff1 表示该符号包含了一个绝对的值,比如表示文件名符号
    SHN_COMMON 0xfff2 表示该符号是一个"COMMON块"类型符号,一般来说,未初始化的全局符号定义就是这种类型
    SHN_UNDEF 0 表示该符号未定义,这个符号表示该符号在本目标文件被引用到,但是定义在其他目标文件中
$ readelf -s test.o

  Symbol table '.symtab' contains 16 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS test.c
     2: 00000000     0 SECTION LOCAL  DEFAULT    1 
     3: 00000000     0 SECTION LOCAL  DEFAULT    3 
     4: 00000000     0 SECTION LOCAL  DEFAULT    4 
     5: 00000000     0 SECTION LOCAL  DEFAULT    5 
     6: 00000004     4 OBJECT  LOCAL  DEFAULT    3 static_var.1701
     7: 00000000     4 OBJECT  LOCAL  DEFAULT    4 static_var2.1702
     8: 00000000     0 SECTION LOCAL  DEFAULT    7 
     9: 00000000     0 SECTION LOCAL  DEFAULT    8 
    10: 00000000     0 SECTION LOCAL  DEFAULT    6 
    11: 00000000     4 OBJECT  GLOBAL DEFAULT    3 global_init_var
    12: 00000004     4 OBJECT  GLOBAL DEFAULT  COM global_uninit_var
    13: 00000000    27 FUNC    GLOBAL DEFAULT    1 
    14: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND printf
    15: 0000001b    56 FUNC    GLOBAL DEFAULT    1 main
  • funcmain 函数他们在.text,所以Ndx为1;它们是函数,所以类型为STT_FUNC;它绑定属性为 STB_GLOBAL,表示全局可见;Size表示函数指令所占用的字节数;Value表示函数相对于代码段起始位置的偏移量。

  • printf 在test.c中被引用,但是没有被定义,所以NdxSHN_UNDEF;类型是STT_NOTYPE;绑定属性为STB_GLOBAL,表示全局可见。

  • global_init_var在已初始化的全局变量,它在.data段,所以Ndx为3;类型是STT_OBJECT;绑定属性为STB_GLOBAL,表示全局可见。
    global_uninit_var是未初始化的全局变量,它是一个SHN_COMMON,其并不存在于.bss段;类型是STT_OBJECT;绑定属性为STB_GLOBAL,表示全局可见。

  • static_var.1701 是已初始化的局部静态变量,它在.data段,所以Ndx为3;类型是STT_OBJECT;绑定属性为STB_LOCAL,即是只在编译单元内部可见。
    static_var2.1702是未初始化的局部静态变量,它在.bss段,所以Ndx为4;类型是STT_OBJECT;绑定属性为STB_LOCAL,即是只在编译单元内部可见。

相关文章

网友评论

      本文标题:程序编译链接(三)-- ELF文件格式

      本文链接:https://www.haomeiwen.com/subject/qkuaoftx.html