在开始链接之前,我们先了解几个概念:

一》符号的概念。

  我们知道,链接的最重要的是“对符号的重定位”,而且上面提到了符号表,那什么是符号呢,在链接中,我们将函数和变量统称为符号(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)的更多相关文章

  1. C++开始前篇,深入编译链接(3)

    一,COMMON块 什么是COMMON块,这是一种机制,早期的Fortran没有动态分配空间的机制,程序员必须事先声明它所需要的临时使用空间的大小.Fortran把这种空间叫做COMMON块,当不同的 ...

  2. C++开始前篇,深入编译链接

    C++开始,为什么要写这个东西,因为按照课堂进度的话,现在的C++已经学到模板以及重载了,有时却仍然因为一些小问题无法解答,原因是忘记了开始时学到的知识,深知不能像猴子掰棒子一样,掰一个扔一个,因此, ...

  3. linux 编译,链接和加载

    1.   序 最近在折腾各种.so,碰到了一些问题,一开始对于很多错误也没有头绪,茫然不知所措.索性化了一天多时间将<<程序员的自我修养—链接.装载与库>>中部分内容略读了一遍 ...

  4. 【原创】构建高性能ASP.NET站点 第六章—性能瓶颈诊断与初步调优(下前篇)—简单的优化措施

    原文:[原创]构建高性能ASP.NET站点 第六章-性能瓶颈诊断与初步调优(下前篇)-简单的优化措施 构建高性能ASP.NET站点 第六章—性能瓶颈诊断与初步调优(下前篇)—简单的优化措施 前言:本篇 ...

  5. webpack2 前篇

    webpack2 前篇 #webpack 前两天用了一天半时间琢磨了下webpack2,想起去年这时候,面对webpack1那样恶心的文档,前前后后搞了好几次才摸索清楚,那真是吐了. 划重点 其实we ...

  6. 【C编程基础】C编译链接命令gccc

    1.gcc安装 rpm -qa|grep gcc ==>检查gcc是否安装 gcc -v ==>检查gcc版本 yum -y install gcc ==>安装gcc  2.基本语法 ...

  7. G++编译链接的那些事

    语言 CPP 前言   虽然 VSCodeC++ 编辑器非常受大家的欢迎,无论是大佬还是小白都说对其爱不释手...   我...用了一段时间后发现实在是麻烦,配置往往花费我大量时间.可以说真的是吃力不 ...

  8. 【原创】Linux下编译链接中常见问题总结

    前言 一直以来对Linux下编译链接产生的问题没有好好重视起来,出现问题就度娘一下,很多时候的确是在搜索帮助下解决了BUG,但由于对原因不求甚解,没有细细研究,结果总是在遇到在BUG时弄得手忙脚乱得. ...

  9. 关于tcc、tlink的编译链接机制的研究

    1.学习过程 在c:\下建立文件夹c,并将编译器tcc.exe.连接器tlink.exe.相关文件c0s.obj.cs.lib.emu.lib.maths.lib放入文件夹中. 要搭建一个简单的C语言 ...

  10. ASP.NET自定义控件组件开发 第三章 为控件添加事件 前篇

    原文:ASP.NET自定义控件组件开发 第三章 为控件添加事件 前篇 第三章 为控件添加事件 好了,我们之前以前开发一个控件.而且也添加了属性,开发也很规范,但是那个控件还差最后一点:添加事件. 系列 ...

随机推荐

  1. iframe用js设定自定义高度

    JS代码 function SetWinHeight(obj){ var win=obj; if (document.getElementById){ if (win && !wind ...

  2. Linux下Redis的安装和部署

    一.Redis介绍 Redis是当前比较热门的NOSQL系统之一,它是一个key-value存储系统.和Memcache类似,但很大程度补偿了Memcache的不足,它支持存储的value类型相对更多 ...

  3. 安装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 ...

  4. PHP安装kafka插件

    在工作中我们经常遇到需要给php安装插件,今天把php安装kafka的插件的步骤整理下,仅供大家参考 1:需要先安装librdkafka git clone https://github.com/ed ...

  5. Netty源码分析之服务端启动过程

    一.首先来看一段服务端的示例代码: public class NettyTestServer { public void bind(int port) throws Exception{ EventL ...

  6. Oracle创建自增ID

    先创建序列sequence create sequence S_User minvalue 1 nomaxvalue  -- 或 maxvalue 999 start with 1 increment ...

  7. Matlab编程知识点

    容易忘记的小知识点: Matlab程序首行程序: clear:close all:clc:程序运行开始最好清理下之前残留的各种数据,不然matlab可能会出错. clear(清理变量空间),close ...

  8. redis 基础知识

    1.安装 sudo apt-get install redis-server 检查Redis服务器程序,执行:ps -aux|grep redis 命令检查Redis服务器状态,执行:netstat ...

  9. Javascript操作DOM常用API总结

    基本概念 在讲解操作DOM的api之前,首先我们来复习一下一些基本概念,这些概念是掌握api的关键,必须理解它们. Node类型 DOM1级定义了一个Node接口,该接口由DOM中所有节点类型实现.这 ...

  10. Java多线程--线程安全问题的相关研究

    在刚刚学线程的时候我们经常会碰到这么一个问题:模拟火车站售票窗口售票.代码如下: package cn.blogs.com.isole; /* 模拟火车站售票窗口售票,假设有50张余票 */ publ ...