C++开始前篇,深入编译链接(补充2)
在开始链接之前,我们先了解几个概念:
一》符号的概念。
我们知道,链接的最重要的是“对符号的重定位”,而且上面提到了符号表,那什么是符号呢,在链接中,我们将函数和变量统称为符号(Symbol)。函数名和变量名就是符号名(Symbol Name)。每一个目标文件都有一个相应的符号表(Symbol Table),这个表里记录了目标文件中所用到的所有符号。每个对应的符号有一个对应的值,叫做符号值(Symbol Value)。对于函数和变量来说,符号值就是它们的地址。 除了函数和变量,还存在其他几种符号。简单了解一下。
1,全局符号:定义在本目标文件中的全局符号
2,外部符号:在本目标文件中引用,却没定义在本目标文件,称为外部符号
3,段名:由编译器产生的符号
4,局部符号:局部变量
5,行号信息 对于链接来说,其中最重要就是全局符号与外部符号。
二》关于ELF符号表的结构:
a,这是段表
b,这是符号表
我们可以找到两个表中以下的关系
1,从符号表的num下标为6开始看,static_init,是我们在定义过初始化了的局部静态变量,绑定属性是LOCAL,在段表中下标为3的段中存储,即在.data段;接下来是 static_uninit,是未初始化的局部静态变量,在段表中下标为4的段中存储,即.bss段。
2,再就是 global_init,与static_init相同,都保存在了.data段。而global_uninit,按照我们的分析,它跟staticy_uninit一样都应该保存与.bss段,这里却是COM,为什么?原来,这跟不同的语言与不同的编译器实现有关,有些编译器会将全局的未初始化变量存放在目标文件.bss段,有些则不存放,只是预留一个未定义的全局变量符号,等到最终链接成可执行文件的时候再在.bss段分配空间。(有关于此处的COM符号,在后面谈到关于COMMON块时再深入了解。)
3,func函数与main函数都是全局可见的,因为是函数,所以类型是STT_FUNC。它们都存在与.text中。
4,printf,因为我们只是使用却没有定义它,所以它在此目标文件中不存在于任何段,所以是UND(未定义)。
三》,强符号与弱符号。
对于C/C++语言来说,编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号。
针对强弱符号的概念,链接器会按如下规则处理和选择被定义多次的全局符号:
规则1,不允许强符号被多次定义。否则报错。
规则2,如果一个符号在某个目标文件中是强符号,在其他文件中都是弱符号,那么选择强符号。
规则3,如果一个符号在所有目标文件中都是弱符号,那么选择其中占用空间最大的一个。
-------------------------------------------------------------------------------------------------------------------------------------
了解之后,现在是时候进入静态链接了。
我们知道,链接是将所有目标文件以及库文件按照某种方式合并起来,最后生成可执行文件。是这么简单吗?是或不是,我们都要好好深入一下。
我们写两个文件a.c和b.c
a.c
extern表明shared可在其他的文件中找。
b.c
编译之后得到a.o,b.o。链接之后得到ab
分别查看这三个文件的信息
。先看ab的SIZE,稍微算一下我们可以发现,.text段和.data段它们的SIZE关系都是ab(SIZE)=a.o(SIZE)+b.o(SIZE);因此可以得出这是按相似段合并的。
。再看VMA,这是Virtual Memory Address,即虚拟地址。LMA表示Load Memory Addre1ss,即加载地址.除了在某些嵌入式系统不同,一般两个值都相同。a.o 和b.o中VMA和LMA都是0,因为编译阶段并不分配内存地址。
。。这种合并方式一般采用一种叫做两步链接的方法,
。。。第一步,空间与地址分配。
问题:分配的是什么空间?
此处的空间有两个含义,一是输出在可执行文件中的空间,二是在装载后的虚拟地址中的虚拟地址空间。我们可以看到,ab文件中并没有.bss段,原来对于.bss这样的段来说,分配空间的意义只局限于虚拟地址空间。在我们着重考虑虚拟地址空间。
总结一下第一步都干了些啥事:
链接之前,目标文件中所有段的VMA都是0,链接时,链接器按照相似段合并的方法在一个整合了两个目标文件的.text和.data段,并且给这些段分配了虚拟空间,也就有了虚拟地址。有了段的地址之后,链接器便开始计算每个段中的符号地址,因各符号在各段中的相对位置都是固定的,因此根据偏移量,可以得到各个符号的正确的虚拟地址。
举个栗子:a.o中.text的虚拟地址是0x80488094,在.text段中main的偏移为X,则main的虚拟地址为0x80488094+X。
。。。第二步,符号解析与重定位。
完成了第一步空间与地址的分配后,链接器根据第一步收集到的信息,读取输入文件中段的数据,重定位信息并且进行符号解析和重定位、调整代码中的地址等。
OK,我们详细的来研究一下。
看一下a.o的反汇编代码:
程序的代码里面使用的都是虚拟地址,从上面反汇编结果可以看出 在目标文件a.o中,main的起始地址是0,因为还没有分配空间。等到空间分配完成后,各个函数才会确定自己在虚拟地址空间中的位置;
仔细观察这两行 ,,第11行 “c7 44 24 04” 是movl指令码, “00 00 00 00”这是shared的地址。
第20行 “E8” 是相对偏移调用指令call的指令码,“FC FF FF FF”是目的地址相对于下一条指令的偏移。
当源代码a.c被编译成目标文件时,编译器并不知道shared和swap的地址,所以编译器暂时把0当做shared的地址;关于FC FF FF FF 是被调用函数的相对于调用指令的下一条指令的偏移量,这也只是一个临时的假地址,是常量-4的补码形式。
现在我们再看一看链接之后的结果:
我们发现,从链接结果来看,
原11行
原20行
它们的地址都被重写了。原20行,call指令的下一条是leave 它的地址是 80480b9 所以相对于leave指令偏移量为0x00000003 的地址为8048b9+3=8048bc。刚好是swap的地址!
说到这儿,其实重定位的过程就是个符号解析的过程,在链接器扫描完所有的输入目标文件之后,所有这些未定义的符号都应该能在全局符号表中找到,否则链接器就会报符号未定义错误。(symbol UND).
OK ,就到这儿,下一节,再来学习上次说到的COMMON块、静态库链接相关的知识。
C++开始前篇,深入编译链接(补充2)的更多相关文章
- C++开始前篇,深入编译链接(3)
一,COMMON块 什么是COMMON块,这是一种机制,早期的Fortran没有动态分配空间的机制,程序员必须事先声明它所需要的临时使用空间的大小.Fortran把这种空间叫做COMMON块,当不同的 ...
- C++开始前篇,深入编译链接
C++开始,为什么要写这个东西,因为按照课堂进度的话,现在的C++已经学到模板以及重载了,有时却仍然因为一些小问题无法解答,原因是忘记了开始时学到的知识,深知不能像猴子掰棒子一样,掰一个扔一个,因此, ...
- linux 编译,链接和加载
1. 序 最近在折腾各种.so,碰到了一些问题,一开始对于很多错误也没有头绪,茫然不知所措.索性化了一天多时间将<<程序员的自我修养—链接.装载与库>>中部分内容略读了一遍 ...
- 【原创】构建高性能ASP.NET站点 第六章—性能瓶颈诊断与初步调优(下前篇)—简单的优化措施
原文:[原创]构建高性能ASP.NET站点 第六章-性能瓶颈诊断与初步调优(下前篇)-简单的优化措施 构建高性能ASP.NET站点 第六章—性能瓶颈诊断与初步调优(下前篇)—简单的优化措施 前言:本篇 ...
- webpack2 前篇
webpack2 前篇 #webpack 前两天用了一天半时间琢磨了下webpack2,想起去年这时候,面对webpack1那样恶心的文档,前前后后搞了好几次才摸索清楚,那真是吐了. 划重点 其实we ...
- 【C编程基础】C编译链接命令gccc
1.gcc安装 rpm -qa|grep gcc ==>检查gcc是否安装 gcc -v ==>检查gcc版本 yum -y install gcc ==>安装gcc 2.基本语法 ...
- G++编译链接的那些事
语言 CPP 前言 虽然 VSCodeC++ 编辑器非常受大家的欢迎,无论是大佬还是小白都说对其爱不释手... 我...用了一段时间后发现实在是麻烦,配置往往花费我大量时间.可以说真的是吃力不 ...
- 【原创】Linux下编译链接中常见问题总结
前言 一直以来对Linux下编译链接产生的问题没有好好重视起来,出现问题就度娘一下,很多时候的确是在搜索帮助下解决了BUG,但由于对原因不求甚解,没有细细研究,结果总是在遇到在BUG时弄得手忙脚乱得. ...
- 关于tcc、tlink的编译链接机制的研究
1.学习过程 在c:\下建立文件夹c,并将编译器tcc.exe.连接器tlink.exe.相关文件c0s.obj.cs.lib.emu.lib.maths.lib放入文件夹中. 要搭建一个简单的C语言 ...
- ASP.NET自定义控件组件开发 第三章 为控件添加事件 前篇
原文:ASP.NET自定义控件组件开发 第三章 为控件添加事件 前篇 第三章 为控件添加事件 好了,我们之前以前开发一个控件.而且也添加了属性,开发也很规范,但是那个控件还差最后一点:添加事件. 系列 ...
随机推荐
- iframe用js设定自定义高度
JS代码 function SetWinHeight(obj){ var win=obj; if (document.getElementById){ if (win && !wind ...
- Linux下Redis的安装和部署
一.Redis介绍 Redis是当前比较热门的NOSQL系统之一,它是一个key-value存储系统.和Memcache类似,但很大程度补偿了Memcache的不足,它支持存储的value类型相对更多 ...
- 安装PHP sphinx扩展 sphinx-1.1.0/sphinx.c:105:2: error: too few arguments 错误
表现: /home/sphinx-1.1.0/sphinx.c: In function 'php_sphinx_client_read_property':/home/sphinx-1.1.0/sp ...
- PHP安装kafka插件
在工作中我们经常遇到需要给php安装插件,今天把php安装kafka的插件的步骤整理下,仅供大家参考 1:需要先安装librdkafka git clone https://github.com/ed ...
- Netty源码分析之服务端启动过程
一.首先来看一段服务端的示例代码: public class NettyTestServer { public void bind(int port) throws Exception{ EventL ...
- Oracle创建自增ID
先创建序列sequence create sequence S_User minvalue 1 nomaxvalue -- 或 maxvalue 999 start with 1 increment ...
- Matlab编程知识点
容易忘记的小知识点: Matlab程序首行程序: clear:close all:clc:程序运行开始最好清理下之前残留的各种数据,不然matlab可能会出错. clear(清理变量空间),close ...
- redis 基础知识
1.安装 sudo apt-get install redis-server 检查Redis服务器程序,执行:ps -aux|grep redis 命令检查Redis服务器状态,执行:netstat ...
- Javascript操作DOM常用API总结
基本概念 在讲解操作DOM的api之前,首先我们来复习一下一些基本概念,这些概念是掌握api的关键,必须理解它们. Node类型 DOM1级定义了一个Node接口,该接口由DOM中所有节点类型实现.这 ...
- Java多线程--线程安全问题的相关研究
在刚刚学线程的时候我们经常会碰到这么一个问题:模拟火车站售票窗口售票.代码如下: package cn.blogs.com.isole; /* 模拟火车站售票窗口售票,假设有50张余票 */ publ ...