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)组成。指令在一个节中,初始化的全局变量在另一个节中,而未初始化的变量又在另外一个节中。

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

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

3.目标文件

目标文件有三种形式:可重定位目标文件。可以在编译时与其它可重定位目标文件合并起来,创建一个可执行目标文件。

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

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

可重定位目标文件

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

4. 符号和符号表

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

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

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

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

5. 符号解析

  • 多重定义的全局符号

    • 强符号:函数和已经初始化的全局变量
    • 弱符号:未初始化的全局变量
    • 规则:
      规则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:目标文件中节的名字和大小。
时间: 2024-10-22 18:36:09

20135302魏静静——《深入理解计算机系统》第7章 学习笔记的相关文章

《深入理解计算机系统》第一章学习笔记

信息就是位+上下文 源程序:就是一个由0和1组合的位(bit)序列,8位组成一字(byte),每个字节表示某个文本字符. 系统中所有的信息--包括磁盘文件.存储器中的程序.存储器中存放的用户数据以及网络上传送的数据,都是由一串位表示的.区分不同数据对象的唯一方法是我们读到这些数据对象时的上下文. C语言的起源: 由Dennis Ritchie在1969年~1973年创建的. 美国国家标准学会(American National Standards Institute,ANSI)在1989年颁布了

《深入理解计算机系统》 第一章读书笔记

最近开始啃CSAPP,买了英文版,看得比较慢.只有先理解系统才能在更多方面学的更明其实质 Chapter1: * 一份hello.c代码的编译分为四个阶段:1.Pre-processor:编译预处理,就是把#include后面的库代码加入到程序中,生成hello.i的文件. 2.Complier:用编译器把hello.i的C代码翻译成汇编语言,并生成:hello.s文件.(汇编语言是高级语言转为机器码的一个中间过程) 3.Assembler:汇编机把汇编语言翻译成机器二进制代码,生成hello.

深入理解计算机系统之存储器层次结构学习笔记

一.存储技术 (一)随机访问存储器 随机访问寄存器(RAM)分为静态随机访问寄存器(SRAM)和动态随机访问寄存器(DRAM).静态RAM可以作为高速缓存寄存器,动态RAM可以用作主存以及图形系统的帧缓冲区.静态RAM将每一个位存储在一个双稳态的存储器单元里,构成静态RAM 的电路可以无限期的保持在两个不同的电压配置或状态之一.动态RAM将每一个位存储为对电容的充电,所以动态RAM要比静态RAM对干扰的敏感度更高.构成动态RAM的电路被干扰后就不会恢复了. 1 传统的DRAM 常规DRAM芯片中

《深入理解计算机系统》第四周学习笔记

一.知识点总结 1.信息存储 练习题2.4 0x503c+0x8=0x5044 0x503c-0x40=0x4ffc 0x503c+64=0x503c+0x40=0x507c 0x50ea-0x503c=0xae 1)字长:指明整数和指针数据的标称大小.一个字长为w的机器的虚拟地址范围为0~2^(w-1),程序最多访问2^w个字节 int .char 4字节,单精度float 字节,双精度double 8字节 2)小端法:最低有效字节在最前面的顺序存储 大端法:最高有效字节在最前面的顺序存储 练

20135302魏静静——Linux课程期中总结

Linux期中总结 Linux课程第一周实验及总结:[http://www.cnblogs.com/20135302wei/p/5218607.html] 冯诺依曼体系结构的核心思想是存储程序计算机.在计算机中有两种指令,一是用户指令,一是系统调用. 当用户使用计算机时,计算机根据其汇编的指令一步步运行,当使用系统调用完后,再返回用户模式,保证系统的稳定. 程序中执行call的堆栈变化 汇编基础 通用寄存器 16位  32位 AX    eax 累加器BX    ebx 基址寄存器CX    e

《深入理解计算机系统》 Chapter 7 读书笔记

<深入理解计算机系统>Chapter 7 读书笔记 链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(货被拷贝)到存储器并执行. 链接的时机 编译时,也就是在源代码被翻译成机器代码时 加载时,也就是在程序被加载器加载到存储器并执行时 运行时,由应用程序执行 链接器使分离编译称为可能. 一.编译器驱动程序 大部分编译系统提供编译驱动程序:代表用户在需要时调用语言预处理器.编译器.汇编器和链接器. 1.将示例程序从ASCⅡ码源文件翻译成可执行目标文件的步骤 (1)运

深入理解计算机系统(序章)------谈程序员为什么要懂底层计算机结构

万丈高楼平地起,计算机系统就像程序员金字塔的地基.理解了计算机系统的构造原理,在写程序的道路上才能越走越远.道理LZ很早就懂了,可是一直没下定决心好好钻研,或许是觉得日常工作中根本用不到这些,又或许是每次拿起书看到那些复杂的底层架构,看到存储器,寄存器,CPU,总线等等这些概念就头大.总之,由于各种各样的原因,对这块的知识一直没有认真花时间去钻研.那么你可能会问,那你写这篇博客的题目不就是准备学习这方面的知识吗?是的,LZ 准备下定决心钻研了,至于原因如下: ①.经常用一些不知其所以然的技术,会

《深入理解计算机系统》第二章习题2_66

最近打算把<深入理解计算机系统>再读一遍,说实话这本书读多少遍都不嫌多,每读一遍都会有收获.这次决心把书中的习题整个过一遍,并把其中我认为比较典型的.有意思的写城博文记录一下,恩,这就是这篇博文的由来.恳请各路大神拍砖. 一. 问题描述(鉴于我这不忍直视的翻译水平,我就直接贴书中的问题描述了): Generate mask indicating leftmost 1 in x. Assume w = 32. For example 0xFF00 -> 0x8000, and 0x6600

20135302魏静静——linux课程第八周实验及总结

linux课程第八周实验及总结 实验及学习总结 1. 进程切换在内核中的实现 linux中进程切换是很常见的一个操作,而这个操作是在内核中实现的. 实现的时机有以下三个时机: 中断处理过程(包括时钟中断.I/O中断.系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule(): 内核线程可以直接调用schedule()进行进程切换,也可以在中断处理过程中进行调度,也就是说内核线程作为一类的特殊的进程可以主动调度,也可以被动调度: 用户