ELF文件结构
简介:
ELF (Executable and Linkable Format)文件,也就是在 Linux 中的目标文件,主要有以下三种类型
可重定位文件(Relocatable File),包含由编译器生成的代码以及数据。链接器会将它与其它目标文件链接起来从而创建可执行文件或者共享目标文件。在 Linux 系统中,这种文件的后缀一般为 .o 。
可执行文件(Executable File),就是我们通常在 Linux 中执行的程序。
共享目标文件(Shared Object File),包含代码和数据,这种文件是我们所称的库文件,一般以 .so 结尾。一般情况下,它有以下两种使用情景:
- 链接器(Link eDitor, ld)可能会处理它和其它可重定位文件以及共享目标文件,生成另外一个目标文件。
- 动态链接器(Dynamic Linker)将它与可执行文件以及其它共享目标组合在一起生成进程镜像。
文件格式:处于方便性和效率考虑,根据过程不同,目标文件格式提供了两种并行视图
首先,看一下链接视图:
文件开始处是ELF头部,它给出了整个文件的组织情况。
如果程序头部表(Program Header Table)存在的话,它就会告诉系统如何创建进程。用于生成进程的目标文件必须有程序头部表,但是重定位文件不需要这个表。
节区部分包含在链接视图中要使用的大部分信息:指令,数据,符号表,重定位信息等等。
接下来就是执行视图:其主要不同点就在于没有了section,而有了多个segment。其实这里的segment大都是来源于链接视图中的section
数据形式:ELF文件格式支持8位/32位体系结构。当然,这种格式是可以扩展的,也可以支持更小或者更大位数的处理器架构,因此,目标文件还应该包含一些控制数据,这部分数据表明了目标文件所使用的架构,这也使得它可以被通用的方式来识别和解释。目标文件中的其他数据采用目的处理器的格式进行编码,与在何种机器上创建没有关系,也就是我们可以进行交叉编译。
目标文件中的所有数据结构都遵循“自然”大小和对齐规则。如下。
如果必要,数据结构可以包含显式地补齐来确保4字节对象按4字节对齐,强制数据结构的大小是4的整数倍等等。
ELF HEADER描述了ELF文件的概要信息,利用这个数据结构可以索引到elf文件的全部信息,数据结构如下:
#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT];
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;
e_ident
该变量给出了用于解码和解释文件中与机器无关的数据方式。
其中,e_ident[EI_MAG1]到e_ident[EI_NAG3],即文件的头4个字节,被称作“魔数”,标识该文件是一个ELF目标文件。
e_ident[EI_CLASS]为e_ident[EI_MAG3]的下一个字节,标识文件的类型或容量。
ELF文件的设计使得它可以在多种字节长度的机器之间移植,而不需要强制规定机器的最长字节和最短字节。
ELFCLASS32类型支持文件大小和虚拟地址空间上限为4GB的机器。
e_ident[EI_DATA]字节给出了目标文件中的特定处理器数据的编码方式
文件数据编码方式表明了文件内容的解析方式。正如之前所述,ELFCLASS32类型文件使用了具有1,2和4字节的变量类型。对于已定义的不同编码方式。
ELFDATALSB编码是用补码,最低有效位占用最低地址
ELFDATAMSB编码使用补码,最高有效位占用最低地址
e_ident[EI_DATA]给出了ELF的版本。目前这个值必须是EV_CURRENT,即之前已经给出的e_version。e_ident[EI_PAD]给出了e_ident中未使用字节的开始地址。这些字节被保留并置为0;处理目标文件的程序应该忽略它们。如果被使用,EI_PAD的值就会改变。
e_machine
这一项指定了当前文件可以运行的机器架构
其中EM应该是ELF Machine的简写
e_version
识别目标文件的版本
e_entry
这一项为系统转交控制权给elf中相应代码的虚拟地址。如果没有相应的入口项,则这一项为0.
e_phoff
这一项给出程序头部表在文件中的字节偏移(Program Header table OFFset)。如果文件中没有程序头部表,则为0。
e_shoff
这一项给出节头表在文件中的字节偏移( Section Header table OFFset )。如果文件中没有节头表,则为0。
e_flags
这一项给出文件中与特定处理器相关的标志,这些标志命名格式为
e_ehsize¶
这一项给出 ELF 文件头部的字节长度(ELF Header Size)。
e_phentsize¶
这一项给出程序头部表中每个表项的字节长度(Program Header ENTry SIZE)。每个表项的大小相同。
e_phnum¶
这一项给出程序头部表的项数( Program Header entry NUMber )。因此,e_phnum
与 e_phentsize
的乘积即为程序头部表的字节长度。如果文件中没有程序头部表,则该项值为0。
e_shentsize¶
这一项给出节头的字节长度(Section Header ENTry SIZE)。一个节头是节头表中的一项;节头表中所有项占据的空间大小相同。
e_shnum¶
这一项给出节头表中的项数(Section Header NUMber)。因此, e_shnum
与 e_shentsize
的乘积即为节头表的字节长度。如果文件中没有节头表,则该项值为0。
e_shstrndx¶
这一项给出节头表中与节名字符串表相关的表项的索引值(Section Header table InDeX related with section name STRing table)。如果文件中没有节名字符串表,则该项值为SHN_UNDEF
。关于细节的介绍,请参考后面的“节”和“字符串表”部分。
Program Header Table
概述:是一个结构体数组,每一个元素的类型是Elf32_Phdr,描述了一个段或者其它系统正在准备程序执行时所需要的信息。其中,ELF头中的e_phentsize和e_phnum指定了该数组每个元素的大小及元素个数
程序的头部只有对于可执行文件和共享目标文件有意义
Elf32_Phdr的数据结构如下:
typedef struct {
ELF32_Word p_type;
ELF32_Off p_offset;
ELF32_Addr p_vaddr;
ELF32_Addr p_paddr;
ELF32_Word p_filesz;
ELF32_Word p_memsz;
ELF32_Word p_flags;
ELF32_Word p_align;
} Elf32_Phdr;
每个字段的说明如下:
字段 | 说明 |
---|---|
p_type | 该字段为段的类型,或者表明了该结构的相关信息。 |
p_offset | 该字段给出了从文件开始到该段开头的第一个字节的偏移。 |
p_vaddr | 该字段给出了该段第一个字节在内存中的虚拟地址。 |
p_paddr | 该字段仅用于物理地址寻址相关的系统中, 由于“System V”忽略了应用程序的物理寻址,可执行文件和共享目标文件的该项内容并未被限定。 |
p_filesz | 该字段给出了文件镜像中该段的大小,可能为0。 |
p_memsz | 该字段给出了内存镜像中该段的大小,可能为0。 |
p_flags | 该字段给出了与段相关的标记。 |
p_align | 可加载的程序的段的 p_vaddr 以及 p_offset 的大小必须是 page 的整数倍。该成员给出了段在文件以及内存中的对齐方式。如果该值为 0 或 1 的话,表示不需要对齐。除此之外,p_align 应该是 2 的整数指数次方,并且 p_vaddr 与 p_offset 在模 p_align 的意义下,应该相等。 |
可执行文件中的段类型如下:
PT_NULL | 0 | 表明段未使用,其结构中其他成员都是未定义的。 |
PT_LOAD | 1 | 此类型段为一个可加载的段,大小由 p_filesz 和 p_memsz 描述。文件中的字节被映射到相应内存段开始处。如果 p_memsz 大于 p_filesz,“剩余”的字节都要被置为0。p_filesz 不能大于 p_memsz。可加载的段在程序头部中按照 p_vaddr 的升序排列。 |
PT_DYNAMIC | 2 | 此类型段给出动态链接信息。 |
PT_INTERP | 3 | 此类型段给出了一个以 NULL 结尾的字符串的位置和长度,该字符串将被当作解释器调用。这种段类型仅对可执行文件有意义(也可能出现在共享目标文件中)。此外,这种段在一个文件中最多出现一次。而且这种类型的段存在的话,它必须在所有可加载段项的前面。 |
PT_NOTE | 4 | 此类型段给出附加信息的位置和大小。 |
PT_SHLIB | 5 | 该段类型被保留,不过语义未指定。而且,包含这种类型的段的程序不符合ABI标准。 |
PT_PHDR | 6 | 该段类型的数组元素如果存在的话,则给出了程序头部表自身的大小和位置,既包括在文件中也包括在内存中的信息。此类型的段在文件中最多出现一次。此外,只有程序头部表是程序的内存映像的一部分时,它才会出现。如果此类型段存在,则必须在所有可加载段项目的前面。 |
PT_LOPROC~PT_HIPROC | 0x70000000 ~0x7fffffff | 此范围的类型保留给处理器专用语义。 |
基地址-Base Address
程序头部的虚拟地址可能并不是程序内存镜像中实际的虚拟地址。通常来说,可执行程序都会包含绝对地址的代码。为了使得程序可以征正常运行,段必须在相应的虚拟地址处。另一方面,共享目标文件通常来说,包含与地址无关的代码。这可以使得共享目标文件可以被多个进程加载,同时,保持程序执行的正确性。尽管系统会为不同的进程选择不同的虚拟地址,但是它仍然保留段的相对地址,因为地址无关代码使用段之间的相对地址来进行寻址,内存中的虚拟地址之间的差必须与文件中的虚拟地址之间的差相匹配。内存中任何段的虚拟地址与文件中对应的虚拟地址之间的差值对于任何一个可执行文件或共享对象来说是一个单一常量值。这个差值就是基地址,基地址的一个用途就是在动态连接期间重新定位程序。
可执行文件或共享目标文件的基地址是在执行过程中由一下三个数值计算的。
- 虚拟内存加载地址
- 最大页面大小
- 程序加载段的最低虚拟地址
要计算基地址,首先要确定可加载段指中p_vaddr最小的内存虚拟地址,之后把该内存虚拟地址缩小为与之最近的最大页面的整数倍即是基地址。
根据要加载到内存中的文件的类型,内存地址可能与p_vadde相同也可能不相同
段权限p_flags
被系统加载到内存中的程序至少有一个可加载的段。当系统为可加载段创建内存镜像时,会按照p_flag将段设置为对应的权限。
其中,所有在PF_MASKPROC中的比特位都是被保留用于与处理器相关的语义信息。如果一个权限位被设置为0,这种类型的段是不可访问的。实际的内存权限取决于相应的内存管理单元,不同的系统可能操作方式不一样。尽管所有权限组合都是可以的,但是系统一般会授予比请求更多的权限。在任何情况下,除非明确说明,一个段不会有写权限。
段内容:
一个段可能包括一个到多个节区,但是这并不会影响程序的加载。尽管如此,我们也必须需要各种各样的数据来使得程序可以执行以及动态链接等。
对于不同的段来说,它的顺序以及所包含的节的个数有所不同。
如下代码段只包含只读的指令以及数据。
Text Segment
数据段包含可写的数据以及指令,通常来说,包含以下内容
程序头部的PT_DYNAMIC类型的元素指向.dynamic节。其中,got表和plt表包含与地址无关的代码相关信息。
.bss节的类型为SHT_NOBITS,这表明它在ELF文件中不占用空间,但是它却占用可执行文件的内存镜像的空间。通常情况下,没有被初始化的数据在段的尾部,因此,p_memsz才会比p_files大
注意:不同的段可能会有所重合,即不同的段包含相同的节
Section Header Table
其实这个数据结构是在ELF文件的 尾部,该表用于定位ELF文件中的每个节区的具体位置。
首先,ELF头中的e_shoff项给出了从文件开头到节头表位置的字节偏移。e_shnum告诉了我们节头表包含的项数;e_shentsize给出了每一项的字节大小。
其次,节头表是一个数组,每个数组的元素的类型是ELF32_Shdr,每一个元素都描述了一个节区的概要内容。
ELF32_Shdr
每个节区头部可以用下面的数据结构进行描述:
typedef struct {
ELF32_Word sh_name;
ELF32_Word sh_type;
ELF32_Word sh_flags;
ELF32_Addr sh_addr;
ELF32_Off sh_offset;
ELF32_Word sh_size;
ELF32_Word sh_link;
ELF32_Word sh_info;
ELF32_Word sh_addralign;
ELF32_Word sh_entsize;
} Elf32_Shdr;
每个字段的含义如下
sh_name | 节名称,是节区头字符串表节区中(Section Header String Table Section)的索引,因此该字段实际是一个数值。在字符串表中的具体内容是以 NULL 结尾的字符串。 |
sh_type | 根据节的内容和语义进行分类,具体的类型下面会介绍。 |
sh_flags | 每一比特代表不同的标志,描述节是否可写,可执行,需要分配内存等属性。 |
sh_addr | 如果节区将出现在进程的内存映像中,此成员给出节区的第一个字节应该在进程镜像中的位置。否则,此字段为 0。 |
sh_offset | 给出节区的第一个字节与文件开始处之间的偏移。SHT_NOBITS 类型的节区不占用文件的空间,因此其 sh_offset 成员给出的是概念性的偏移。 |
sh_size | 此成员给出节区的字节大小。除非节区的类型是 SHT_NOBITS ,否则该节占用文件中的 sh_size 字节。类型为SHT_NOBITS 的节区长度可能非零,不过却不占用文件中的空间。 |
sh_link | 此成员给出节区头部表索引链接,其具体的解释依赖于节区类型。 |
sh_info | 此成员给出附加信息,其解释依赖于节区类型。 |
sh_addralign | 某些节区的地址需要对齐。例如,如果一个节区有一个 doubleword 类型的变量,那么系统必须保证整个节区按双字对齐。也就是说,sh\_addr \% sh\_addralign=0。目前它仅允许为 0,以及 2 的正整数幂数。 0 和 1 表示没有对齐约束。 |
sh_entsize | 某些节区中存在具有固定大小的表项的表,如符号表。对于这类节区,该成员给出每个表项的字节大小。反之,此成员取值为0。 |
索引为0的节区头也存在,此索引标记的是未定义的节区引用。这一项
sh_name | 0 | 无名称 |
sh_type | SHT_NULL | 限制 |
sh_flags | 0 | 无标志 |
sh_addr | 0 | 无地址 |
sh_offset | 0 | 无文件偏移 |
sh_size | 0 | 无大小 |
sh_link | SHN_UNDEF | 无链接信息 |
sh_info | 0 | 无辅助信息 |
sh_addralign | 0 | 无对齐要求 |
sh_entsize | 0 | 无表项 |
特殊下标:
节头表中比较特殊的几下标如下
SHN_UNDEF | 0 | 标志未定义的,丢失的,不相关的或者其它没有意义的节引用。例如,与节号SHN_UNDEF相关的“定义”的符号就是一个未定义符号。注:虽然0号索引被保留用于未定义值,节头表仍然包含索引0的项。也就是说,如果ELF头的e_shnum为6,那么索引应该为0~5。更加详细的内容在后面会说明。 |
SHN_LORESERVE | 0xff00 | 保留索引值范围的下界。 |
SHN_LOPROC | 0xff00 | 处理器相关的下界 |
SHN_HIPROC | 0xff1f | 处理器相关的上界 |
SHN_ABS | 0xfff1 | 相关引用的绝对值。例如与节号SHN_ABS相关的符号拥有绝对值,它们不受重定位的影响 |
SHN_COMMON | 0xfff2 | 这一节区相定义的符号是通用符号,例如FORTRAN COMMON,C语言中未分配的外部变量。 |
SHN_HIRESERVE | 0xffff | 保留索引值范围的上界。 |
系统保留在SHN_LORESERVE到SHN_HIRESERVE之间(包含边界)的索引值,这些值不在节头表中引用,也就是说,节头表不包含保留索引项。
节类型目前有下列可选范围,其中 SHT 是Section Header Table 的简写。
SHT_NULL | 0 | 该类型节区是非活动的,这种类型的节头中的其它成员取值无意义。 |
SHT_PROGBITS | 1 | 该类型节区包含程序定义的信息,它的格式和含义都由程序来决定。 |
SHT_SYMTAB | 2 | 该类型节区包含一个符号表(SYMbol TABle)。目前目标文件对每种类型的节区都只 能包含一个,不过这个限制将来可能发生变化。 一般,SHT_SYMTAB 节区提供用于链接编辑(指 ld 而言) 的符号,尽管也可用来实现动态链接。 |
SHT_STRTAB | 3 | 该类型节区包含字符串表( STRing TABle )。 |
SHT_RELA | 4 | 该类型节区包含显式指定位数的重定位项( RELocation entry with Addends ),例如,32 位目标文件中的 Elf32_Rela 类型。此外,目标文件可能拥有多个重定位节区。 |
SHT_HASH | 5 | 该类型节区包含符号哈希表( HASH table )。 |
SHT_DYNAMIC | 6 | 该类型节区包含动态链接的信息( DYNAMIC linking )。 |
SHT_NOTE | 7 | 该类型节区包含以某种方式标记文件的信息(NOTE)。 |
SHT_NOBITS | 8 | 该类型节区不占用文件的空间,其它方面和SHT_PROGBITS相似。尽管该类型节区不包含任何字节,其对应的节头成员sh_offset 中还是会包含概念性的文件偏移。 |
SHT_REL | 9 | 该类型节区包含重定位表项(RELocation entry without Addends),不过并没有指定位数。例如,32位目标文件中的 Elf32_rel 类型。目标文件中可以拥有多个重定位节区。 |
SHT_SHLIB | 10 | 该类型此节区被保留,不过其语义尚未被定义。 |
SHT_DYNSYM | 11 | 作为一个完整的符号表,它可能包含很多对动态链接而言不必 要的符号。因此,目标文件也可以包含一个 SHT_DYNSYM 节区,其中保存动态链接符号的一个最小集合,以节省空间。 |
SHT_LOPROC | 0X70000000 | 此值指定保留给处理器专用语义的下界( LOw PROCessor-specific semantics )。 |
SHT_HIPROC | OX7FFFFFFF | 此值指定保留给处理器专用语义的上界( HIgh PROCessor-specific semantics )。 |
SHT_LOUSER | 0X80000000 | 此值指定保留给应用程序的索引下界。 |
SHT_HIUSER | 0X8FFFFFFF | 此值指定保留给应用程序的索引上界。 |
sh_flags
节头中sh_flags字段的每一个比特位都可以给出相应的标记信息,其定义了对应的节区的内容是否可以被修改,被执行等信息。
SHF_WRITE | 0x1 | 这种节包含了进程运行过程中可以被写的数据。 |
SHF_ALLOC | 0x2 | 这种节在进程运行时占用内存。对于不占用目标文件的内存镜像空间的某些控制节,该属性处于关闭状态(off)。 |
SHF_EXECINSTR | 0x4 | 这种节包含可执行的机器指令(EXECutable INSTRuction)。 |
SHF_MASKPROC | 0xf0000000 | 所有在这个掩码中的比特位用于特定处理器语义。 |
当节区类型不同的时候,sh_link和sh_info也会具有不同的含义。
SHT_DYNAMIC | 节区中使用的字符串表的节头索引 | 0 |
SHT_HASH | 此哈希表所使用的符号表的节头索引 | 0 |
SHT_REL/SHT_RELA | 与符号表相关的节头索引 | 重定位应用到的节的节头索引 |
SHT_SYMTAB/SHT_DYNSYM | 操作系统特定信息,Linux 中的 ELF 文件中该项指向符号表中符号所对应的字符串节区在 Section Header Table 中的偏移。 | 操作系统特定信息 |
other | SHN_UNDEF |
0 |
Section
节区包含目标文件中除了ELF头部,程序头部表,节区头部表的所有信息。
- 每个节区都有对应的节头来描述它。但是反过来,节区头部并不一定会对应着一个节区。
- 每个节区在目标文件中是连续的,但是大小可能为0
- 任意两个节区不能重叠,即一个字节不能同时存在于两个节区中。
- 目标文件中可能会有闲置空间,各种头和节不一定会覆盖到目标文件中的所有字节,闲置区域的内容未指定。
许多在ELF文件中的节都是预定义的,它们包含程序和控制信息。这些节被操作系统使用,但是对于不同操作系统同一节区可能会有不同类型以及属性。
可执行文件是由链接器将一些单独的目标文件以及库文件链接起来而得到的,其中,链接器会解析引用(不同文件中的子例程的引用以及数据的引用,调整对象文件中的绝对引用)并且重定位指令。加载与链接过程需要目标文件中的信息,并且会将处理后的信息存储在一些特定的节区中。比如.dynamic
每一种操作系统都会支持依序链接模型,但这些模型都大致可以分为两种
静态链接 | 静态链接的文件中所使用的库文件或者第三方库都被静态绑定了,其引用已经被解析了。 |
动态链接 | 动态链接的文件中所使用的库文件或者第三方库只是单纯地被链接到可执行文件中。当可执行文件执行时使用到相应函数时,相应的函数地址才会被解析。 |
有一些特殊的节可以支持调试,比如说.debug,.line节;支持程序控制的节有
.bss,.data,.data1,/rodata,.rodata1
.comment | SHT_PROGBITS | 包含版本控制信息。 | |
.debug | SHT_PROGBITS | 此节区包含用于符号调试的信息。 | |
.dynamic | SHT_DYNAMIC | SHF_ALLOC SHF_WRITE | 此节区包含动态链接信息。SHF_WRITE 位设置与否是否被设置取决于具体的处理器。 |
.dynstr | SHT_STRTAB | SHF_ALLOC | 此节区包含用于动态链接的字符串,大多数 情况下这些字符串代表了与符号表项相关的名称。 |
.dynsym | SHT_DYNSYM | SHF_ALLOC | 此节区包含动态链接符号表。 |
.got | SHT_PROGBITS | 此节区包含全局偏移表。 | |
.line | SHT_PROGBITS | 此节区包含符号调试的行号信息,描述了源程序与机器指令之间的对应关系,其内容是未定义的。 | |
.plt | SHT_PROGBITS | 此节区包含过程链接表(procedure linkage table)。 | |
.relname | SHT_REL | 这些节区中包含重定位信息。如果文件中包含可加载的段,段中有重定位内容,节区的属性将包含 SHF_ALLOC 位,否则该位置 0。传统上 name 根据重定位所适用的节区给定。例如 .text 节区的重定位节区名字将是:.rel.text 或者 .rela.text。 | |
.relaname | SHT_RELA | ||
.shstrtab | SHT_STRTAB | 此节区包含节区名称。 |
注意:
- 以“."开头的节区名称是系统保留的,当然应用程序也可以使用这些节区。为了避免与系统节区冲突,应用程序应该尽量使用没有前缀的节区名称
- 目标文件格式允许定义不在上述列表中的节区,可以包含多个名字相同的节区。
- 保留给处理器体系结构的节区名称一般命名规则为:处理器体系结构名称简写+节区名称
Code Section
概述:
在动态链接器创建了进程镜像,并且执行了重定位后,每一个共享目标文件都有机会去执行一些初始化的代码。所有的共享目标文件会在可执行文件获取权限之前进行初始化。
在调用目标文件A的初始化之前,会首先调用所有A依赖的共享目标文件的初始化代码。
比如说,如果目标文件A依赖于另外一个目标文件B,那么B就会在A的依赖列表中,这会被记录在动态结构的DT_NEEDED中,循环依赖的初始化是未被定义的。
目标文件的初始化通过递归每一个被依赖的表项来完成。只有当一个目标文件依赖的所有目标文件都处理完自己的依赖之后,这个目标文件才会执行初始化代码。
共享目标文件也会有结束的函数,这些函数在进程完成自己自己的终止序列时通过atexit机制来执行。动态链接器调用终止函数的顺序恰好与上面的初始化序列相反。
动态链接器将会确保它只会执行初始化或者终止函数最多一次。
共享目标文件通过动态结构中的DT_INT和DT_FINI来指定它们的初始化以及结束函数。在一般情况下,这些函数在.init节与.fini节中。
注意:尽管atexit终止处理函数通常来说会被执行,但它并不会保证在程序消亡时被执行。更特殊的是,如果程序调用了_exit函数或者进程由于接受到一个信号后消亡了,那么它将不会执行对应的函数。
动态链接器并不负责调用可执行文件的.init 节或者利用atexit注册可执行文件的.fini节。由用户通过atexit机制指定的种植函数必须在所有共享目标文件的结束函数前执行。
.Symtab:Symbol Table
概述
每个目标文件都会有一个符号表,熟悉编译原理的就会知道,在编译程序时,必须有相应的结构来管理程序中的符号以便于对函数和变量进行重定位。
此外,链接本质就是把多个不同的目标文件相互“粘”在一起。实际上,目标文件相互粘合是目标文件之间对地址的引用,即函数和变量的地址的相互引用。而在粘合的过程中,符号就是其中的粘合剂。
目标文件的符号表包含了一些通用的符号,这部分信息在进行了strip操作后就会消失。这些符号信息可能包括变量名、函数名
符号表可以被视为一个数组,数组中的每一个元素都是一个结构体,具体如下:
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;
每个字段的含义如下:
st_name | 符号在字符串表中对应的索引。如果该值非 0,则它表示了给出符号名的字符串表索引,否则符号表项没有名称。 注:外部 C 符号在 C 语言和目标文件的符号表中具有相同的名称。 |
st_value | 给出与符号相关联的数值,具体取值依赖于上下文,可能是一个正常的数值、一个地址等等。 |
st_size | 给出对应符号所占用的大小。如果符号没有大小或者大小未知,则此成员为 0。 |
st_info | 给出符号的类型和绑定属性。之后会给出若干取值和含义的绑定关系。 |
st_other | 目前为 0,其含义没有被定义。 |
st_shndx | 如果符号定义在该文件中,那么该成员为符号所在节在节区头部表中的下标;如果符号不在本目标文件中,或者对于某些特殊的符号,该成员具有一些特殊含义。 |
其中,符号表中下标存储了符号表的元素,同时这个元素也相对比较特殊,作为所有未定义的所有