节与静态链接
常规的 ELF 文件,ET_REL, ET_DYN 以及 ET_EXEC 类型都含有节,对于 ET_REL 类型 的ELF文件,节是必须存在的,而对于 ET_DYN 和 ET_EXEC 是可以没有节的,但由于Linux系统的版本碎片化比较严重,可执行文件也可能要求含有节,否则无法执行(如安卓后期版本的系统),这依赖于系统动态链接器的实现。
ET_REL 类型的 ELF,节中包含了一些符号定义、节属性(可读可写可执行)。通过 readelf 工具可以方便查看这些信息:
$readelf test.o -S
There are 15 section headers, starting at offset 0x2e0:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .group GROUP 00000000 000034 000008 04 12 12 4
[ 2] .text PROGBITS 00000000 00003c 000032 00 AX 0 0 1
[ 3] .rel.text REL 00000000 00022c 000020 08 I 12 2 4
[ 4] .data PROGBITS 00000000 00006e 000000 00 WA 0 0 1
[ 5] .bss NOBITS 00000000 00006e 000000 00 WA 0 0 1
[ 6] .rodata PROGBITS 00000000 00006e 00000b 00 A 0 0 1
[ 7] .text.__x86.get_p PROGBITS 00000000 000079 000004 00 AXG 0 0 1
[ 8] .comment PROGBITS 00000000 00007d 00002c 01 MS 0 0 1
[ 9] .note.GNU-stack PROGBITS 00000000 0000a9 000000 00 0 0 1
[10] .eh_frame PROGBITS 00000000 0000ac 000050 00 A 0 0 4
[11] .rel.eh_frame REL 00000000 00024c 000010 08 I 12 10 4
[12] .symtab SYMTAB 00000000 0000fc 0000f0 10 13 11 4
[13] .strtab STRTAB 00000000 0001ec 00003e 00 0 0 1
[14] .shstrtab STRTAB 00000000 00025c 000082 00 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),
p (processor specific)
以上readelf 对一个 .o 文件 (ET_REL类型) 的输出,其中 .text 段中存放可执行的代码,.data 中存放数据(如全局变量,静态变量等),.rodata 存放了常量数据(如字符串等),.bss 中存放未初始化或初始化为零的数据,本节将介绍这些细节。
节表
节表是节头(Section Header)数组,位置和个数由 Elf_Ehdr 的 e_shoff 和 e_shnum 指定。
Elf_Shdr* shdrs = (Elf_Shdr*)(elf_data + ehdr->e_shoff);
Elf_Half scnt = elf->e_shnum;
节头包含了节名称,节属性,文件偏移等内容,定义如下:
/* Section header. */
typedef struct
{
Elf_Word sh_name; /* Section name (string tbl index) */
Elf_Word sh_type; /* Section type */
Elf_Word sh_flags; /* Section flags */
Elf_Addr sh_addr; /* Section virtual addr at execution */
Elf_Off sh_offset; /* Section file offset */
Elf_Word sh_size; /* Section size in bytes */
Elf_Word sh_link; /* Link to another section */
Elf_Word sh_info; /* Additional section information */
Elf_Word sh_addralign; /* Section alignment */
Elf_Word sh_entsize; /* Entry size if section holds table */
} Elf_Shdr;
sh_name
节名称,这是一个节字符串表的偏移,节字符串的索引在 Elf_Ehdr 的 e_shstrndx 中指定。
const char* sname = (const char*)(elf_data +
shdr[elf->e_shstrndx].sh_offset);
sh_type
节类型,以下为 ET_REL 常见的节类型定义:
节类型 | 节描述 |
---|---|
SHT_NULL | 索引为 0 的节 |
SHT_PROGBITS | 程序数据,如代码、数据等 |
SHT_NOBITS | 未初始化或初始化为零的数据,该节不占用ELF文件空间,但有大小 |
SHT_SYMTAB | 符号节 |
SHT_STRTAB | 字符串节 |
SHT_REL | REL 重定位节(32位) |
SHT_RELA | RELA 重定位节(64位) |
SHT_GROUP | 组节 |
sh_flags
节的标志位可以组合,描述了节的属性,定义如下:
节标志 | 描述 |
---|---|
SHF_WRITE | 可写 |
SHF_ALLOC | 执行时驻留内存(这种节一般包含在段内) |
SHF_EXECINSTR | 可执行 |
SHF_MERGE | 可能被合并 |
SHF_STRINGS | 包含以 0 结尾的字符串 |
SHF_INFO_LINK | sh_info 关联了另一个节的索引 |
SHF_GROUP | 此节是个组节的成员 |
SHF_TLS | 节包含了线程局部存储(TLS)数据 |
sh_addr
节的虚拟地址,对于 ET_REL 一般都为0。
sh_offset
节的文件偏移。
sh_size
节的大小。
sh_link
关联到其它节(不同节代表不同的意义,后面会有介绍)。
sh_info
附加的节信息(不同节代表不同的意义,后面会有介绍)。
sh_addralign
地址对齐。
sh_entsize
如果节内容是特定元素的数组,该值代表了元素大小,如对于 SHT_SYMTAB,该址为的大小为sizeof(Elf_Sym)。
各个节类型的 sh_info 与 sh_link 的意义如下:
节类型 | sh_info | sh_link |
---|---|---|
SHT_SYMTAB / SHT_DYNSYM | 全局符号的起始索引 | (静态/动态)字符串节索引 |
SHT_REL / SHT_RELA | 被重定位的节的索引 | (静态/动态)符号节索引 |
SHT_DYNAMIC | 无 | 动态字符串节索引 |
SHT_GNU_versym | 无 | 动态符号节索引 |
SHT_GNU_verneed | Verneed 项的数量 | 动态字符串节索引 |
SHT_GNU_verdef | Verdef 项的数量 | 动态字符串节索引 |
SHT_HASH / SHT_GNU_HASH | 无 | 动态符号节索引 |
符号节与字符串节
符号节分静态符号节(ST_SYMTAB)与动态符号节(SHT_DYNSYM),可以通过枚举节表得到。每种符号节在一个 ELF 文件中只存在一个,动态符号节只在 ET_EXEC 和 ET_DYN 类型的 ELF 中存在,不会在 ET_REL 类型的 ELF 中出现。
符号节中包含了 Elf_Sym 类型的符号表,Elf_Sym 的定义如下:
32位定义:
typedef struct
{
Elf32_Word st_name; /* Symbol name (string tbl index) */
Elf32_Addr st_value; /* Symbol value */
Elf32_Word st_size; /* Symbol size */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf32_Section st_shndx; /* Section index */
} Elf32_Sym;
64位定义:
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;
st_name
符号名,该值为符号节所关联的字符串节的字符串索引。
// uint8_t* elf_data;
// Elf_Shdr* sym_sect;
// Elf_Sym* sym;
const char* strtab = (const char*)(elf_data +
shdr[sym_sect->sh_link].sh_off);
const char* sym_name = strtab + sym->st_name;
st_info
符号信息,描述了符号类型与符号绑定信息。
符号类型可以通过 ELF_ST_TYPE(st_info) 获取,符号类型的定义如下:
符号类型 | 描述 |
---|---|
STT_NOTYPE | 未定义符号 |
STT_OBJECT | 符号是数据 |
STT_FUNC | 符号是函数 |
STT_SECTION | 节符号,每个节都会有一个节符号 |
STT_FILE | 符号名是源文件的文件名,如 ./../../test.c |
STT_COMMON | 符号是公共数据 |
STT_TLS | 符号是 TLS 数据 |
符号绑定可以通过 ELF_ST_BIND(st_info) 获取,符号绑定的定义如下:
绑定类型 | 描述 |
---|---|
STB_LOCAL | 局部符号,对其它文件不可见(如被static修饰的函数) |
STB_GLOBAL | 全局符号 |
STB_WEAK | 弱符号(允许符号找不到) |
st_other
描述了符号的可见性,定义如下:
可见类型 | 描述 |
---|---|
STV_DEFAULT | 默认的可见性 |
STV_INTERNAL | 处理器定义的隐藏类 |
STV_HIDDEN | 符号对其它模块隐藏 |
STV_PROTECTED | 符号不导出 |
STV_INTERNAL 和 STV_PROTECTED 是很少见的(至少笔者没有见过),对于SO库(ET_DYN)来说,符号是否导出,不是通过遍历符号表,而是通过查找哈希表,再检查符号类型,后续章节会介绍哈希表。
st_shndx
符号所在节的节索引,还有一些特殊定义:
节索引 | 描述 |
---|---|
SHN_UNDEF | 未定义符号,可能存在其它文件或模块中 |
SHN_LORESERVE | 小于该值且不为SHN_UNDEF,则代表真正的节索引,否则均为特殊索引 |
SHN_ABS | 绝对符号,只是描述一些信息,如对于STT_FILE 类型的符号 |
st_value
符号的地址
st_size
符号大小