CSAPP–第七章–链接(上)

Aki 发布于 2022-11-29 359 次阅读


【CSAPP-深入理解计算机系统】7

CSAPP - 第七章:链接_TRUTHBUAA的博客-CSDN博客_csapp 链接

概述、

链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程。这个文件可被加载(拷贝)到内存中并执行:

  • 链接可以执行于编译时(compile time),也就是源代码翻译成机器码时。
  • 也可以执行于加载时(load time),也就是程序被加载器加载到内存并执行时。
  • 甚至执行于运行时(run time),由应用程序来执行。

链接是由叫做链接器(linker)的程序自动执行的。链接器的出现,使得分离编译成为可能,我们不用将一个大型的应用程序组织为一个巨大的源文件,而是把它分解成更小、更好管理的模块。可以独立的修改和编译这些模块。当我们改变这些模块中的一个时,我们只要单独地编译它,并将它重新链接到应用上,而不用编译其他文件。

编译器驱动程序、

例如下面有两个源文件

//main.cpp

#include<iostream>
using namespace std;
int sum(int*p,size_t len);   //另一个文件的函数
int main()
{
    int array[] = {1,2,3};
    cout<<sum(array,3)<<endl;
    return 0;
}
//sum.cpp

int sum(int*p,size_t len)
{
   int res{0};
   for(int*q = p;q <p+len;++q)
   {
        res +=*q;
   }
   return res;
}

大多数编译系统提供 编译器驱动程序(compiler driver),它代表用户在需要时调用语言处理器cpp,编译器ccl,汇编器as,链接器ld。比如使用 GNU 编译系统构造实例程序:

g++ -Og -Wall -o prog  sum.cpp main.cpp   //一步到位生成可执行文件prog

下图概括了驱动程序将示例程序从 ASCII 码源文件翻译为可执行目标文件的过程。

  • 步骤1:驱动程序运行C预处理器(cpp),它将C源程序 main.c 翻译成一个 ASCII 码中间文件:main.i
g++ -E -Og -o main.i main.cpp
g++ -E -Og -o sum.i sum.cpp
  • 步骤2:驱动程序运行C编译器(cc/cc1),将 main.i 文件翻译为汇编文件 main.s
g++ -S -Og main.i -o main.s
g++ -S -Og sum.i -o sum.s
  • 步骤3:驱动程序运行汇编器(as),将 main.s 翻译为可重定位目标文件 main.o
g++ -c -Og main.s -o main.o
g++ -c -Og sum.s -o sum.o
  • 步骤4:驱动程序运行链接器(ld),将 main.o 和 sum.o 以及一些必要的系统目标文件组合起来,创建一个可执行目标文件 prog
g++ -o prog main.o sum.o

步骤5:在 Linux shell 命令行输入一下命令运行可执行文件

linux> ./prog

上面的是分别编译每一个文件的方法,还有直接编译全部文件的方法。

g++ -Og -Wall -o prog *.cpp

以上两种方法相比较,第二种方法编译时需要所有文件重新编译,而第一种方法可以只重新编译修改的文件,未修改的文件不用重新编译。

shell 调用操作系统中一个叫做 加载器(loader)的函数,它将可执行文件 prog 中的代码和数据复制到内存中,然后将控制转移到这个程序的开头。

静态链接、

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

  1. 符号解析(symbol resolution):将每个 符号引用 和一个 符号定义 关联起来。每个符号定义对应于 一个函数一个全局变量 或者 一个静态变量
  2. 重定位(relocation):编译器和汇编器生成从地址0开始的代码和数据节。链接器通过把每个 符号定义 与 一个 内存位置 关联起来,从而重定位这些节,然后修改对这些符号的引用,使得它们指向这个内存位置。(通过汇编器生成的 重定位条目 的详细指令执行重定位)

目标文件、

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

Linux目标文件格式:可执行可链接格式(Executable and Linkable Format, ELF).
Windows目标文件格式:可移植可执行格式(Portable Executable,PE)

目标文件有三种形式:

  • 可重定位目标文件(.o):包含二进制代码和数据,其形式可以在编译时和其他可重定位目标文件合并起来,创建一个可执行目标文件。
  • 可执行目标文件(.out):包含二进制代码和数据,其形式可以被直接复制到内存并执行。
  • 共享目标文件(.so):一种特殊类型的可重定位目标文件,可以在加载或者运行时被动态的加载进内存并链接。

可重定位目标文件、

ELF(Executable and Linkable Format)可重定位目标文件的格式,Linux下的目标文件和可执行文件都按照该格式进行存储,夹在ELF头和节头部表之间的都是节。一个典型的ELF可重定位目标文件包含下面几个节:

  • .text:已编译程序的机器代码。
  • .rodata:只读数据段,此段的数据不可修改,存放常量。比如printf语句中格式串和开关语句的跳转表。
  • .data:已初始化的全局和静态C变量。局部C变量在运行时被保存在栈中,既不出现在.data节中,也不在.bss节中。
  • .bss:未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量。在目标文件中这个节不占据实际的空间,它仅仅是一个占位符。在目标文件中,未初始化变量不需要占据任何实际的磁盘空间。运行时,在内存中分配这些变量,初始值为0,这样提高了空间效率。
  • .symtab:符号表,它存放在程序中定义和引用的函数和全局变量的信息。
  • .rel.text:一个.text节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。一般而言,任何调用外部函数或引用全局变量的指令都需要修改。调用本地函数的指令则不需要修改。可执行目标文件中并不需要重定位信息,因此通常忽略。
  • .rel.data:被模块引用或定义的全局变量的重定位信息。一般任何已初始化的全局变量,如果它的初始值是一个全局变量地址或外部定义函数的地址,都需要修改。
  • .debug:一个调试符号表,其条目是程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的C源程序。
  • .line:原始C源程序中的行号和.text节中机器指令之间的映射。
  • .strtab:一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。