linux内核分析 第七周读书笔记

第七章 链接 

1、链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载到存储器并执行。

2、链接可以执行于编译时,加载时,运行时。

7.1编译器驱动程序

  1、大多数编译系统提供编译驱动程序,它代表用户在需要时调用语言预处理器、编译器、汇编器和链接器。

7.2 静态链接

  1、像Unix ld程序这样的静态链接器以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的可以加载运行的可执行目标文件作为输出。

  2、输入的可重定位目标文件由各种不同的代码和数据节组成。

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

      (1)符号解析。目标文件定义和引用符号。符号解析的目的是将每个符号引用刚好和一个符号定义联系起来。

      (2)重定位。编译器和汇编器生成从地址0开始的代码和数据节。

7.3 目标文件

  1、目标文件有三种形式:

    (1)可重定位目标文件;

    (2)可执行目标文件;

    (3)共享目标文件;

  2、编译器和汇编器生成可重定位目标文件(包括共享目标文件)。链接器生成可执行目标文件

  3、各个系统之间,目标文件格式都不相同。直到今天,可执行文件仍然被称为a.out文件。

7.4 可重定位目标文件

  1. 一个典型的ELF可重定位目标文件的格式P451。ELF头(ELF header)以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。其中包括ELF头的大小、目标文件的类型(如可重定位、可执行或是共享的)、机器类型(如IA32)、节头部表的文件偏移,以及节头部表中的条目大小和数量。不同的节的位置和大小是由节头部表描述的,其中目标文件中每个节都有一个固定大小的条目。
  2. 夹在ELF头和节头部表之间的都是节。一个典型的ELF可重定位目标文件包含下面几个节:

    .text 已编译程序的机器代码

    .rodata 只读数据

    .data 已初始化的全局C变量。局部C变量在运行时保存在栈中,既不出现在.data节中 ,也不出现在.bss节中。

    .bass 未初始化的全局C变量。在目标文件中这个节不占据实际的空间,它仅仅是一个占位符。目标文件格式区分初始化和未初始化变量是为了空间效率:在目标文件中,未初始化变量不需要占据任何实际的磁盘空间。

    .symtab 一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。每个可重定位目标文件在.symtab中都有一张符号表 。

    .rel.text 一个.text节中位置的列表,当链接器吧这个目标文件和其他文件结合时,需要修改这些位置。一般而言,任何调用外部函数或引用全局变量的指令都需要修改。另一方面,调用本地函数的指令则不需要修改。注意,可执行目标文件中并不需要重定位信息,因此通常省略,除非用户显示第指示链接器包含这些信息。

    .rel.data 被模块引用或定义的任何全局变量的重定位信息。一般而言,任何已初始化的全局变量,如果它的初始值是一个全局变量地址或者外部定义函数的地址,都需要被修改。

    .debug 一个调试符号表,其条目是程序总定义的局部变量和类型定义,程序中定义和引用的 全局变量,以及原始的C源文件。

    .line 原始C源文件中的行号和.text节中机器指令之间的映射。

    .strtab 一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。

7.5符号和符号表

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

  2、在链接器的上下文中,有三种不同的符号:

    (1)由m定义并能被其他模块引用的全局符号;

    (2)由其他模块定义并被模块m引用的全局符号(外部符号);

    (3)只被模块m定义和引用的本地符号;

7.6符号解析

  1、链接器解析符号引用的方法是将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义联系起来。

7.6.1 链接器如何解析多重定义的全局符号

  1、在编译时,编译器向汇编器输出每个全局符号,或者是强或者是弱,而汇编器把这个信息隐含的编码在可重定位目标文件的符号表里。

  2、函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号。

  3、根据强弱符号的定义,Unix链接器使用下面的规则来处理多重定义的符号:

    规则1:不允许有多个强符号。

    规则2:如果有一个强符号和多个弱符号,那么选择强符号。

    规则3:如果有多个弱符号,那么从这些弱符号中任意选择一个。

7.6.2 与静态库链接

  1、所有的编译系统都提供一种机制,将所有相关的目标模块打包成为一个单独的文件,称为静态库。当链接器构造一个输出的可执行文件时,它只拷贝静态库里被应用程序引用的目标模块。

  2、在链接时,链接器将只拷贝程序引用的目标模块。

  3、在Unix系统中,静态库以一种称为存档的特殊文件格式存放在磁盘中。

  4、存档文件是一组连接起来的可重定位目标文件的集合。存档文件名由后缀.a标识。

  5、链接时加上-static参数告诉编译器驱动程序,链接器应该构建一个完全链接的可执行目标文件,它可以加载到存储器并执行,在加载时无需更进一步的链接。

7.6.3 链接器如何使用静态库来解析引用

  1、在符号解析的阶段,链接器从左到右按照他们在编译器驱动程序命令行上出现的相同的顺序来扫描可重定位目标文件和存档文件。(驱动程序自动将命令行中所有的.c文件翻译为.o文件)

  2、在这次扫描中,链接器维持一个可重定位目标文件的集合E(这个集合中的文件会被合并起来形成可执行文件),一个未解析的符号(即引用了但是尚未定义的符号)集合U,以及一个在前面输入文件中已定义的符号集合D。初始时,E、U和D都是空的。

7.7   重定位

  1、重定位由两步组成:

    (1)重定位节和符号定义

    (2)重定位节中的符号引用

  2、无论何时汇编器遇到对最终位置未知的目标引用,它就会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。

  3、代码的重定位条目放在.rel.text中,已初始化数据的重定位条目放在.rel.data中。

  4、PC值通常是存储器中下一条指令的地址。

7.8 可执行目标文件

  1. 可执行目标文件的格式类似于可重定位目标文件的格式。ELF头部描述文件的总体格式。它还包括程序的入口点,也就是当程序运行时要执行的第一条指令的地址。.text 、.rodata和.data 节和可重定位目标文件中的节是相似的,除了这些节已经被重定位到它们最终的运行时存储器地址以外。.init节定义了一个小函数,叫做_init,程序的初始化代码会调用它。因为可执行文件是完全链接的(已被重定位了),所以它不再需要.rel节。
  2. ELF可执行文件被设计得很容易加载到存储器,可执行文件的连续的片被映射到连续的存储器段。段头部表描述了这种映射关系。

7.9   加载可执行目标文件

  1、加载器将可执行目标文件中的代码和数据从磁盘拷贝到存储器中,然后通过跳转到程序的第一条指令入口点来运行该程序。这个将程序拷贝到存储器并运行的过程叫做加载。

  2、在32位Linux系统中,代码段总是从地址0x08048000处开始。

  3、数据段是在接下来的下一个4KB对齐的地址处。运行时堆在读/写段之后接下来的第一个4KB对齐的地址处,并通过调用malloc库往上增长。

  4、还有一个段是为共享库保留的。

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

7.10 动态链接共享库

共享库是致力于解决静态库缺陷的一个现代创新产物。共享库是一个目标模块,在运行时,可以加载到任意的存储器地址,并加一个在存储器中的程序链接起来。这个过程称为动态链接,是由一个叫做动态链接器的程序来执行的。共享库也称为共享目标,在Unix系统中通常用.so后缀来表示。

7.11 从应用程序中加载和连接共享库

  1、dlopen函数加载和链接共享库filename。

  2、dlsym函数的输入是一个指向前面已经打开共享库的句柄和一个符号名字,如果该符号存在,就返回符号的地址,否则返回NULL。

  3、如果没有其他共享库正在使用这个共享库,dlclose函数就卸载该共享库。

  4、dlerror函数返回一个字符串,它描述的是调用dlopen、dlsym或者dlclose函数时发生的最近的错误,如果没有错误发生,就返回NULL。

7.12 与位置无关的代码(PIC)

  1、共享库的一个主要目的就是允许多个正在运行的进程共享存储器中相同的库代码,因而节约宝贵的存储器资源。

  2、不需要链接器修改库代码就可以在任何地址加载和执行这些代码。这样的代码叫做与位置无关的代码(PIC)。

  3、ELF编译系统使用一种有趣的技术,叫做延迟绑定,将过程地址的绑定推迟到第一次调用该过程时。

  4、延迟绑定是通过两个数据结构之间简洁但又有些复杂的交互来实现的,这两个数据结构是:GOT和过程链接表(PLT)。GOT是.data节的一部分,PLT是.text节的一部分。

7.13 处理目标文件的工具

  1、在Unix系统中有大量可用的工具可以帮助理解和处理目标文件。

  AR:创建静态库,插入、删除、列出和提取成员。

  STRINGS:列出一个目标文件中所有可打印的字符串。

  STRIP:从目标文件中删除符号的信息。

  NM:列出一个目标文件的符号表中定义的符号。

  SIZE:目标文件中节的名字和大小。

  READELF:显示一个目标文件的完整结构,包括ELF头中的编码的所有信息。包含SIZE和NM的功能。

  OBJDUMP:所有二进制工具之母,能够显示一个目标文件中所有的信息。它最大的作用是反汇编.text节中的二进制指令。

  LDD:列出一个可执行文件在运行时所需要的共享库。

7.14 小结

  1、链接可以在编译时由静态编译器来完成,也可以在加载时和运行时由动态链接器来完成。

  2、链接器处理称为目标文件的二进制文件,它有三种不同的形式:可重定位的、可执行的和共享的。

  3、链接器的两个主要任务是符号解析和重定位。

  4、静态链接器是由像GCC这样的编译驱动器调用的。

  5、多个目标文件可以被连接到一个单独的静态库中。

  6、加载器将可执行文件的内容映射到存储器,并运行这个程序。

  7、被编译为位置无关代码的共享库可以加载到任何地方,也可以在运行时被多个进程共享。

posted @ 2016-04-10 20:49  20125221银雪纯  Views(146)  Comments(0Edit  收藏  举报