- 预处理、编译、汇编、链接
- 预处理:展开宏定义、处理条件预编译指令、处理”#include“、删除注释、保留”#pragma“编译器指令
- 编译:将预处理完的文件生成目标文件(.o或.obj文件),这个文件包含了编译阶段生成的汇编代码
- 汇编:将生成的汇编文件转为机器指令、
- 链接:多个目标文件和可能的库文件链接后得到可执行文件或者动态链接库文件。
-
编译器将源代码转变成汇编代码的过程:
- 词法分析:扫描器将源代码的字符序列分割成一系列记号。记号一般可以分为:关键字、标识符、字面量(数字、字符串等)、特殊符号(=、+等)
- 语法分析:语法分析器对记号进行分析产生语法树(即表达式)
- 语义分析:由语义分析器执行。在编译期间仅能分析静态语义,而动态语义需要在运行期才能确定。静态语义通常包括声明和类型的匹配、类型转换。
- 中间语义生成:源代码级优先器将语法树转换成中间代码
- 目标代码生成与优化:代码生成器将中间代码转换成目标机器代码,其过程与目标机器联系紧密。目标代码优化器将生成的目标代码进行优化
-
静态链接:
处理各个模块之间相互引用的部分,使模块间能够正确的衔接。链接过程主要包括了地址和空间分配、符号决议、重定位。
-
- 重定位:当编译器编译到一个本文件外的函数时,会先将该指令地址搁置,等到最后链接时由链接器对这些被搁置指令的地址进行修正,这个地址被修正的过程就叫做重定位,每个要被修正的地方则叫做重定位入口。
- 地址和空间分配:在程序编译生成多个目标文件后,链接器需要为整个可执行程序确定一个合适的地址空间布局,并给各个目标文件中的代码段、数据段等分配相应的虚拟地址范围。
- 符号决议:当不同的目标文件中存在对外部符号(如函数、全局变量等)的引用时,符号决议过程就是要确定这些符号的具体定义位置。比如,一个源文件中调用了另一个源文件中定义的函数,在编译为目标文件后,这个函数调用处就只是一个未确定的符号引用,符号决议阶段就要找到该函数实际定义所在的目标文件以及其在目标文件中的具体位置。
-
可执行文件格式:
在LINUX下,可执行文件、动态链接库、静态链接库均按照ELF格式存储。
-
目标文件:
源代码编译后但未进行链接的中间文件。其文件格式与可执行文件类似,仅有个别处结构不同。目标文件中除了编译后的机器指令代码、数据等内容还包括了链接时所需的一些信息比如符号表、调试信息、字符串等,这些信息内容以“节”或“段”的形式存储,机器指令被放在“代码段”中(“.code 或 .text”),而全局变量和局部静态变量则放在”数据段“中(" .data ")。
-
- 举例说明:
由上图可知:
- 机器代码保存在 .text 段
- 已经初始化的全局变量和局部变量保存在 .data 段
- 未初始化的全局变量和局部变量保存在 .bss 段。
- 指令和数据分开存放的好处:
- 防止程序的指令被有意或无意地改写:当程序被装载后,数据和指令分别被映射到两个虚存区域。因为数据区域对于进程来说是可读写的,而指令区域对于进程来说是只读的,所以这两个虚存区域的权限可以被分别设置成可读写和只读。
- 提高缓存命中率
- 内存共享方面:实现进程间程序共享、资源共享。
- 举例说明:
-
目标文件结构分析:使用binutils的工具objdump来查看文件内部结构。
-
打印目标文件基本信息:
分析:由上述可知,除了基本的代码段、数据段和BSS段以外,还有只读数据段(.rodate)、注释信息段(.comment)、堆栈提示段(.note.GNU-stack)。
- 列说明:
- Size,指段的大小,通常以字节为单位。
- VMA(Virtual Memory Address),指段在虚拟内存中的地址,是程序运行时该段所在的虚拟内存位置。
- LMA(Load Memory Address),指段在加载到内存时的地址,大多数情况下LMA 与 VMA 是相同的,但在某些特殊情况下,如带有重定位功能的程序,它们可能会不同。LMA 主要关注的是节在物理内存或加载时的地址。
- File off(File offset),指段在目标文件中的偏移量。
- Align(Alignment),指段的对齐方式,通常以 2 的幂次方表示。
- 属性说明:每个段的第二行说明了其属性,比如"CONTENTS"表示该段在文件中存在(指占有实际空间,所以BSS段无CONTENTS),"ALLOC"表示已分配内存,"LODA"表示可重定位,"READONLY"表示只读,"CODE"表示是代码段。
- 列说明:
-
代码段:
分析:第一列内容为偏移量,中间四列为十六进制内容,最右边一列为.text段的ASCII码。(ASCII码中的”.“表示无法用ASCII码表示的)
-
ELF结构中常见其他段:
-
自定义扩展段:GCC提供了一个扩展机制,使程序员可以指定变量所处的段以满足某些硬件的内存或I/O的地址布局从而实现某些特定功能。
分析:在全局变量或函数之前加上”_attribute_((section("name")))“属性就可以把相应的变量或函数放到以”name“为段名的段中。关于自定义的段,其段名不能使用”.“作为前缀以免和系统段相混淆。
-
-
ELF文件重要结构:
- ELF Header :位于 ELF 文件的开头,包含了关于 ELF 文件的基本信息,如文件类型(可执行文件、可重定位文件、共享库等)、目标机器架构(如 x86、ARM 等)、文件版本、入口点地址(对于可执行文件,指示程序开始执行的位置)、程序头表的偏移量和大小(如果存在)、节头表的偏移量和大小等。操作系统和相关工具通过读取 ELF 头来确定如何处理该文件。
- ELF魔数:0x7F 0x45 0x4C 0x46 标识这是一个ELF格式的文件。
- .text段:紧接在 ELF Header 之后,存放程序的可执行指令,通常被标记为只读。
- .data段:一般在.text 段之后,包含已初始化的全局变量和静态变量,在程序加载到内存时会被一同加载到内存中,供程序在运行时使用。
- .bss段:位于.data 段之后,存放未初始化的全局变量和静态变量。系统会在程序加载时为它们分配内存空间,并将其初始化为 0(对于数值类型)或空值(对于指针等类型)。
- 其他节:可能包含各种其他类型的节,具体取决于程序的需求和编译器的设置,比如:.rodata (只读数据段,存放常量字符串等只读数据)、.debug (包含调试信息,如变量名、函数名、行号等,用于调试工具在调试程序时提供详细的信息)、 .comment (可能包含编译器版本、编译选项等注释信息)。
- Section header table(节头表):在其他节之后,描述了 ELF 文件中各个节的信息,包括节的名称、类型、在文件中的偏移量、大小、虚拟内存地址、加载内存地址、对齐方式、标志等。链接器、加载器等工具通过节头表来定位和处理各个节。
Section Header 的结构实际上是个以”Elf32_Shdr“结构体为元素的数组,数组元素的个数等于段的个数,每个”Elf32_Shdr“结构体都对应一个段,其结构体成员如下:
-
关于段的属性取决于 sh_type(段的类型)和 sh_flags(段的标志位),如下图:
-
- String Table(字符串表):
- 与节头表等信息相关联,位置不固定。存储 ELF 文件中使用的各种字符串,如节名、符号名等。其他部分通过引用字符串表中的索引来使用这些字符串,节省空间并提高效率。
- 字符串表在ELF文件中以段的形式保存,常见的有.strtab(字符串表)和 .shstrtab(段表字符串),它将字符串集中起来存放到一个表,用字符串在表中的偏移来引用字符串。
- Sysbol Table(符号表):与字符串表等信息相关联,位置不固定。包含了程序中定义和引用的符号信息,如函数名、全局变量名、静态变量名等,以及它们的相关属性(如符号类型、绑定类型、可见性等)和在文件中的位置(如在哪个节中、偏移量等)。
- 每个目标文件都会有一个符号表,每个符号都有符号值,也是它的地址。符号分类如下:
- 全局符号:定义在本文件,可被其他目标文件引用。
- 外部符号:在本文件中引用但不是定义在本文件中。
- 段名:其值为该段的起始地址,有编译器产生。
- 局部符号:只在单元内部可见。
- 行号信息:指明目标文件指令与源代码中代码行的对应关系。
- 符号表结构:符号表也是文件中的一个段,段名为 .symtab ,是一个 Elf32_Sym 结构的数组,这个数组的第一个元素为无效的”未定义“符号。其结构体成员如下:
- 关于符号对应的值:在目标文件中,非 "COMMON 块" 类型的符号,其st_value 表示在段中的偏移;而 "COMMON块" 的则表示对齐属性。在可执行文件中,表示符号的虚拟地址。
- 关于符号类型和绑定信息:低四位表示符号类型,高二十八位表示符号绑定信息。
-
关于符号所在段:如果符号定义在本目标文件中,则表示在段表中的下标。否则如下表:
-
特殊符号: 由链接器定义但程序员可以直接使用的符号。例如:
- 强符号与弱符号:编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号。可以通过 ”_attribute_((weak))“ 来定义任何一个强符号为弱符号。强弱符号区分规则如下:
- 强引用与弱引用:强引用要求符号必须是已定义的,否则会报错;而弱引用则是指,当符号未被定义时,链接器会默认其为0或是特殊值,并不进行报错。可使用 “_attribute_((weakref))” 来声明对一个外部函数的引用为弱引用。
- 每个目标文件都会有一个符号表,每个符号都有符号值,也是它的地址。符号分类如下:
- ELF Header :位于 ELF 文件的开头,包含了关于 ELF 文件的基本信息,如文件类型(可执行文件、可重定位文件、共享库等)、目标机器架构(如 x86、ARM 等)、文件版本、入口点地址(对于可执行文件,指示程序开始执行的位置)、程序头表的偏移量和大小(如果存在)、节头表的偏移量和大小等。操作系统和相关工具通过读取 ELF 头来确定如何处理该文件。
标签:文件,符号,ELF,目标,程序员,修养,文件格式,字符串,全局变量 From: https://www.cnblogs.com/sejwy/p/18655923