gcc -v查看编译的过程
文件编译过程: 语言预处理器-->
xxx.i-->编译器-->
xxx.s-->汇编器-->
xxx.o-->链接器-->
可执行文件
目标文件分为三类:可重定位目标文件、共享目标文件、可执行目标文件
编译器、汇编器 可生产可重定位的目标文件(包括共享目标文件);链接器生成可执行目标文件
什么是符号解析?在代码中,变量 函数等符号 有 定义 和引用之分,因此符号解析的目的就是将每个符号引用与一个符号定义联系起来;
链接器来做符号解析,链接器将每个符号的引用 与 此时输入的可重定位目标文件中的符号表中的一个符号定义 联系起来。
什么是重定位?编译器和汇编器
生成从0地址开始的代码和数据节,链接器通过吧每个符号定义与一个存储器位置联系起来(简单理解为符号定义的位置),然后修改所有对这些符号的引用,使得他们指向这个存储器位置,从而
重定位这些节。链接器在完成符号解析后(即把一个符号的引用与一个符号的定义联系起来)。接下来,
链接器准备对各目标文件中的确定长度的相关节进行重定位了,合并输入模块,并为每个符号分配运行时的地址,
重定位分为两步:
1:重定位节和符号定义:
链接器将多个目标文件中所有相同类型的节合并为同一类型的新的聚合节。如,各输入模块的.data节在最终可执行目标文件中被合并为一个.data节 ,然后,链接器将运行时存储器地址赋给新的聚合节(里面输入各模块的各个节 以及 各个符号),这步完成后,程序中的每个指令和全局变量都有唯一的运行时存储地址了。
2:重定位节中的符号引用
在这一步,链接器修改代码节和数据节中对每个符号的引用,使得他们指向正确的运行时地址。
==============================================================
可以将各个.o目标文件打包成一个库来使用
ar:创建静态库(各个xx.o),插入 删除 列出 和 提前成员
strings:列出一个目标文件中所有可打印的字符串
strip:从目标文件中删除符号表信息
nm:列出一个目标文件的符号表中定义的符号
size:列出目标文件中节的名字和大小
readelf:显示一个目标文件的完整结构,包括ELF头中编码的所有信息(包含size和nm的功能)
objdump:所有二进制工具之母,能够显示一个目标文件中的所有的信息,他最大的作用是反汇编.text节中的二进制指令
ldd:列出一个可执行文件在运行时所需要的共享库
===================
可重定位目标文件============================
可重定位目标文件 与 可执行目标文件 的组织结构大致是相同的;有些小差别;
ELF头:
.text:
.rodata:
.bss:
.symbol: 符号表
【符号表由汇编器构造的】,存放的是程序中定义和引用的函数 和全局变量 或 静态变量(静态变量在有些书中统称为全局变量)的信息;因为局部变量是存放在堆栈中,所以
符号表中是不包含局部变量条目的符号。
.rel.text:.text中需要重定位的位置,比如引用另一个目标文件的函数等;可执行文件中 并不需要重定位信息,因此通常省略,除非用户显示地指示链接器包含这些信息;
.rel.data:
.debug:一个调试符号表,其条目是程序中定义局部变量 和类型定义(-g选项)
.line:原始C源程序中的行号与.text节中机器指令之间的对应关系
.strtab:一个字符串表,其内容包括.symbol和.debug节中的符号表 以及节头部中的节名字,字符串表 就是以null结尾的字符串序列
===================
可执行目标文件加载
============================
先看下面的可执行目标文件的分析,再看这里的加载吧,排版问题
proc/进程PID/maps
查看某进程的虚拟地址空间是如何分配利用的。
cat /proc/1/statm
487 185 133 31 0 67 0
很简单地返回7组数字,每一个的单位都是一页 (常见的是4KB)
分别是
size:任务虚拟地址空间大小
Resident:正在使用的物理内存大小
Shared:共享页数
Trs:程序所拥有的可执行虚拟内存大小
Lrs:被映像倒任务的虚拟内存空间的库的大小
Drs:程序数据段和用户态的栈的大小
dt:脏页数量
很简单地返回7组数字,每一个的单位都是一页 (常见的是4KB)
分别是
size:任务虚拟地址空间大小
Resident:正在使用的物理内存大小
Shared:共享页数
Trs:程序所拥有的可执行虚拟内存大小
Lrs:被映像倒任务的虚拟内存空间的库的大小
Drs:程序数据段和用户态的栈的大小
dt:脏页数量
===================
可执行目标文件============================
ELF可执行文件(a.out)或类似编译器编译出的文件(.bin), 都是由各个节(section:如.text .bss .data等)组成;运行时,各个节被加载到内存中,此时的内存中以段(Segment:如代码段 数据段)的形式组织相关的节,以便程序运行;
ELF可执行文件(a.out): 在linux下,可使用readelf nm objdump 以及gdb 来查看与调试可运行的文件;
ELF可执行文件的组成形式如下:
【
一般C语言的编译后执行语句都编译成机器代码,保存在.text段;已初始化的全局变量和局部静态变量都保存在. data段;未初始化的全局变量和局部静态变量一般放在一个叫."bss"的段里。我们知道未初始化的全局变量和局部静态变量默认值都为0,本来它们也可以被放在.data段的,但是因为它们都是0,所以为它们在.data段分配空间并且存放数据0是没有必要的。程序运行的时候它们的确是要占内存空间的,并且可执行文件必须记录所有未初始化的全局变量和局部静态变量的大小总和,记为.bss段。所以.bss段只是为未初始化的全局变量和局部静态变量预留位置而已,它并没有内容,所以它在文件中也不占据空间。】
【函数中的局部变量将放在栈中,既不在.data 也不在.bss中】
一个进程的内存映像,从低地址开始分为五部分
正文段
初始化数据段
未初始化数据段
堆区
栈区
【栈由该区域的最高地址向低地址增长,而堆由该区域的低地址向高地址增长】
【当一程序启动运行的初期,并没有把该程
序所需要的所有的物理空间分配给它,而是只分配了满足当时可以使之运行的几个页面。程序继续运行(虚拟地址)需读取新的页面时,发现该页面不在内存
中,就要用一定的算法
为该进程对应的虚拟地址空间
分配一内存页面】
简单来讲,程序的装入到运行的主要包含以下几个步骤:
1:读入可执行文件的头部信息以确定其文件格式及地址空间的大小;
2:以段的形式划分地址空间;
3:将可执行程序读入地址空间中的各个段,建立虚实地址间的映射关系;
4:将bbs段清零;
5:创建堆栈段;
6:建立程序参数、环境变量等程序运行过程中所需的信息;
7:启动运行。
//程序头表中 描述可执行文件 到 虚拟内存空间的映射关系
Program Headers:
//程序头表中 描述段 到 节 的映射关系
readelf -a a.out :可查看ELF文件的各个头信息(这些信息描述了整个可执行文件的组织信息)
1 简介