可重定位文件结构分析
1. 文件头
使用命令readelf –h vmlinux查看elf文件头:
[mszsdtcf49][~/ws/arm_elf_linux/relocate_elf_reader]$ readelf -h vmlinux.o
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: AArch64
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 818885464 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 0 (201200)
Section header string table index: 65535 (201196)
[mszsdtcf49][~/ws/arm_elf_linux/relocate_elf_reader]$
文件的最开始几个字节给出如何解释文件的提示信息。这些信息独立于处理器,也独立于文件中的其余内容。ELF Header 部分可以用以下的数据结构表示:
typedef struct {
unsigned char e_ident[EI_NIDENT]; /* Id bytes */
Elf64_Quarter e_type; /* file type */
Elf64_Quarter e_machine; /* machine type */
Elf64_Half e_version; /* version number */
Elf64_Addr e_entry; /* entry point */
Elf64_Off e_phoff; /* Program hdr offset */
Elf64_Off e_shoff; /* Section hdr offset */
Elf64_Half e_flags; /* Processor flags */
Elf64_Quarter e_ehsize; /* sizeof ehdr */
Elf64_Quarter e_phentsize; /* Program header entry size */
Elf64_Quarter e_phnum; /* Number of program headers */
Elf64_Quarter e_shentsize; /* Section header entry size */
Elf64_Quarter e_shnum; /* Number of section headers */
Elf64_Quarter e_shstrndx; /* String table index */
} Elf64_Ehdr;
来源:https://github.com/torvalds/linux/blob/master/include/uapi/linux/elf.h#L222
ELF Header大小比较小,只占用64个byte空间,每个字段意义与readelf输出的对应。
图片来源:https://bbs.pediy.com/thread-255670.htm
2. 节(section)
节区头部表
ELF 头部中,e_shoff 成员给出从文件头到节区头部表格的偏移字节数; e_shnum 给出表格中条目数目; e_shentsize 给出每个项目的字节数。从这些信息中可以确切地定 位节区的具体位置、长度。
每个节区头部数据结构描述:
typedef struct {
Elf64_Half sh_name; /* section name */
Elf64_Half sh_type; /* section type */
Elf64_Xword sh_flags; /* section flags */
Elf64_Addr sh_addr; /* virtual address */
Elf64_Off sh_offset; /* file offset */
Elf64_Xword sh_size; /* section size */
Elf64_Half sh_link; /* link to another */
Elf64_Half sh_info; /* misc info */
Elf64_Xword sh_addralign; /* memory alignment */
Elf64_Xword sh_entsize; /* table entry size */
} Elf64_Shdr;
如何从Elf64_Ehdr 找到Elf64_Shdr?
shdrs = (Elf64_Shdr *)(ehdr->e_shoff + (void *)ehdr);
第一个section不描述任何数据,但是保存了节区数量以及节区使用的符号表位置,其内容如下:
[mszsdtcf49][~/ws/arm_elf_linux/relocate_elf_reader]$ ./relocate_elf -n 0 vmlinux.o
The file:vmlinux.o size is 833088504.
shdr->sh_name = 0
shdr->sh_type = 0
shdr->sh_flags = 0
shdr->sh_addr = 0
shdr->sh_offset= 0
shdr->sh_size = 0x311f0 // section个数
shdr->sh_link = 0x311ec // section的symbol存放在哪个section中
shdr->sh_info = 0
shdr->sh_addralign = 0
shdr->sh_entsize = 0
[mszsdtcf49][~/ws/arm_elf_linux/relocate_elf_reader]$
ehdr与shdr的关系如下图:
图片来源:http://chuquan.me/2018/05/21/elf-introduce/
3. section类型
节包含对象文件中的所有信息,但ELF头,程序头表和节头表除外。此外,目标文件的节满足几个条件。
• 对象文件中的每个节都只有一个描述它的节头。可能存在不包含节的节标题。
• 每个节在文件中占用一个连续的字节序列(可能为空)。
• 文件中的节可能不会重叠。文件中的任何字节都不能驻留在多个部分中。
• 目标文件可能具有无效空间。各种标头和节可能不会``覆盖''目标文件中的每个字节。未指定非活动数据的内容。
3.1 section结构体
section的类型存放在section table中的sh_type中,elf.h中列出的取值可能如下:
/* Legal values for sh_type (section type). */
#define SHT_NULL 0 /* Section header table entry unused */
#define SHT_PROGBITS 1 /* Program data */
#define SHT_SYMTAB 2 /* Symbol table */
#define SHT_STRTAB 3 /* String table */
#define SHT_RELA 4 /* Relocation entries with addends */
#define SHT_HASH 5 /* Symbol hash table */
#define SHT_DYNAMIC 6 /* Dynamic linking information */
#define SHT_NOTE 7 /* Notes */
#define SHT_NOBITS 8 /* Program space with no data (bss) */
#define SHT_REL 9 /* Relocation entries, no addends */
#define SHT_SHLIB 10 /* Reserved */
#define SHT_DYNSYM 11 /* Dynamic linker symbol table */
#define SHT_INIT_ARRAY 14 /* Array of constructors */
#define SHT_FINI_ARRAY 15 /* Array of destructors */
#define SHT_PREINIT_ARRAY 16 /* Array of pre-constructors */
#define SHT_GROUP 17 /* Section group */
#define SHT_SYMTAB_SHNDX 18 /* Extended section indeces */
#define SHT_NUM 19 /* Number of defined types. */
#define SHT_LOOS 0x60000000 /* Start OS-specific. */
#define SHT_GNU_ATTRIBUTES 0x6ffffff5 /* Object attributes. */
#define SHT_GNU_HASH 0x6ffffff6 /* GNU-style hash table. */
#define SHT_GNU_LIBLIST 0x6ffffff7 /* Prelink library list */
#define SHT_CHECKSUM 0x6ffffff8 /* Checksum for DSO content. */
#define SHT_LOSUNW 0x6ffffffa /* Sun-specific low bound. */
#define SHT_SUNW_move 0x6ffffffa
#define SHT_SUNW_COMDAT 0x6ffffffb
#define SHT_SUNW_syminfo 0x6ffffffc
#define SHT_GNU_verdef 0x6ffffffd /* Version definition section. */
#define SHT_GNU_verneed 0x6ffffffe /* Version needs section. */
#define SHT_GNU_versym 0x6fffffff /* Version symbol table. */
#define SHT_HISUNW 0x6fffffff /* Sun-specific high bound. */
#define SHT_HIOS 0x6fffffff /* End OS-specific type */
#define SHT_LOPROC 0x70000000 /* Start of processor-specific */
#define SHT_HIPROC 0x7fffffff /* End of processor-specific */
#define SHT_LOUSER 0x80000000 /* Start of application-specific */
#define SHT_HIUSER 0x8fffffff /* End of application-specific */
常用的section type解释如下:
SHT_NULL 此值将节标题标记为无效;它没有关联的部分。节标题的其他成员具有未定义的值。
SHT_PROGBITS 该部分保存由程序定义的信息,其格式和含义仅由程序确定。
SHT_SYMTAB和SHT_DYNSYM 这些部分包含一个符号表。当前,一个目标文件可能每种类型只有一个节,但是将来可能会放宽此限制。通常, SHT_SYMTAB提供用于链接编辑的符号,尽管它也可以用于动态链接。作为完整的符号表,它可能包含许多动态链接不需要的符号。因此,目标文件也可能包含SHT_DYNSYM节,该节保留最少的动态链接符号集,以节省空间。
SHT_STRTAB 该部分包含一个字符串表。一个目标文件可能具有多个字符串表节。
SHT_RELA 本节包含带有显式加数的重定位条目,例如对于32位类的目标文件,类型为Elf32_Rela。一个目标文件可能具有多个重定位部分。
SHT_HASH 该部分包含一个符号哈希表。当前,一个目标文件可能只有一个哈希表,但是将来可能会放宽此限制。
SHT_DYNAMIC 本节包含用于动态链接的信息。当前,一个目标文件可能只有一个动态节,但是将来可能会放宽此限制。
SHT_NOTE 本节包含以某种方式标记文件的信息。
SHT_NOBITS 此类型的节在文件中不占空间,但与SHT_PROGBITS相似。尽管本节不包含任何字节,但该sh_offset成员包含概念性文件偏移量。
SHT_REL 本节包含没有明确加数的重定位条目,例如 32位类目标文件的Elf32_Rel类型。一个目标文件可能具有多个重定位部分。
SHT_SHLIB 此节类型是保留的,但具有未指定的语义。
来源:http://osr5doc.xinuos.com/en/topics/ELF_secthead.html
• SHT_SYMTAB、SHT_DYNSYM → Symbol table entry
如果节的类型是SHT_SYMTAB或者SHT_DYNSYM,那么该节的内容使用Elf32_Sym和Elf64_Sym描述。
• SHT_RELA → Relocation table entry with addend
如果节的类型是SHT_RELA,那么该节的内容使用Elf32_Rela和Elf64_Rela描述。
• SHT_DYNAMIC → Dynamic section entry
如果节的类型是SHT_DYNAMIC,那么该节的内容使用Elf32_Dyn和Elf64_Dyn描述。
3.2 Symbol
Elf64_Shdr中,如果sh_type描述的section取值为SHT_SYMTAB、SHT_DYNSYM,,那么该section为一个Elf64_Sym[] 类型symbol tab。
/* Symbol table entry. */
typedef struct
{
Elf64_Word st_name; /* Symbol name (string tbl index) */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf64_Section st_shndx; /* Section index */
Elf64_Addr st_value; /* Symbol value */
Elf64_Xword st_size; /* Symbol size */
} Elf64_Sym;
可重定位文件的符号表包含定位和重新定位程序的符号定义和引用所需的信息。符号表索引是该数组的下标。索引0既指定表中的第一个条目,又用作未定义的符号索引。
st_name 目标文件的符号字符串表的索引,其中包含符号名称的字符表示。如果该值非零,则表示给出符号名称的字符串表索引。否则,符号表条目将没有名称。
st_value 相关符号的值。根据上下文,这可以是绝对值,地址等。请参见“符号值”。
st_size 许多符号都有关联的大小。例如,数据对象的大小是对象中包含的字节数。如果符号没有大小或未知大小,则该成员保持0。
st_info 符号的类型和绑定属性。表7-19中显示了值和含义的列表。以下代码显示了如何操作在中定义的值sys/elf.h:
#define ELF32_ST_BIND(info) ((info) >> 4)
#define ELF32_ST_TYPE(info) ((info) & 0xf)
#define ELF32_ST_INFO(bind, type) (((bind)<<4)+((type)&0xf))
#define ELF64_ST_BIND(info) ((info) >> 4)
#define ELF64_ST_TYPE(info) ((info) & 0xf)
#define ELF64_ST_INFO(bind, type) (((bind)<<4)+((type)&0xf))
st_other 符号的可见性。表7-21中显示了值和含义的列表。以下代码显示了如何操作32位和64位对象的值。其他位包含0,没有定义的含义。
#define ELF32_ST_VISIBILITY(o) ((o)&0x3)
#define ELF64_ST_VISIBILITY(o) ((o)&0x3)
st_shndx 每个符号表条目都是相对于某个部分定义的。该成员保存相关节头表的索引。如表7-12所示,某些部分的索引指示特殊含义。
来源:https://docs.oracle.com/cd/E19683-01/816-1386/chapter6-79797/index.html
特定section index
在ehdr中会记录所有的section,通过index来查找section,这里的st_shndx记录的也是section数组的index,但是一些节头表索引是保留的;可重定位文件也没有这些特殊索引的部分。
Name Value
SHN_UNDEF 0
SHN_LORESERVE 0xff00
SHN_LOPROC 0xff00
SHN_HIPROC 0xff1f
SHN_ABS 0xfff1
SHN_COMMON 0xfff2
SHN_HIRESERVE 0xffff
SHN_UNDEF 通常表示该符号在本文件中未定义 (外部符号),Linux linker 会为该符号做地址重定位。
SHN_LORESERVE 此值指定保留索引范围的下限。
SHN_LOPROC到SHN_HIPROC 此包含范围内的值保留用于特定于处理器的语义。
SHN_ABS 表示该符号包含一个绝对的 (absolute) 值 (往往是一个地址),不受重定位影响。(例如FILE类型,__bss_start symbol类型)
SHN_COMMON 相对于本节定义的符号是通用符号,例如FORTRAN,COMMON或未分配的C外部变量。(表示该符号是一个 common 符号,通常未初始化的全局变量就是该类型的符号。)
SHN_HIRESERVE 此值指定保留索引范围的上限。系统在SHN_LORESERVE和SHN_HIRESERVE之间(包括两端)保留索引 ;这些值不引用节标题表。节标题表不包含保留索引的条目。
3.3 strtab
.strtab节保存的是符号字符串表,表中的内容会被.symtab的Elf64_Sym结构中的st_name引用。节类型为SHT_STRTAB。
当shdrs[n].sh_type == SHT_SYMTAB或者SHT_DYNSYM时,该section的sh_link指向strtab.
if (shdrs[i].sh_type == SHT_SYMTAB || shdrs[i].sh_type == SHT_DYNSYM) {
const char *shname = shstrtab + shdrs[i].sh_name;
Elf64_Sym *syms = base + shdrs[i].sh_offset;
size_t entries = shdrs[i].sh_size / shdrs[i].sh_entsize;
const char *strtab = base + shdrs[shdrs[i].sh_link].sh_offset;
do_elf_section_info(&shdrs[i]);
printf("shname=%s, syms=%px, strtab=%px\n", shname, syms, strtab);
print_syms(shdrs, shstrtab, shname, syms, entries, strtab);
}
来源:http://chuquan.me/2018/05/21/elf-introduce/
通过section .strtab可以找到strtab,然后根据section .symtab的name来查找对应symbol的name。
3.4 重定位表(rel/rela)
重定位是将符号引用与符号定义连接在一起的过程。例如,当程序调用函数时,相关的调用指令必须在执行时将控制权转移到正确的目标地址。可重定位文件必须具有描述如何修改其节内容的信息,从而使可执行文件和共享目标文件可以保存进程程序映像的正确信息。
typedef struct
{
Elf64_Addr r_offset;
Elf64_Xword r_info;
} Elf64_Rel;
typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
Elf64_Sxword r_addend; /* Addend */
} Elf64_Rela;
关于rel/rela字段的解释如下:
r_offset 该成员提供了应用重定位操作的位置。不同的对象文件对此成员的解释略有不同。
对于可重定位文件,该值指示节偏移量(_moucnt_loc中的offset)。重定位部分本身描述了如何修改文件中的另一部分。重定位偏移量指定第二部分中的存储单元。
对于可执行文件或共享库,该值指示受重定位影响的存储单元的虚拟地址。此信息使重定位条目对运行时链接程序更加有用。
尽管对于不同的对象文件,成员的解释有所不同,以允许相关程序进行有效访问,但是重定位类型的含义保持不变。
r_info 该成员既提供符号表索引(必须针对该索引进行重定位),也提供要应用的重定位类型。例如,调用指令的重定位条目将保存被调用函数的符号表索引。如果索引是STN_UNDEF,即未定义的符号索引,则重定位使用0作为符号值。
重定位类型是特定于处理器的。重定位条目的重定位类型或符号表索引是分别将ELF32_R_TYPE或ELF32_R_SYM应用于条目的r_info成员的结果:
#define ELF64_R_SYM(info) ((info)>>32)
#define ELF64_R_TYPE(info) ((Elf64_Word)(info))
#define ELF64_R_INFO(sym, type) (((Elf64_Xword)(sym)<<32)+\
(Elf64_Xword)(type))
对于Elf64_Rel和Elf64_Rela结构,该r_info字段进一步细分为8位类型标识符和24位类型相关数据字段:
#define ELF64_R_TYPE_DATA(info) (((Elf64_Xword)(info)<<32)>>40)
#define ELF64_R_TYPE_ID(info) (((Elf64_Xword)(info)<<56)>>56)
#define ELF64_R_TYPE_INFO(data, type) (((Elf64_Xword)(data)<<8)+\
(Elf64_Xword)(type))
r_addend 该成员指定用于计算要存储到可重定位字段中的值的常数加数。
来源:https://docs.oracle.com/cd/E19683-01/816-1386/6m7qcoblj/index.html#chapter6-54839
以mcount_loc 为例,从vmlinux.o中读出来的rela_mcount_loc section信息如下:
[*] Offset Info Addend Type Sym. Value Sym. Name (section)
0000000000000000 0000000600000101 00007100 OTHERS 0000000000000000 $x.2 (.rela__mcount_loc)
0000000000000008 0000000600000101 00007138 OTHERS 0000000000000000 $x.2 (.rela__mcount_loc)
0000000000000010 0000000600000101 000071f4 OTHERS 0000000000000000 $x.2 (.rela__mcount_loc)
Offset: 存放在mcount_loc中的位置.
Info: 前32bit为描述的section index,后32bit为type.
Addend: 相对section的偏移量.