diff --git a/study/kernel/01-process/03-execute/01-elf/README.md b/study/kernel/01-process/03-execute/01-elf/README.md index 4c86dd4..3bcada0 100644 --- a/study/kernel/01-process/03-execute/01-elf/README.md +++ b/study/kernel/01-process/03-execute/01-elf/README.md @@ -1,4 +1,4 @@ -Linux进程ELF文件格式 +Linu进程ELF文件格式 ======= @@ -500,25 +500,231 @@ sh_link和sh_info字段的具体含义依赖于sh_type的值 | .data | SHT_PROGBITS | SHF_ALLOC + SHF_WRITE | 这些节区包含初始化了的数据,将出现在程序的内存映像中 | | .data1 | SHT_PROGBITS | SHF_ALLOC + SHF_WRITE | 这些节区包含初始化了的数据,将出现在程序的内存映像中 | | .debug | SHT_PROGBITS | (无) | 此节区包含用于符号调试的信息 | -| .dynamic | SHT_DYNAMIC | 此节区包含动态链接信息。节区的属性将包含 SHF_ALLOC 位。是否 SHF_WRITE 位被设置取决于处理器 | +| .dynamic | SHT_DYNAMIC | | 此节区包含动态链接信息。节区的属性将包含 SHF_ALLOC 位。是否 SHF_WRITE 位被设置取决于处理器 | | .dynstr | SHT_STRTAB | SHF_ALLOC | 此节区包含用于动态链接的字符串,大多数情况下这些字符串代表了与符号表项相关的名称 | | .dynsym | SHT_DYNSYM | SHF_ALLOC | 此节区包含了动态链接符号表 | | .fini | SHT_PROGBITS | SHF_ALLOC + SHF_EXECINSTR | 此节区包含了可执行的指令,是进程终止代码的一部分。程序正常退出时,系统将安排执行这里的代码 | -| .got | SHT_PROGBITS | 此节区包含全局偏移表 | +| .got | SHT_PROGBITS | | 此节区包含全局偏移表 | | .hash | SHT_HASH | SHF_ALLOC | 此节区包含了一个符号哈希表 | | .init | SHT_PROGBITS | SHF_ALLOC +SHF_EXECINSTR | 此节区包含了可执行指令,是进程初始化代码的一部分。当程序开始执行时,系统要在开始调用主程序入口之前(通常指 C 语言的 main 函数)执行这些代码 | -| .interp | SHT_PROGBITS | 此节区包含程序解释器的路径名。如果程序包含一个可加载的段,段中包含此节区,那么节区的属性将包含 SHF_ALLOC 位,否则该位为 0 | +| .interp | SHT_PROGBITS | | 此节区包含程序解释器的路径名。如果程序包含一个可加载的段,段中包含此节区,那么节区的属性将包含 SHF_ALLOC 位,否则该位为 0 | | .line | SHT_PROGBITS | (无) | 此节区包含符号调试的行号信息,其中描述了源程序与机器指令之间的对应关系。其内容是未定义的 | | .note | SHT_NOTE | (无) | 此节区中包含注释信息,有独立的格式。 -| .plt | SHT_PROGBITS | 此节区包含过程链接表(procedure linkage table)。 -| .relname -| .relaname SHT_REL -SHT_RELA 这些节区中包含了重定位信息。如果文件中包含可加载的段,段中有重定位内容,节区的属性将包含 SHF_ALLOC 位,否则该位置 0。传统上 name 根据重定位所适用的节区给定。例如 .text 节区的重定位节区名字将是:.rel.text 或者 .rela.text。 -| .rodata -| .rodata1 SHT_PROGBITS SHF_ALLOC 这些节区包含只读数据,这些数据通常参与进程映像的不可写段。 -.shstrtab SHT_STRTAB 此节区包含节区名称。 -.strtab SHT_STRTAB 此节区包含字符串,通常是代表与符号表项相关的名称。如果文件拥有一个可加载的段,段中包含符号串表,节区的属性将包含SHF_ALLOC 位,否则该位为 0。 -.symtab SHT_SYMTAB 此节区包含一个符号表。如果文件中包含一个可加载的段,并且该段中包含符号表,那么节区的属性中包含SHF_ALLOC 位,否则该位置为 0。 -.text SHT_PROGBITS SHF_ALLOC + -SHF_EXECINSTR 此节区包含程序的可执行指令 +| .plt | SHT_PROGBITS | |此节区包含过程链接表(procedure linkage table)| +| .relname
.relaname | SHT_REL
SHT_RELA | |这些节区中包含了重定位信息。如果文件中包含可加载的段,段中有重定位内容,节区的属性将包含 SHF_ALLOC 位,否则该位置 0。传统上 name 根据重定位所适用的节区给定。例如 .text 节区的重定位节区名字将是:.rel.text 或者 .rela.text | +| .rodata
.rodata1| SHT_PROGBITS | SHF_ALLOC | 这些节区包含只读数据,这些数据通常参与进程映像的不可写段 | +| .shstrtab | SHT_STRTAB | | 此节区包含节区名称 | +| .strtab | SHT_STRTAB | |此节区包含字符串,通常是代表与符号表项相关的名称。如果文件拥有一个可加载的段,段中包含符号串表,节区的属性将包含SHF_ALLOC 位,否则该位为 0 | +| .symtab | SHT_SYMTAB | |此节区包含一个符号表。如果文件中包含一个可加载的段,并且该段中包含符号表,那么节区的属性中包含SHF_ALLOC 位,否则该位置为 0 | +| .text | SHT_PROGBITS | SHF_ALLOC + SHF_EXECINSTR | 此节区包含程序的可执行指令 | +##字符串表(节区) +------- + +首先要知道,字符串表它本身就是一个节区,从第二章描述中可知,每一个节区都存在一个节区头部表项与之对应,所以字符串表这个节区也存在一个节区头部表项对应,而在elf文件头部结构中存在一个成员e_shstrndx给出这个节区头部表项的索引位置。因此可以通过 +```c +shstrab = (rt_uint8_t *)module_ptr +shdr[elf_module->e_shstrndx].sh_offset; +``` +来得到字符串表的起始位置。 +字符串表节区包含以 NULL(ASCII 码 0)结尾的字符序列,通常称为字符串。ELF目标文件通常使用字符串来表示符号和节区名称。对字符串的引用通常以字符串在字符 +串表中的下标给出。 + +一般,第一个字节(索引为 0)定义为一个空字符串。类似的,字符串表的最后一个字节也定义为 NULL,以确保所有的字符串都以 NULL 结尾。索引为 0 的字符串在 +不同的上下文中可以表示无名或者名字为 NULL 的字符串。 + +允许存在空的字符串表节区,其节区头部的 sh_size 成员应该为 0。对空的字符串表而言,非 0 的索引值是非法的。 + +例如:对于各个节区而言,节区头部的 sh_name 成员包含其对应的节区头部字符串表节区的索引,此节区由 ELF 头的 e_shstrndx 成员给出。下图给出了包含 25 个字节的一个字符串表,以及与不同索引相关的字符串。 + +![字符](./images/char.png) + +那么上面字符串表包含以下字符串: + +| 索引 | 字符串 | +| ------------- |:-------------:| +| 0 | (无) | +| 1 | name. | +| 7 | Variable | +| 11 | able | +| 16 | able | +| 24 | (空字符串) | + + + + + +##符号表(Symbol Table) +------- +首先,符号表同样本身是一节区,也存在一对应节区头部表项。 + +目标文件的符号表中包含用来定位、重定位程序中符号定义和引用的信息。 + +符号表索引是对此数组的索引。索引0表示表中的第一表项,同时也作为未定义符号的索引。 + +符号表是由一个个符号元素组成,用elfxx_sym来结构来表示, 定义在[include/uapi/linux/elf.h](http://lxr.free-electrons.com/source/include/uapi/linux/elf.h#L182), 同样32位为elf32_sym, 64位对应elf64_sym + +每个元素的数据结构如下定义: + +| 成员 | 类型 | 描述 | +| ------------- |:-------------:|:-------------:| +| st_name | Elf32_Word/Elf64_Word | 名称,索引到字符串表 | +| st_value | Elf32_AddrElf64_Addr | 给出相关联的符号的取值。依赖于具体的上下文 | +| st_size | Elf32_Word/Elf64_Word | 相关的尺寸大小 | +| st_info | unsigned char | 给出符号的类型和绑定属性 | +| st_other | unsigned char | 该成员当前包含 0,其含义没有定义 | +| st_shndx | Elf32_Half/Elf64_Half | 给出相关的节区头部表索引。某些索引具有特殊含义 | + +###st_info给出符号的类型和绑定属性 +------- + +st_info 中包含符号类型和绑定信息,操纵方式如: + +```c +#define ELF32_ST_BIND(i) ((i)>>4) +#define ELF32_ST_TYPE(i) ((i)&0xf) +#define ELF32_ST_INFO(b, t) (((b)<<4) + ((t)&0xf)) +``` + +**st_info 的高四位(ELF32_ST_BIND(i))** + +表示符号绑定,用于确定链接可见性和行为。具体的绑定类型如: + +| 名称 | 取值 | 说明 | +| ------------- |:-------------:|:-------------:| +| STB_LOCAL | 0 | 局部符号在包含该符号定义的目标文件以外不可见。相同名称的局部符号可以存在于多个文件中,互不影响 | +| STB_GLOBAL | 1 | 全局符号对所有将组合的目标文件都是可见的。一个文件中对某个全局符号的定义将满足另一个文件对相同全局符号的未定义引用 | +| STB_WEAK | 2 | 弱符号与全局符号类似,不过他们的定义优先级比较低 | +| STB_LOPROC | 13 | 处于这个范围的取值是保留给处理器专用语义的 | +| STB_HIPROC | 15 | 处于这个范围的取值是保留给处理器专用语义的 | + +全局符号与弱符号之间的区别主要有两点: + +(1) 当 链 接 编 辑 器 组 合 若 干 可 重 定 位 的 目 标 文 件 时 , 不 允 许 对 同 名 的STB_GLOBAL 符号给出多个定义。另一方面如果一个已定义的全局符号已经存在,出现一个同名的弱符号并不会产生错误。链接编辑器尽关心全局符号,忽略弱符号。类似地,如果一个公共符号(符号的 st_shndx 中包含 SHN_COMMON),那么具有相同名称的弱符号出现也不会导致错误。链接编辑器会采纳公共定义,而忽略弱定义。 +(2). 当链接编辑器搜索归档库(archive libraries)时,会提取那些包含未定义全局符号的档案成员。成员的定义可以是全局符号,也可以是弱符号。连接编辑器不会提取档案成员来满足未定义的弱符号。未能解析的弱符号取值为 0。 + +在每个符号表中,所有具有 STB_LOCAL 绑定的符号都优先于弱符号和全局符号。符号表节区中的 sh_info 头部成员包含第一个非局部符号的符号表索引。 + + +**st_info的低四位ELF32_ST_TYPE(i)** + +定义如下 + +| 名称 | 取值 | 说明 | +| ------------- |:-------------:|:-------------:| +| STT_NOTYPE | 0 | 符号的类型没有指定 | +| STT_OBJECT | 1 | 符号与某个数据对象相关,比如一个变量、数组等等 | +| STT_FUNC | 2 | 符号与某个函数或者其他可执行代码相关 | +| STT_SECTION| 3 | 符号与某个节区相关。这种类型的符号表项主要用于重定位,通常具有 STB_LOCAL 绑定 | +| STT_FILE | 4 | 传统上,符号的名称给出了与目标文件相关的源文件的名称。文件符号具有 STB_LOCAL 绑定,其节区索引是SHN_ABS,并且它优先于文件的其他 STB_LOCAL 符号(如果有的话) | +| STT_LOPROC~STT_HIPROC | 13~15 | 此范围的符号类型值保留给处理器专用语义用途 | + +在共享目标文件中的函数符号(类型为 STT_FUNC)具有特别的重要性。当其他目标文件引用了来自某个共享目标中的函数时,链接编辑器自动为所引用的符号创建过 +程链接表项。类型不是 STT_FUNC 的共享目标符号不会自动通过过程链接表进行引用。 + +如果一个符号的取值引用了某个节区中的特定位置,那么它的节区索引成员(st_shndx)包含了其在节区头部表中的索引。当节区在重定位过程中被移动时,符号的取值也会随之变化,对符号的引用始终会“指向”程序中的相同位置。 + + +###st_shndx +------- + +如前面所述,st_shndx给出相关的节区头部表索引。但其值也存在一些特殊值,具有某些特殊的含义: + +| 名称 | 取值 | 说明 | +| ------------- |:-------------:|:-------------:| +| SHN_ABS | | 符号具有绝对取值,不会因为重定位而发生变化 | +| SHN_COMMON | | 符号标注了一个尚未分配的公共块。符号的取值给出了对齐约束,与节区的 sh_addralign成员类似。就是说,链接编辑器将为符号分配存储空间,地址位于 st_value 的倍数处。符号的大小给出了所需要的字节数 | +| SHN_UNDEF | | 此节区表索引值意味着符号没有定义。当链接编辑器将此目标文件与其他定义了该符号的目标文件进行组合时,此文件中对该符号的引用将被链接到实际定义的位置 | + +###st_value +------- + +不同的目标文件类型中符号表项对 st_value 成员具有不同的解释: + +1. 在可重定位文件中,st_value 中遵从了节区索引为 SHN_COMMON 的符号的对齐约束。 + +2. 在可重定位的文件中,st_value 中包含已定义符号的节区偏移。就是说,st_value 是从 st_shndx 所标识的节区头部开始计算,到符号位置的偏移。 + +3. 在可执行和共享目标文件中,st_value 包含一个虚地址。为了使得这些文件的符号对动态链接器更有用,节区偏移(针对文 件的解释)让位于虚拟地址(针对内存的解释),因为这时与节区号无关。 + +尽管符号表取值在不同的目标文件中具有相似的含义,适当的程序可以采取高效的数据访问方式。 + + +##重定位信息 +------- + +重定位是将符号引用与符号定义进行连接的过程。例如,当程序调用了一个函数时,相关的调用指令必须把控制传输到适当的目标执行地址。 + +###重定位表项 +------- + +可重定位文件必须包含如何修改其节区内容的信息,从而允许可执行文件和共享目标文件保存进程的程序映像的正确信息。重定位表项就是这样一些数据。 + +可重定位表项的数据结构如下定义: + +**Elf32_Rel** + +| 成员 | 类型 | 描述 | +| ------------- |:-------------:|:-------------:| +| r_offset | Elf32_Addr/Elf64_Addr | 给出了重定位动作所适用的位置 | +| r_info | Elf32_Word/Elf64_Word | 给出要进行重定位的符号表索引,以及将实施的重定位类型 | + +**Elf32_Rela** + +| 成员 | 类型 | 描述 | +| ------------- |:-------------:|:-------------:| +| r_offset | Elf32_Addr/Elf64_Addr | 给出了重定位动作所适用的位置 | +| r_info | Elf32_Word/Elf64_Word | 给出要进行重定位的符号表索引,以及将实施的重定位类型 | +| r_addend | Elf32_Word | 给出一个常量补齐,用来计算将被填充到可重定位字段的数值 | + + +重定位节区会引用两个其它节区:符号表、要修改的节区。节区头部的 sh_info 和sh_link 成员给出这些关系。不同目标文件的重定位表项对 r_offset 成员具有略微不同的解释。 +r_info通常分为高8位和低8位,分别表示不同的含义: + +```c +#define ELF32_R_SYM(i) ((i)>>8) +#define ELF32_R_TYPE(i) ((unsigned char)(i)) +#define ELF32_R_INFO(s, t) (((s)<<8) + (unsigned char)(t)) +``` +高8位用作要进行重定位的符号表索引,通过它可以得出一个符号表项,而低8位表示将实施的重定位类型,它是和处理器相关的。 + +###ELF32_R_TYPE(i) +------- + +重定位表项描述如何修改后面的指令和数据字段。一般,共享目标文件在创建时,其基本虚拟地址是 0,不过执行地址将随着动态加载而发生变化。 + +#程序头部Elf32_phdr +------- + +以上说了那么多的有关节区的相关内容,那么现在来讨论一个程序头部表。其实节区头部表是以elf资源的角度来看待elf文件的,即这个elf文件到底存在哪些资源,以及这些资源之间的关联关系,而程序头部表,则以程序运行来看elf文件的,即要运行这个elf文件,需要将哪些东西载入到内存镜像。 + +程序头部是一个表,它的起始地址在elf头部结构中的e_phoff成员指定,数量由e_phnum表示,每个程序头部表项的大小由e_phentsize指出。 + +可执行文件或者共享目标文件的程序头部是一个结构数组,每个结构描述了一个段或者系统准备程序执行所必需的其它信息。目标文件的“段”包含一个或者多个“节区”,也就是“段内容(Segment Contents)”。程序头部仅对于可执行文件和共享目标文件有意义。 + + + +下面来看程序头号部表项的数据结构: + +| 成员 | 类型 | 描述 | +| ------------- |:-------------:|:-------------:| +| p_type | Elf32_Word/Elf64_Word | 段类型 | +| p_offset | Elf32_Off/Elf64_Off | 段位置 | +| p_vaddr | Elf32_Addr/Elf64_Addr | 给出段的第一个字节将被放到内存中的虚拟地址 | +| p_paddr | Elf32_Addr/Elf64_Addr | 仅用于与物理地址相关的系统中 | +| p_filesz | Elf32_Word/Elf64_Word | 给出段在文件映像中所占的字节数 | +| p_memsz | Elf32_Word/Elf64_Word | 给出段在内存映像中占用的字节数 | +| p_flags | Elf32_Word/Elf64_Word | 与段相关的标志 | +| p_align | Elf32_Word/Elf64_Word | 对齐 | + +##段类型p_type +------- + +| 名称 | 取值 | 说明 | +| ------------- |:-------------:|:-------------:| +| PT_NULL | 0 | 此数组元素未用。结构中其他成员都是未定义的 | +| PT_DYNAMIC | 2 | 数组元素给出动态链接信息 | +| PT_INTERP | 3 | 数组元素给出一个 NULL 结尾的字符串的位置和长度,该字符串将被当作解释器调用。这种段类型仅对与可执行文件有意义(尽管也可能在共享目标文件上发生)。在一个文件中不能出现一次以上。如果存在这种类型的段,它必须在所有可加载段项目的前面 | +| PT_NOTE | 4 | 此数组元素给出附加信息的位置和大小 | +| PT_SHLIB | 5 | 此段类型被保留,不过语义未指定。包含这种类型的段的程序与 ABI不符 | +| PT_PHDR | 6 | 此类型的数组元素如果存在,则给出了程序头部表自身的大小和位置,既包括在文件中也包括在内存中的信息。此类型的段在文件中不能出现一次以上。并且只有程序头部表是程序的内存映像的一部分时才起作用。如果存在此类型段,则必须在所有可加载段项目的前面 | +|PT_LOPROC~PT_HIPROC | 0x70000000~0x7fffffff | 此范围的类型保留给处理器专用语义 | \ No newline at end of file diff --git a/study/kernel/01-process/03-execute/01-elf/images/char.png b/study/kernel/01-process/03-execute/01-elf/images/char.png new file mode 100644 index 0000000..ea26514 Binary files /dev/null and b/study/kernel/01-process/03-execute/01-elf/images/char.png differ