一、编译原理
1.编译器
编译器简单理解就是将一种语言(源语言)转换为等价的另一种语言(目标语言)的程序。
编译器分为前端(Front end)和后端(Back end)两部分。
前端:机器无关,把源程序分解成组成要素和相应的语法结构,通过这个结构创建源程序的中间表示,同时收集和程序相关的信息,存放到符号表中;
后端:机器相关,根据中间表示和符号表构造目标程序。
编译过程大致为5个步骤:
词法分析(Lexical analysis)-》输出有意义的词素(Lexeme)
语法分析(Syntax analysis)-》产生语法树(Syntax tree)
语义分析(Semantic analysis)
中间代码生成和优化
代码生成和优化
2.GCC编译过程
GCC编译主要有四个阶段:预处理(Preprocess)、编译(Compile)、汇编(Assemble)、链接(Link)。
三个工具组件:ccl(第一和第二阶段)、as(第三阶段)、collect2(对ld命令的封装,第四阶段)
- 预处理阶段:处理以#开头的预处理指令,如#include、#define等,转换后直接插入程序文本中,通常以.i作为拓展名。
GCC -E hello.c -o hello.i #单独执行预处理
1
2. 编译阶段:执行词法分析、语法分析、语义分析以及优化,生成汇编代码。
#GCC中预处理已经和编译合并处理
GCC -S hello.c -o hello.s
#masm指定为intel格式,fno-asynchronous-unwind-tables生成没有cfi宏的汇编指令,以提高可读性。
GCC -S hello.i -o -masm=intel -fno-asynchronous-unwind-tables
3.汇编阶段:根据汇编指令和机器指令的对照表进行翻译,将.s翻译为目标文件.o。
GCC -c hello.c -o hello.o
GCC -c hello.s -o hello.o
1
2
4.链接阶段:分为静态链接和动态链接两种方式,默认使用动态链接,使用-static指定使用静态链接。将目标文件和依赖库进行链接,主要包括地址和空间分配、符号绑定、重定位等。
GCC hello.o -o hello
1
两个常用命令:file和objdump
file 文件名:查看文件详细信息
objdump -sd hello -M intel:反汇编
二、ELF文件格式
ELF(Executable and Linkable Format):可执行可链接格式,在Linux上运行,在/usr/include/elf.h中定义。
1.ELF文件类型
可执行文件(.exec)
可重定位文件(.rel)
共享目标文件(.dyn)
2.ELF文件结构
视角选择:
链接视角:通过节(Section)来进行划分,通常包含代码(.text)、数据(.data)、BSS(.bss)三种节;
运行视角:通过段(Segment)来进行划分。
(1)ELF文件头
ELF文件头(ELF header)位于目标文件最开始的位置,包含整个文件的基本信息。
readelf -h elfDemo.rel #查看elf文件头信息
1
注意:文件头中存在魔术字符(Magic):7f 45 4c 46 即字符串“\177ELF”,可以通过搜索该字符确定内存映射地址。
(2)节头表
节头表(Section header table)包含了目标文件中所有节的信息,记录了节的名字、长度、偏移、读写权限等信息。节头表的位置记录在文件头的e_shoff域中。
readelf -S elfdemo.rel#查看目标文件节头表
1
注意:节头表对于程序运行不是必须的,因为它与程序内存和布局无关,是程序头表的任务,所以常有程序去除节头表,以增加反编译器的分析难度。
(3)代码节(.text)
objdump -x -s -d elfDemo.rel #输出各节的内容
1
Contents of section .text部分是.text数据的十六进制形式,共0x4e个字节,最左边一列是偏移量,中间四列为内容,最右边一列为ASCII码形式。
Disassembly of section .text部分是反汇编结果。
(4)数据节(.data)
保存以及初始化的全局变量和局部静态变量。
.rodata 保存只读数据,包含只读变量和字符串常量
(5)BSS节(.bss)
用于保存未初始化的全局变量和局部静态变量,没有Contents属性,只是为变量预留了位置,因此该节的sh_offset域也就没有意义。
(6)其他节
字符串表(.strtab)
用来表示符号名和节名,引用字符串表时只需给出字符徐磊在表中的偏移量即可
字符串表的第一个和最后一个字符均为null字符,以确保所有字符串的开始和终止。
符号表(.dynsym和.symtab)
.dynsym保存引用外部文件的符号,只在运行时被解析;
.symtab还保存了本地符号,用于调试和链接。
目标文件通过在该表中的索引值来使用该符号,索引值从0开始,但0的表项不具备实际意义,他表示未定义的符号。
3.可执行文件的装载
当运行一个可执行文件时,首先要将该文件和动态链接库装载到 进程空间中,形成一个进程镜像。
每个进程都有独立的虚拟地址空间,这个空间如何布局是由记录在 段头表 中的程序头决定的。
ELF文件头的e_phoff域给出了段头表的位置。
通过
readelf - l elefDemo.exec
1
可以看出一个段包含了一个或多个节,相当于对这些节按不同权限进行分组,使之可以同时装载多个节,从而节省资源。
常见的段:
PT_LOAD类型段:用于描述可装载的节(动态链接的可执行文件则包含两个–将.data和.text分开存放)
PT_DYNAMIC动态段:包含动态链接库所必须的信息,如共享库列表、GOT表和重定位表等。
PT_NOTE类型段:保存了系统相关的附加信息,运行并不需要
PT_INTERP段:将位置和大小信息存放在一个字符串中,是对程序解释器的位置的描述。
PT_PHDR段;保存了程序头表本身的位置和大小