20135302魏静静——《深刻理解计算机系统》第7章 学习笔记

《深刻理解计算机系统》第7章   连接

本章主要内容:数据结构

  • 连接——静态连接、动态连接(连接又包括两个主要任务:符号解析和重定位)
  • 符号——全局符号和本地符号、符号表、符号解析
  • 连接文件的建立及引用——gcc、ar rcs、sharedj及fPIC命令参数
  • 重定位——重定位条目、重定位符号引用(PC相对引用和绝对引用)
  • 目标文件——可重定位目标文件(其中又详细介绍了ELF可重定位文件的结构及格式)、可执行目标文件、共享目标文件
        连接(linking)是将各类代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(或被拷贝)到存储器并执行。


连接能够执行于编译时,即源代码被翻译成机器代码时;也可执行于加载时,即在程序被加载器加载到存储器并执行时;甚至执行于运行时,由应用程序来执行。

1.编译器驱动程序

 

大多数编译系统提供编译器驱动程序(compiler driver),它表明用户在须要时调用语言预处理器、编译器、汇编器和连接器。函数

GNU编译系统编译源码:工具

  • 首先,运行C预处理器(cpp),将.c文件翻译成.i文件;
  • 接着,运行C编译器(cc1),将.i文件翻译成ASCII汇编语言文件.s文件;
  • 而后,运行汇编器(as),将.s文件翻译成可重定位目标文件.o文件;
  • 最后,运行连接器(ld),将各.o文件组合起来,建立一个可执行目标文件。

2.静态连接

Unix的静态连接器(static linker)ld,以一组可重位目标文件和命令行参数做为输入,生成一个彻底连接的能够加载和运行的可执行目标文件做为输出。输入的可重定位目标文件由各类不一样的代码和数据节(section)组成。指令在一个节中,初始化的全局变量在另外一个节中,而未初始化的变量又在另一个节中。编码

为了构造可执行文件,连接器必须完成两个主要任务:spa

  • 符号解析(symbol resolution)。目标文件定义和引用符号。符号解析的目的是将每一个符号引用恰好和一个符号定义联系起来。
  • 重定位(relocation)。编译器和汇编器生成从地址0开始的代码和数据节。连接器经过把每一个符号定义与一个存储器位置联系起来,而后修改全部对这些符号的引用,使得它们指向这个存储器位置,从而重定位这些节。

3.目标文件

目标文件有三种形式:可重定位目标文件。能够在编译时与其它可重定位目标文件合并起来,建立一个可执行目标文件。操作系统

  • 可执行目标文件。可被直接拷贝到存储器并执行。
  • 共享目标文件。在加载或运行时被动态地加载到存储器并连接。

编译器和汇编器生成可重定位目标文件(包括共享目标文件)。连接器生成可执行目标文件。
现代Unix系统使用可执行和可连接格式(ELF)。


可重定位目标文件命令行

一个典型的可重定位目标文件包含下面几个节:
.text:已编译程序的机器代码。
.rodata:只读数据。
.data:已初始化的全局C变量。局部C变量在运行时保存在栈中,既不出如今.data节中,也不出如今.bss节中。
.bss:未初始化的全局C变量。翻译

4. 符号和符号表

每一个可重定位目标模块m都有一个符号表,它包含m所定义和引用的符号的信息。3d

  • 在连接器的上下文中,有三种不一样的符号:

    • 一、由m定义并能被其余模块引用的全局符号。全局连接器符号对应于非静态的C函数以及被定义为不带C static属性的全局变量。
    • 二、由其余模块定义并被模块m引用的全局符号。这些符号称为外部符号(external),对应于定义在其余模块中的C函数和变量。
    • 三、只被模块m定义和引用的本地符号。有的本地连接器符号对应于带static属性的C函数和全局变量。
  • 符号表

    • 每一个符号都和目标的某个节相关联,由section字段表示。
    • section字段三个特殊的伪节
      • ABS:不应被重定位的符号。
      • UNDEF:未定义的符号,在本目标模块中引用,但在其余地方定义。
      • COMMON:未被分配位置的未初始化数据目标。
    • Ndx=1表示.test节,Ndx=3表示.data节。

5. 符号解析

  • 多重定义的全局符号

    • 强符号:函数和已经初始化的全局变量
    • 弱符号:未初始化的全局变量code

    • 规则:

      规则1:不容许有多个强符号。
      规则2:若是有一个强符号和多个弱符号,那么选择强符号。
      规则3:若是有多个弱符号,那么从这些弱符号中任意选择一个。
  • 静态库连接

全部的编译系统都提供一种机制,将全部相关的目标模块打包成为一个单独的文件,称为静态库(Linux下是存档文件,Windows下是lib),能够用作连接器的输入。
    • 当连接器构造一个输出的可执行文件时,它只拷贝静态库里被应用程序引用的目标模块。
    • 存档文件:一组链接起来的可重定位目标文件的集合,有一个头部用来描述每一个成员目标文件的大小和位置。存档文件名由后缀.a标识。
    • 连接时加上-static参数:告诉编译器驱动程序,连接器应该构建一个彻底连接的可执行目标文件,它能够加载到存储器并执行,在加载时无需更进一步的连接。

6. 重定位

  • 重定位节和符号定义:

    • 连接器将全部相同类型的节合并为同一类型的新的聚合节,将运行时存储器地址赋给新的聚合节,赋给输入模块定义的每一个节,以及赋给输入模块定义的每一个符号。
    • 此时,程序中的每一个指令和全局变量都有惟一的运行时存储器地址了。
  • 重定位节中的符号引用:

    • 连接器修改代码节和数据节中对每一个符号的引用,使得它们指向正确的运行时地址。
    • 连接器依赖于称为重定位条目的可重定位目标模块中的数据结构。
  • 重定位符号引用

    • 相对引用
    • 绝对引用

7. 可执行目标文件及加载

(1)可执行目标文件

  • C程序开始时是一组ASCII文本文件,已经被转化为一个二进制文件,且这个二进制文件包含加载程序到存储器并运行它所需的全部信息。

  • 段头部表:可执行文件的连续片被映射到连续的存储器段,段头部表描述了这种关系。

(2)加载可执行目标文件

加载器将可执行目标文件中的执行代码和数据从磁盘拷贝到存储器中,而后经过跳转到程序的第一条指令或入口点来运行该程序。这个将程序拷贝到存储器并运行的过程叫作加载。
    Unix程序运行时存储器映像:
                               

  • 用户栈老是最大的合法用户地址开始,向下增加的(向低存储器地址方向增加)。从栈的上部开始的段是为操做系统驻留存储器的部分(也就是内核)的代码和数据保留的。

  • 当加载器运行时,它建立如上图所示的存储器映像。在可执行文件中段头部表的指导下,加载器将可执行文件的相关内容拷贝到代码和数据段。
  • 接下来,加载器跳转到程序的入口点,也就是符号_start的地址。在_start地址处的启动代码(startup code)是在目标文件ctrl.o中定义的,对全部的C程序都是同样的。

8. 动态链接共享库

  • 共享库是一个目标模块,在运行时,能够加载到任意的存储器地址,并和一个在存储器中的程序连接起来。这个过程称为动态连接,是由一个叫作动态连接器的程序来执行的。
  • 共享库也称为共享目标,在Unix系统中一般用.so后缀来表示。微软的操做系统大量地利用了共享库,它们称为DLL(动态连接库)。
  • 共享库是以两种不一样的方式来“共享”的(在Windows中分别称为“隐式连接”和“显示连接”)。
    • 首先,在任何给定的文件系统中,对于一个库只有一个.so文件。全部引用该库的可执行目标文件共享这个.so文件中的代码和数据,而不是像静态库的内容那样被拷贝和嵌入引用它们的可执行的文件中。
    • 其次,在存储器中,一个共享库的.text节 一个副本能够被不一样的正在运行的进程共享。
  • 与位置无关的代码PIC

编译库代码,使得不须要连接器修改库代码就能够在任何地址加载和执行这些代码。
    • 用户对GCC使用-fPIC选项指示GNU生成PIC代码

9. 处理目标文件的工具

  • AR:建立静态库,插入、删除、列出和提取成员。
  • READELF:显示一个目标文件的完整结构,包括ELF头中的编码的全部信息。包含SIZE和NM的功能。
  • OBJDUMP:全部二进制工具之母,可以显示一个目标文件中全部的信息。它最大的做用是反汇编.text节中的二进制指令。
  • LDD:列出一个可执行文件在运行时所须要的共享库。
  • STRINGS:列出一个目标文件中全部可打印的字符串。
  • STRIP:从目标文件中删除符号的信息。
  • NM:列出一个目标文件的符号表中定义的符号。
  • SIZE:目标文件中节的名字和大小。
相关文章
相关标签/搜索