【Linux】ELF文件结构源码分析

一、ELF文件头结构体:Elf32_Ehdr

查看ELF文件的文件头:

[gongruiyang@localhost ws]$ readelf -h SimpleSection.o
ELF 头:
      Magic:  7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
      Class:                             ELF64
      Data:                             2 补码,小端序 (little endian)
      Version:                          1 (current)
      OS/ABI:                           UNIX - System V
      ABI Version:                      0
      Type:                             REL (可重定位文件)
      Machine:                          Advanced Micro Devices X86-64
      Version:                           0x1
      Entry point address:              0x0
      Start of program headers:         0 (bytes into file)
      Start of section headers:          1064 (bytes into file)
      Flags:                             0x0
      本头的大小:                        64 (字节)
      程序头大小:                        0 (字节)
      Number of program headers:         0
      节头大小:                          64 (字节)
      节头数量:                          13
      字符串表索引节头:                   12

可见该文件的ELF Header的大小为64字节(0x40)

与该文件头对应的内核源码中的结构体为:include\linux\elf.h

typedef struct elf32_hdr{
    unsigned char	e_ident[EI_NIDENT /* 16 */];  /* ELF魔法数字 Magic */
    Elf32_Half/* unsigned short */	 e_type;     /* ELF文件类型 */
    Elf32_Half/* unsigned short */	 e_machine;  /* ELF文件的CPU平台属性,相关常量以EM_开头 */
    Elf32_Word/* unsigned int */	   	e_version;  /* ELF版本号,一般为常数 1 */
    Elf32_Addr/* unsigned int */	   e_entry;    /* ELF程序的入口地址,可重定位文件一般无入口地址设置为0 */
    Elf32_Off /* unsigned int */	   e_phoff;    /*  */
    Elf32_Off /* unsigned int */	   e_shoff;    /* 段表在文件中的偏移,也就是段表从文件的第e_shoff+1个字节开始 */
    Elf32_Word/* unsigned int */	   e_flags;    /* ELF标志位*/
    Elf32_Half/* unsigned short */	 e_ehsize;   /* ELF文件头本身大小 */
    Elf32_Half/* unsigned short */	 e_phentsize;/*  */
    Elf32_Half/* unsigned short */	 e_phnum;    /*  */
    Elf32_Half/* unsigned short */	 e_shentsize;/* 段表描述符的大小,等于 sizeof Elf32_Shdr */
    Elf32_Half/* unsigned short */	 e_shnum;    /* 段表描述符的数量,等于ELF文件中拥有段的数量 */
    Elf32_Half/* unsigned short */	 e_shstrndx; /* 段表字符串表 所在的段 在段表中的下标 */
} Elf32_Ehdr;

e_ident

e_ident是一个16个字节大小的数组,正好与 Magic中16个字节的数据一一对应

ELF标记:

#define	ELFMAG0		0x7f		/* EI_MAG */
#define	ELFMAG1		'E'
#define	ELFMAG2		'L'
#define	ELFMAG3		'F'

ELF文件类:

#define	ELFCLASSNONE	0		/* EI_CLASS */
#define	ELFCLASS32	1
#define	ELFCLASS64	2
#define	ELFCLASSNUM	3

字节序:

#define ELFDATANONE	0		/* e_ident[EI_DATA] */
#define ELFDATA2LSB	1
#define ELFDATA2MSB	2

ELF版本:

#define EV_NONE		0		/* e_version, EI_VERSION */
#define EV_CURRENT	1
#define EV_NUM		2

在这里插入图片描述

e_type

e_type表示ELF文件类型的,有以下常量表示

/* These constants define the different elf file types */
#define ET_NONE   0
#define ET_REL    1
#define ET_EXEC   2
#define ET_DYN    3
#define ET_CORE   4
常量含义
ET_REL1可重定位文件,一般为 .o 文件
ET_EXEC2可执行文件,例如 a.out
ET_DYN3共享目标文件,一般为 ,so 文件
ET_CORE4core dump 文件

e_machine

e_machine表示机器类型,ELF文件格式被设计成可以在多个平台下使用,e_machine 成员就表示该平台属性,比如 e_machine 值为 3 表示该ELF文件只能在 Intel x86 机器下使用

/* These constants define the various ELF target machines */
#define EM_NONE		0
#define EM_M32		1
#define EM_SPARC	2
#define EM_386		3
#define EM_68K		4
#define EM_88K		5
#define EM_486		6	/* Perhaps disused */
#define EM_860		7
常量含义
EM_M321AT&T WE 32100
EM_SPARC2SPARC
EM_3863Intel x86
EM_68K4Motorola 68000
EM_88K5Motorola 88000
EM_8697Intel 80860

二、段表描述符结构体:Elf32_Shdr

使用objdump -h命令看到的是 ELF 文件中包含的关键段显示出来,而省略了其他辅助性的段,比如:符号表、字符串表、段名字符串表、重定位表等,我们可以使用readelf -S命令来看完整的段表结构:

[gongruiyang@localhost ws]$ readelf -S SimpleSection.o
共有 13 个节头,从偏移量 0x428 开始:

节头:
[] 名称              类型     地址              偏移量    大小                全体大小          旗标 链接 信息 对齐
[ 0]                   NULL     0000000000000000  00000000  0000000000000000  0000000000000000         0  0    0
[ 1] .text             PROGBITS 0000000000000000  00000040  0000000000000055  0000000000000000  AX     0  0    1
[ 2] .rela.text        RELA     0000000000000000  00000318  0000000000000078  0000000000000018   I    10  1    8
[ 3] .data             PROGBITS 0000000000000000  00000098  0000000000000008  0000000000000000  WA     0  0    4
[ 4] .bss              NOBITS   0000000000000000  000000a0  0000000000000004  0000000000000000  WA     0  0    4
[ 5] .rodata           PROGBITS 0000000000000000  000000a0  0000000000000004  0000000000000000   A     0  0    1
[ 6] .comment          PROGBITS 0000000000000000  000000a4  000000000000002d  0000000000000001  MS     0  0    1
[ 7] .note.GNU-stack   PROGBITS 0000000000000000  000000d1  0000000000000000  0000000000000000         0  0    1
[ 8] .eh_frame         PROGBITS 0000000000000000  000000d8  0000000000000058  0000000000000000   A     0  0    8
[ 9] .rela.eh_frame    RELA     0000000000000000  00000390  0000000000000030  0000000000000018   I    10  8    8
[10] .symtab           SYMTAB   0000000000000000  00000130  0000000000000180  0000000000000018        11  11   8
[11] .strtab           STRTAB   0000000000000000  000002b0  0000000000000067  0000000000000000         0  0    1
[12] .shstrtab         STRTAB   0000000000000000  000003c0  0000000000000061  0000000000000000         0  0    1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

根据偏移量可以画出如下所示图解:

在这里插入图片描述

与之相对应内核源码中的结构体为:include\linux\elf.h

typedef struct {
    Elf32_Word/*unsigned int*/	sh_name;
    Elf32_Word/*unsigned int*/	sh_type;
    Elf32_Word/*unsigned int*/	sh_flags;
    Elf32_Addr/*unsigned int*/	sh_addr;
    Elf32_Off/*unsigned int*/	sh_offset;
    Elf32_Word/*unsigned int*/	sh_size;
    Elf32_Word/*unsigned int*/	sh_link;
    Elf32_Word/*unsigned int*/	sh_info;
    Elf32_Word/*unsigned int*/	sh_addralign;
    Elf32_Word/*unsigned int*/	sh_entsize;
} Elf32_Shdr;

typedef struct elf64_shdr {
    Elf64_Word/*unsigned int*/ sh_name;		/* Section name, index in string tbl */
    Elf64_Word/*unsigned int*/ sh_type;		/* Type of section */
    Elf64_Xword/*unsigned long long*/ sh_flags;		/* Miscellaneous section attributes */
    Elf64_Addr/*unsigned long long*/ sh_addr;		/* Section virtual addr at execution */
    Elf64_Off/*unsigned long long*/ sh_offset;		/* Section file offset */
    Elf64_Xword/*unsigned long long*/ sh_size;		/* Size of section in bytes */
    Elf64_Word/*unsigned int*/ sh_link;		/* Index of another section */
    Elf64_Word/*unsigned int*/ sh_info;		/* Additional section information */
    Elf64_Xword/*unsigned long long*/ sh_addralign;	/* Section alignment */
    Elf64_Xword/*unsigned long long*/ sh_entsize;	/* Entry size if section holds table */
} Elf64_Shdr;

结构体中成员变量含义如表所示:

成员变量含义
sh_name段名
sh_type段的类型
sh_flags段的标志位
sh_addr段虚地址
sh_offset段偏移量
sh_size段的长度
sh_link 和 sh_info段链接信息
sh_addralign段地址对齐
sh_entsize项的长度
  • sh_name:段名是一个字符串,位于一个叫做 “.shstrtab” 的字符串表,sh_name中保存的是一个数值表示段名字符串在 ”.shstrtab“ 中的偏移
  • sh_addr:如果该段可以被加载,则 sh_addr 为该段被加载后在进程地址空间中的虚拟地址,否则为0
  • sh_offset:若该段位于文件中,则表示该段在文件中的偏移;否则无意义
  • sh_addralign:若该值为 0 或 1 表示无对齐要求,其他值则需按照对应值对齐
  • sh_entsize:有些段(例如:符号表)包含一些固定大小的项,对于这种段,sh_entsize 表示每个项的大小,如果该值为 0 表示该段不包含固定大小的项

sh_type

sh_type表示段的类型,使用不同的常量来区分不同的类型

/* sh_type */
#define SHT_NULL	0
#define SHT_PROGBITS	1
#define SHT_SYMTAB	2
#define SHT_STRTAB	3
#define SHT_RELA	4
#define SHT_HASH	5
#define SHT_DYNAMIC	6
#define SHT_NOTE	7
#define SHT_NOBITS	8
#define SHT_REL		9
#define SHT_SHLIB	10
#define SHT_DYNSYM	11
#define SHT_NUM		12
#define SHT_LOPROC	0x70000000
#define SHT_HIPROC	0x7fffffff
#define SHT_LOUSER	0x80000000
#define SHT_HIUSER	0xffffffff
常量含义
SHT_NULL0无效段
SHT_PROGBITS1程序段、代码段、数据段
SHT_SYMTAB2符号表
SHT_STRTAB3字符串表
SHT_RELA4重定位表
SHT_HASH5符号表的哈希表
SHT_DYNAMIC6动态连接信息
SHT_NOTE7提示性信息
SHT_NOBITS8表示该段在文件中没内容
SHT_REL9该段包含重定位信息
SHT_SHLIB10保留段
SHT_DYNSYM11动态链接的符号表

sh_flag

sh_flag:段的标志位表示该段在进程虚拟地址空间中的属性,比如是否可写、是否可执行等

/* sh_flags */
#define SHF_WRITE	0x1
#define SHF_ALLOC	0x2
#define SHF_EXECINSTR	0x4
#define SHF_MASKPROC	0xf0000000
常量含义
SHF_WRITE1表示该段在进程虚拟地址空间中 可写
SHF_ALLOC2表示该段在进程虚拟地址空间中 需要分配空间
SHF_EXECINSTR4表示该段在进程虚拟地址空间中 可以被执行

三、常见系统保留段的属性表

由以下的表我们可以知道:一个段是否是可读写或执行,是取决于该段对应内核中的结构体的一个成员变量决定的

Namesh_typesh_flag
.bssSHT_NOBITS (表示该段无内容)需分配空间 + 可写
.commentSHT_PROGBITS(程序段、代码段、数据段)none
.dataSHT_PROGBITS(数据段)需分配空间 + 可写
.rodataSHT_PROGBITS(数据段)需分配空间
.symtabSHT_SYMTAB(符号表)需分配空间
.textSHT_PROGBITS(程序段)需分配空间 + 可执行
.strtabSHT_STRTAB(字符串表)需分配空间

四、重定位表和字符串表

重定位表

.rela.text该段的类型为SHT_RELA,即该段是一个重定位表。

重定位:链接器在处理目标文件时,需要对代码段和数据段中一些对绝对地址进行引用的位置进行重定位

该段的sh_link表示符号表的下标,sh_info表示作用于哪个段

字符串表

ELF 文件中用到了字符串,例如:段名称、变量名等。

因为字符串的长度是不定的,所以用固定结构表示较为困难,一种常见的存储方法是:把字符串集中起来存放到一个表中,然后使用字符串在表中的偏移量来引用字符串,如表所示:

在这里插入图片描述

那么偏移量与字符串对应关系如下表所示:

在这里插入图片描述

段中最常见的两个字符串表分别是 .strtab(字符串表 String Table).shstrtab(段表字符串表 Section Header String Table)

  • 字符串表:用来保存普通的字符串,例如:符号名称
  • 段表字符串表:用来保存段表中用到的字符串,例如:段名

通过readelf -h SimpleSection.o能够看到最后一个字符串表索引节头:12,表示.shstrtab在段表中的下标,即[12] .shstrtab
26)]

那么偏移量与字符串对应关系如下表所示:

[外链图片转存中…(img-Tbbb3Dio-1640054447227)]

段中最常见的两个字符串表分别是 .strtab(字符串表 String Table).shstrtab(段表字符串表 Section Header String Table)

  • 字符串表:用来保存普通的字符串,例如:符号名称
  • 段表字符串表:用来保存段表中用到的字符串,例如:段名

通过readelf -h SimpleSection.o能够看到最后一个字符串表索引节头:12,表示.shstrtab在段表中的下标,即[12] .shstrtab

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值