c语言的编译链接过程

整个的编译链接过程如上图所示

首先对于一个cpp而言他需要四步    预处理 编译 汇编 链接 四个阶段过程 

预处理 将所有#define删除  展开定义 删除注释 添加 行号 添加添加编译 include头文件展开 添加调试信息debug。保留所有的pragma处理。编译 器进行处理。

编译阶段 进行的词法分析,语法分析,语义分析,和优化汇编代码。

汇编阶段 只进行过翻译 as

链接器(重要) 链接器是将各个模块之间能够正确的衔接起来,将相互引用的部分处理好。地址和空间分配,符号决议(强弱符号)重定位。

符号解析也叫符号决议 目标文件定义和引用符号。符号解析的目的是将每个符号引用刚好与一个符号的定义相联系

符号决议强符号。都是弱符号选择一个其中空间最大的,只能有一个强符号,一个强符号和弱符号只能保留一个强符号。没有初始化的。static和const变量


符号表在编译程序工作的过程中需要不断收集、记录和使用源程序中一些语法符号的类型和特征等相关信息。这些信息一般以表格形式存储于系统中。如常数表、变量名表、数组名表、过程名表、标号表等等,统称为符号表。对于符号表组织、构造和管理方法的好坏会直接影响编译系统的运行效率。

符号表的结构在目标文件中有一个段.symtab 段他是一个32位EFL的素组。主要存储了符号的名字,符号的值符号的大小,符号的类型和绑定信息,符号所在的段。每个符号都有一个ELF32_SYM的结构体

每个符号都有一个所对应的符号的名字。这个包含了符号名在字符串表 中的下表

每个符号都有一个值这个值是如果是变量和函数的定义符号那这个额就是地址。如果不是在common则表示的是该符号在段中(由符号所在的段)的偏移。

符号的大小就是所占空间的大小。

符号的绑定信息就是符号是local符号还是global符号,或者强弱符号。

符号的类型是说符号属于哪一类。函数变量还是未知类型。常见的有FUNC FILE OBJECT变量 section段

符号所在的段比如

 ABS一个绝对的地址文件名的符号就属于。

COMMON 未初始化的全局符号就是 这个类型。

UNDEF 为定义的符号其他模块中的变量。


static变量他的绑定信息为本地的

静态链接

空间和地址分配

将各个目标中的obj文件中的相似段进行和并且bss段是不占空间。分配空间。链接器给目标文件分配一个是输出可执行文件的空间一个是装载之后虚拟地址中的虚拟地址的空间。bss段在可执行文件中不占内存但是在装入内存后要分配内存。对于虚拟地址空间的分配是关系到链接器对于后面的空间计算。而可执行文件的大小和链接过程没有关系。

此时的地址就是进程的地址。实际的运行地址。


将所有obj 文件中的段和属性位置以及符号表中的所有符号定义和符号引用收集起来统一的放到全局的符号表中。然后链接器获取到所有输出文件的段的长度,将他们合并。算出各个段的长度和位置,建立起映射关系。

此时的地址就是进程的地址。实际的运行地址。但是段的地址还是0所以当连接后所有段都分配到自己的地址。所谓的文件偏移。

符号地址的确定 实际的段地址加上前面符号的偏移就得到了实际的虚拟地址。

符号的解析和重定位过程。

objdump -d 查看 反汇编代码在分配空间前每个变量的地址


编译器和汇编器生成从地址0开始的代码和数据节。链接器通过把每个符号定位与一个存储器位置联系起来,然后修改这些符号的引用,使得它们指向这个存储器,从而重定位这些节。第一步完成了地址和空间的分配之后确定了虚拟地址,第二部就要根据符号的地址进行指令的修改。让所有重定位入口改回正确的位置。如图变量b

的地址是0x00。

重定位表。用于修改相应段中的内容。如果text段有需要重定位的。那就会有一个.rel.text 的段保存了了代码的重定位表。如何查看重定位表objdump -r b.o每个重定位的地方都有一个重定位的入口,就是他在该段的偏移。


符号分为四类: 导出符号(export,本地符号), 导入符号(import,外部符号), 静态符号(本地符号), 局部符号(本地符号,不出现在符号表中)。
 
导出符号, 在本模块定义, 能够被其他模块引用的符号。 非static全局函数, 非static全局变量。
导入符号, 在其他模块定义,被本模块引用的符号。  extern 修饰的全局非static变量声明(extern int a), 其他模块的函数引用
静态符号, 在本模块定义, 只能被本模块引用的符号。 static函数, static全局变量。
局部符号, 在函数内部定义的非static变量。不出现在符号表,由栈管理。链接器不care这类符号

将他们存放在符号表中但却不去为他们进行内存关联一直等到链接阶段在进行处理。

重定位发生于目标代码链接阶段,在链接阶段链接器就会查找符号表,当他发现了function2.cpp的符号表之中任然有没有决议的内存地址时,链接器就会查找所有的目标代码文件,一直到他找到了function1.cpp所生成的目标代码文件符号表时发现了这些没有决议的符号变量的真正内存地址,这是function2.cpp所生成的目标代码文件就会更新它的符号表,将这些尚未决议的符号变量的内存地址写进其符号表中。


程序如何会变成进程。pcb块内核中会维护一个每个进程都有。所有进程用户空间都是独立的主要是因为task_struct中的有每个程序各个段的起始和结束位置。内核空间是共享的。

链接分为两部

1.合并所有的obj文件的段调整段偏移和段长度合并符号表进行符号解析。

2符号的重定位。

obj因为没有重定位。地址都是相对地址。

obj -h obj.o 查看obj文件中段的信息

readelf -h main.o 查看ELF 信息

bss段在obj文件中并不占空间。

obj文件的组成格式


段表保存每个段的信息。段表在文件中的偏移量记录在文件头中

obj中不给符号分配地址。链接后符号解析后得到符号地址。是虚拟地址所有段的地址都是0函数的地址也是0

ELF head 



为什摸bss段在文件中不占内存却还是将变量放在bss段因为他们都是0所以不用存只需要知道大小实际运行过程中获取大小然后映射到内存中bss段。

弱符号会导致一些问题。如果该弱符号在本地文件复制而实际过程却被其他文件强符号给替代。有可能会出错。

常量字符串是在只读数据段中存

每个段都有自己的权限。代码段可读可写的段。弱符号在com等待链接过程中被选择。

链接器只对global符号进行处理没有分配到段的符号都叫符号的引用链接时符号解析进行处理。

符号的解析 所有obj符号表中对符号引用的地方都要找到符号定义的地方。

编译的时候对符号不知道符号的地址所以是0代码都是指令。所以你需要进行重定位修改。所以函数调用的地址都是和该函数下一行的偏移 

程序变成进程。


比obj的头多了一个program  在运行时候进行加载program中的信息。主要是将指令和数据进行加载到内存。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值