我觉得在学习编译链接过程之前有必要了解一下虚拟地址空间。
虚拟地址空间
1、什么是虚拟地址空间?
虚拟地址空间其实就是内存映射出来的存放地址的集合,它不是真实存在的,但又是可见的。
2、32位虚拟地址空间是多大?为啥?
大小:4G
32位即就是32位地址总线(32条),一个位的地址对应一个字节的内存大小;32位地址总线所保存的地址( 地址都是用16进制表示)为
0x 0000 0000 --》 0x ffff ffff。但是为了更清楚的解释问题,地址用二进制表述为 0000 0000 0000 0000 0000 0000 0000 0000 到
1111 1111 1111 1111 1111 1111 1111 1111 ,由此可见一个虚拟地址空间保存的地址是2^32字节,所以它的地址空间的大小是4G。
3、虚拟地址空间图示如下:
用32位系统来说,每个进程都会产生一个4G大小的虚拟地址空间。
编译链接大致过程
4、运行main.c代码文件
代码如下:
#include <stdio.h>
#include <stdlib.h>
int data1 = 10;
int data2 = 0;
int data3;
static data4 = 20;
static data5 = 0;
static data6;
int main()
{
int a = 30;
int b = 0;
int c;
printf("a= %d b= %d\n",a,b);
return 0;
}
代码 --> 运行 (过程如下):
代码 --> 编译 --> 链接--> 运行
文件类型: .c .o .out
二进制可重定位文件 二进制可执行文件
5、编译过程:预编译 --> 编译 --> 汇编
预编译(生成 “.i” 文件)
操作命令:gcc -E main.c -o main.i
内部处理:处理宏定义 处理 # 开头的预处理指令 删除所有注释
编译(生成 “.s” 文件):进行词法分析、语法分析、语义分析及优化后产生相应的汇编代码文件
操作命令:gcc –S main.i –o main.s
内部处理:生成符号 生成汇编指令
汇编(生成 “.o” 文件):将汇编代码编程机器可执行的指令
操作命令:gcc –c main.s –o main.o
内部操作:将所有的汇编代码翻译成机器可识别的二进制指令
6、连接过程(这里的链接暂不考虑动态库和静态库的链接):将所有相关文件中的各个段,重新组合链接成新文件
操作命令:gcc main.o –o main
内部处理:合并段表 调整段偏移 合并符号表 完成符号的重定位
预编译(生成 “.i” 文件), 操作命令:gcc -E main.c -o main.i,生成的 .i 文件如下图
编译(生成 “.s” 文件),操作命令:gcc –c main.s –o main.o,生成的 .s文件如下图
汇编 (生成 “.o” 文件),操作命令:gcc –c main.s –o main.o,生成的 .o 文件如下图
链接,操作命令:gcc main.o –o main,链接后生成的文件如下图
ELF文件
7、ELF文件的基本概念:
产生: 链接器链接后生成的最终文件为ELF格式可执行文件,一个ELF可执行文件通常被链接为不同的段,常见的段譬如.text、.data、.rodata、.bss等段。
内容:可执行文件、可重定位文件(.o)、共享目标文件(.so)、核心转储文件都是以elf文件格式存储的。
组成:文件头、段表(section)、程序头。
8、ELF文件的段:
一个典型的ELF文件包含下面几个段:
.text:已编译程序的指令代码段。
.rodata:ro代表read only,即只读数据(譬如常数const)。
.data:已初始化的C程序全局变量和静态局部变量。
.bss:未初始化的C程序全局变量和静态局部变量。
.debug:调试符号表,调试器用此段的信息帮助调试。
ELF文件格式如下图所示,位于ELF Header和Section Header Table之间的都是段(Section)。
9、查看ELF文件:
file 命令:查看文件头部信息
readelf 命令:读取ELF文件中信息
-h:读取ELF文件头
-S:读取段表
查看头部信息:
查看权限:
读取ELF文件头:
从上图可得:
Entry point addres: 程序的虚拟地址入口,因为这不是可执行的程序,故而为0 。
Start of program ... : 与上一行同理,这个程序没有program headers 。
Start of section... : sections头开始出,这里208是十进制,表示从地址偏移0xD0处开始。
Size of this header : ELF文件头的字节数。
Size of program headers: 不是可执行程序,故为0 。
Size of section headers: section header的大小,这里每个section头大小为40个字节。
Number ... : 一共有9个section头 。
读取段表:
从上图中可得:
1、.data 大小为00000c (12), 12 / 4 = 3, 无误。
2、.bss 大小为000014 (20),20 / 4 = 5 ,无误。
符号、强符号和弱符号 :
符号:所有数据都会产生符号,指令只有函数名会产生符号。
强符号:初始化了的非静态数据。
弱符号:没有初始化的非静态数据。
弱符号在存储过程中不占 .bss 空间,因为其被强符号所替换。所以有时候 .bss 中少 的数据是因为强符号替换弱符号造成的。
链接详解
链接过程:
1、合并段表:将两个二进制可重定位文件中的段信息整合放到一个文件中。
2、调整段偏移:段表经过合并之后大小发生变化,所以地址会进行适当的偏移。
3、合并符号表:将多个二进制可重定位文件中的符号整合到一个文件中。
4、完成符号的重定位:链接器把每个符号定义与一个虚拟地址联系起来,然后修改所有对这些符号的引用,然后通过虚拟地址使得它们指向相应的存储位置。
图示如下:
按照属性划分不同的段放在同一个页,运行时按照LOAD页信息将段按照页读入到虚拟地址空间
图示如下: