1. 基础
一个标准线程由线程 ID, 指令指针 PC, 寄存器集合, 堆栈组成

2. 广义编译
完整过程
- 预处理
gcc -E.cpp => .icc1- 处理
#预编译指令- 展开
#define宏定义 - 处理
#ifdef等 条件预编译指令 - 处理
#include预编译指令 - 删除注释
// - 添加行号和文件名标识
- 展开
- 进行文本替换
- 编译 cc1
gcc -S.i => .scc1- 词法分析/扫描
- 语法分析/ 语法树
- 语义分析
- 中间代码生成
- 源代码优化
- 汇编代码生成
- 目标代码优化
- 汇编 as
gcc -c.s => .oas- 无任何优化, 根据对照表生成机器代码
- 链接 ld
gcc -.o => .outld- 将多个可重定位目标文件合并为可执行目标文件.
狭义编译
- 词法分析/扫描
将源代码的字符序列分割为一系列记号 (Token)
- 关键词: for while if
- 标识量: student
- 字面表: 1 2 3 “h”
- 特殊符号: +-= 并且还将标识符存放到符号表中,将数字字符串常量存放到文字表中
- 语法分析/ 语法树
对由扫描器产生的记号进行语法分析, 产生语法树 .
- 对于任意子树, 先计算其左子树和右子树的结果, 再计算根节点结果.
- 以符号和数字作为叶节点 ![[grammar-tree.png|575]]
- 语义分析
检查表达式有效性,处理类型转换。
编译器进行静态语义分析 (编译器即可确定的语义)
- 上下文相关性审查(比如,使用了未赋值的变量)
- 声明和类型的匹配
- 类型转换 该阶段为语法树的表达式标识了类型,对于隐式转化会在树中插入相应的转换节点
- 源代码优化,产生中间代码生成 Intermediate Code
- 编译器前端将语法树转换为中间语言, 即语法树的顺序表示, 与运行环境无关
- 包含类型
- 三地址码
x = y op z - P-代码
- 三地址码
- 目标代码生成
- 编译器后端(中的代码生成器)将中间语言转为目标机器汇编代码.
- 目标代码优化
- 编译器后端 (中的目标代码优化器)
编译器前后端划分 前端负责生成中间代码 后端负责将中间代码转换为目标机器代码,包括:
- 目标代码生成器
- 目标代码优化器
优点:
- 不同平台使用同一个前端和多个针对性后端
两次优化
3. 链接
符号 子程序或变量的起始地址
模块间通信:
- 模块间函数调用
- 模块间变量访问
-
两者都是”模块间符号的引用”
3.1 静态链接
原理: 将指令对其他符号地址的引用进行修正;
编译目标文件时, 将目标地址设为 0, 由链接器在进行链接时将其修正. 地址修正的过程称为重定位, 每个修正的地方为 重定位入口.
链接前目标文件中所有节的VMA都为0,虚拟地址尚未分配, 链接过程中计算对应值. 链接后各个节被分配到相应的虚拟地址
合并方式
- 按序叠加
- 合并相似节: 将目标文件中的相似节合并为一个段segment.
两步链接
合并相似节对应两步链接方法
地址和空间分配
扫描所有输入的目标文件, 记录所有节的长度、属性、位置, 将所有的符号定义和符号引用统一存放到全局符号表中,合并所有节为多个段, 计算合并后段的长度和位置, 建立映射关系.
此过程中生成程序头表 Program-Header-Table. ![[ELF-file-structure.png|475]]
产生的段
- 代码段 text (只读内存段)
- ELF-Header Program-Header-Table .init .text .rodata
- 数据段 data (读/写内存段)
- .data .bss
- 不加载到内存中的符号表、调试信息
- section-head-table .symtab .debug .strtab
符号解析(符号决议)
读取节的数据和重定位数据, 进行符号解析和重定位、调整代码、调整代码中地址
符号解析: 将“每个引用”和”可重定位目标文件符号表中一个确定的符号定义“关联起来.
局部符号解析:静态局部变量
- 要求每个模块中每个局部符号有一个定义
- 并要求每个定义有唯一名字
多重定义的全局符号解析: 全局变量和函数
- 不允许出现多个强符号,可以出现多个弱符号
- 定义函数和初始化全局变量为强符号
- 未初始化全局变量为弱符号.
- 当存在强符号时,必选强符号;如果此时存在弱符号占用空间更大则警告
- 当不存在强符号时, 从弱符号中任意选择; 类型不同时选择空间最大符号.
重定位
在链接前两步已经确定所有符号的虚拟地址,据此对每个需要重定位的符号进行地址修正。
ELF文件使用重定位表用以保存重定位相关信息,每个要被重定位的节都有一个对应的重定位表.
- .rel.text
- .rel.data
每个重定位位置即为一个重定位入口.
3.2 动态链接
程序运行时进行链接,需要OS支持,需要同时映射可执行文件和共享目标文件
- 静态库
- 浪费内存和磁盘空间
- 更新必须重新链接,更新困难
根据共享目标文件在内存中的地址分配可划分:
- 静态共享库(地址固定)
- OS在某个特定地址划分处一些地址块, 为模块预留空间
- 会导致地址冲突、升级困难等问题,基本不采用.
- 动态共享库(地址不固定)
- 又称装载时重定位,在链接时对所有绝对地址的引用不做重定位,而是在装载时进行重定位。
- 当模块装载地址确定,即目标地址确定,对所有绝对地址引用进行重定位
地址无关代码PIC 将指令中需要被修改的部分分离出来,与数据部分放在一起,数据部分可以在每个进程中拥有一个副本。
模块内部函数调用与数据访问,使用相对地址调用,无需重定位 模块外部函数调用与数据访问,使用代码无关地址技术,将地址相关部分放到数据段,以便能够同时读写,且每个进程都有独立副本互不影响。
在数据段中创建全局偏移表GOT,用以指向需要访问的外部符号,代码引用外部符号时使用GOT进行间接引用
链接器在装载模块时会查找每个变量所在的地址,随后填充GOT中各个项。
4. 目标文件
发展变革:
- a.out
- COFF
- PE & ELF
| ![[object-file-evolution.png | 575]] |
5. PE 格式目标文件
- DOS HEADER
- DOS Stub
- IMAGE NT HEADERS
- SECTION Table
- Section
6. ELF 格式目标文件
- 分类
- 可重定位文件: 包括静态链接库
- 可执行文件
- 共享目标文件
- 核心转储文件
目标文件按照属性不同将数据划分为多个节 section
文件内容
- ELF Header
- Program-head-table 链接后存在
- Sections
- .text(只读)
- .data 有初值全局变量和静态变量
- .bss 未初始化局部静态变量编译时一定存放在 .bss 节中 未初始化全局变量编译时不一定存放在 .bss 节中, 与编译器有关. 不存放则预留一个未定义全局变量符号,等待链接时在.bss节分配空间
- .rodata(只读) 只读变量 (const 修饰), 字符串常量
- .comment 编译器版本信息字符串
- .debug 调试信息
- .dynamic 动态链接信息
- .hash 符号哈希表
- Section-head-table 描述所有节的属性
- 有n个节描述符 Elf32_Shdr,其中第一个为无效节描述符,共(n-1)个有效节.
- Symbol table
- .symtab 符号表
- Elf32_Sym数组, 每个元素对应一个符号, 第一个元素无效.
- 符号值st_value: 函数或变量地址
- 包含符号分类
- 定义在本目标文件中全局符号
- 在本目标文件中引用的外部符号
- 节名
- 局部符号
- 行号信息(可选)
- 链接时只关注全局符号和外部符号
- .symtab 符号表
- String table
- .strtab 字符串表 : 保存普通字符串
- .shstrtab 节头字符串表 : 保存节头表中用到的字符串(如, 节名)
- Relocation table
- 每个需要重定位的代码节或数据节都需要单独的重定位节
- .rel.text
- .rel.*
![[ELF-file-content.png]]
6.1 文件头 ELF Header
描述整个文件属性.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define EI_NIDENT(16)
typedef struct{
unsigned char e_ident[EI_NIDENT]; /* Magic number和其它信息 */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf64_Half e_type; /* Object file type */
Elf64_Half e_machine; /* Architecture */
Elf64_Word e_version; /* Object file version */
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
Elf64_Word e_flags; /* Processor-specific flags */
Elf64_Half e_ehsize; /* ELF header size in bytes */
Elf64_Half e_phentsize; /* Program header table entry size */
Elf64_Half e_phnum; /* Program header table entry count */
Elf64_Half e_shentsize; /* Section header table entry size */
Elf64_Half e_shnum; /* Section header table entry count */
Elf64_Half e_shstrndx; /* Section header string table index */
} Elf64_Ehdr;
| ![[ELF-header.png | 575]] |
取值:
- MagicNum 中
- Magic:
0x7F, 0x45 ,0x4c, 0x46- 对应:DEL ‘E’ ‘L’ ‘F’
- ELF文件类型
- 0 : 无效;1 : 32bit;2 : 64bit
- 字节序: 大小端
- 0: 无效;1: 小端;2: 大端
- ELF版本
- 01
- OS/ABI
- ABI 版本
- Magic:
- etype
- 1可重定位; 2可执行; 3共享
- emachine
- 3 intelx86
readelf -h file
![[readelf-result.png|600]]
6.2 程序头表 Program-head-table
主要描述了程序执行时在内存中的布局以及如何映射到内存中, 是对可执行二进制文件中段的描述, 程序装载时必需。
在可执行ELF文件中必需, 可链接文件中可选,通过program header来获得每个segment的属性, 通过section header来获得每个section的属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct {
Elf32_Word p_type; /* segment type */
Elf32_Off p_offset; /* segment offset */
Elf32_Addr p_vaddr; /* virtual address of segment */
Elf32_Addr p_paddr; /* physical address - ignored? */
Elf32_Word p_filesz; /* number of bytes in file for seg. */
Elf32_Word p_memsz; /* number of bytes in mem. for seg. */
Elf32_Word p_flags; /* flags */
Elf32_Word p_align; /* memory alignment */
} Elf32_Phdr;
typedef struct {
Elf64_Half p_type; /* entry type */
Elf64_Half p_flags; /* flags */
Elf64_Off p_offset; /* offset */
Elf64_Addr p_vaddr; /* virtual address */
Elf64_Addr p_paddr; /* physical address */
Elf64_Xword p_filesz; /* file size */
Elf64_Xword p_memsz; /* memory size */
Elf64_Xword p_align; /* memory & file alignment */
} Elf64_Phdr;
链接前每个节的VMA、LMA都为0, 链接过程中计算对应值.
readelf
![[Pasted image 20240630141320.png]]
常见段类型:
- PT_LOAD 会被装载或映射到内存中
- PT_DYNAMIC 动态段,动态链接可执行文件特有的段,包含
- 运行时需要链接的共享库列表
- 全局偏移表GOT
- 重定位条目相关信息
6.3 节 Sections
重定位表 .rel.*
.rel.text
1
2
3
4
5
6
7
8
9
10
typedef struct{
Elf64_Addr r_offset;
Elf64_Xword r_info;
} Elf64_Rel;
typedef struct{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
Elf64_Sxword r_addend; /* Addend */
} Elf64_Rela;
符号表 .symtab
记录了目标文件中用到的所有符号, 每个符号拥有对应符号值, 即地址.
符号表中符号:
- 定义在本目标文件中全局符号
- 本目标文件引用的外部符号
- 节名, 值为该节的起始地址
- 局部符号, 编译内部可见, 对链接无任何作用
- 行号信息, 可选
链接只考虑全局符号和外部符号.
符号表由多个 Elf32_Sym 结构体组成, 第一个元素为无效的未定义符号.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Elf32_Sym
{
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 */
};
struct Elf64_Sym
{
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 */
};
readelf
![[Pasted image 20240630141450.png]]
字符串表 .strtab
将所有字符串集中到一个表中, 利用偏移使用不同的字符串。 保存普通字符串,包括:
- 符号名
节头表字符串表 .shstrtab
保存节头表中使用的字符串:
- 节名
6.4 节头表 Section-head-table
包含多个节描述符 ( Elf32_Shdr 结构体), 从中可以得到每个节的所有信息,第一个元素是无效节描述符。
- 节名
- 节长度
- 文件中的偏移
- 读写权限
- 其他节属性
可链接文件必有, 可执行文件可选,通过program header来获得每个segment的属性, 通过section header来获得每个section的属性。
1
2
3
4
5
6
7
8
9
10
11
12
typedef struct{
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign;/* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;
| ![[Pasted image 20240603232545.png | 625]] |
readelf
![[Pasted image 20240630141057.png]]
![[Pasted image 20240630141154.png]]
7. 程序的装载
一、装载的方式
页映射:将内存和磁盘中的数据和指令按照页为单位划分,以后装载和操作的单位就是页。4kb
二、程序的装载运行步骤
- 创建一个独立的虚拟地址空间;(虚拟页到物理页的映射关系,此时为空即可,缺页错误时会自动设置)
- 读取可执行文件头,检查文件有效性
- 读取可执行文件程序头表,建立虚拟地址空间和可执行文件的映射关系;
- 动态链接的过程;
- 启动动态链接器,将控制权交给动态链接器入口
- 动态链接器根据环境参数对可执行文件进行动态链接工作
- 控制权转交给可执行文件
| ![[Pasted image 20240630175347.png | 500]] |
内存读写权限配置
.rodata .text 只读