【第 6 周】深入理解计算机系统共读心得体会

请大家以回帖的方式将你在共读第一周的心得体会用你自己的话表达出来。

样例:

所在小组

第一组

组内昵称

张三

你的心得体会

可以基于不同知识点进行,有更新请在原回贴更新,每人每周只发一个帖子

一段自己的阐述

第二段自己的阐述

所在小组

第七组

组内昵称

杨文

你的心得体会

链接器将帮助我们理解构造大型程序时,怎么将第三方库、模块的引用是如何进行的。
现在的服务器都是基于 Linux 的,理解好了链接器将帮助我们避免一些编程错误。
链接器将会在加载、运行程序、虚拟内存、分页、内存映射等方面发挥作用。

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

  1. 符号解析
  2. 重定位

我们要认识到链接器的一个基本事实是:目标文件纯粹是字节块的集合

目标文件有以下三种形式:

  • 可重定位目标文件
  • 可执行目标文件
  • 共享目标文件

一个目标模块就是一个字节序列,一个目标文件就是一个以文件形式存放在磁盘中的目标模块。

从贝尔实验室诞生的第一个 Unix 系统使用的是 a.out 格式(直到今天,可执行文件仍然成为 a.out 文件)。而 Windows 使用可移植可执行(Portable Executable PE)格式。MacOS-X 使用 Mach-O 格式。现代 x86-64 Linux 和 Unix 系统使用可执行可链接格式(Executable and Linkable Format, ELF)。

所在小组

第七组

组内昵称

吴奇驴

你的心得体会

  1. 应用程序在运行时加载和链接共享库:linux系统提供接口dlopen;
  2. Java本地接口(Java Native Interface, JNI):允许Java程序调用“本地的”C和C++函数;C函数foo编译到共享库中,java程序调用时候利用dlopen或类似的接口进行动态链接,然后进行调用;
  3. 库打桩机制:允许截获对共享库函数的调用,取而代之执行自己的代码,或者进行一层包装,添加功能;
  4. 打桩:编译时打桩、链接时打桩、运行时打桩(LD_PRELOAD);
  5. 异常:中断(异步、外部I/O设备的信号结果)、陷阱(有意的,系统调用)、故障(错误,可能被修复)、终止(不可恢复致命错误,程序终止);

所在小组

第六组

组内昵称

吴彬

你的心得体会

1、程序编译过程

源文件(.c)->预处理器->中间文件(.i)->编译器->汇编文件(.s)->汇编器->可重定位目标文件->链接器->可执行文件

2、可重定位目标文件:其中rel.text节:text 节中位置的列表

3、异常

  中断:

 陷阱和系统调用:

4、进程

int main()
{
    pid_t pid;
    int x = 1;
    
    pid = Fork();
    if (pid == 0) 
    {   // Child
        printf("I'm the child!  x = %d\n", ++x);
        exit(0);
    }
    
    // Parent
    printf("I'm the parent! x = %d\n", --x);
    exit(0);
}

一次调用,多次返回

所在小组

第六组

组内昵称

杨凯伟

心得体会

链接

链接有助于理解程序如何在机器上运行。文件本文介绍了可执行文件的编码,链接过程,静态和动态库,以及库打桩技术。

编译器/链接器可以输出三种目标文件,这三种目标文件都是二进制文件。

  • 可重定位的目标文件(.o等),由编译器输出,无法加载到内存或运行,需要链接器进一步处理;
  • 可执行的目标文件(.out, .exe等),由链接器输出,可以直接复制到内存并执行
  • 共享目标文件(.so等),动态共享库,可以在加载或者运行时被动态地加载进内存并链接

库打桩机制
库打桩技术可以分别在编译,链接,或动态链接时拦截函数调用,插入用于调试,记录,测试等代码,实现类似装饰器的效果。

  • 编译时打桩
    需要能够访问程序的源代码
  • 链接时打桩
    需要能够访问程序的可重定位对象文件
  • 运行时打桩
    只需要能够访问可执行目标文件

所在小组

静默组

组内昵称

Tang_D

你的心得体会

  1. 编译先把.c文件变成可重定位目标文件,然后通过链接器最后变成可执行文件
  2. 首先把文件符号记录在符号表,同时符号有一个对应符号引用,最后把符号引用的实际运行地址确定好,每个符号的实际运行地址也相应确定。
  3. 每一个全局变量只能有一个强引用,如果全局变量未初始化为弱引用,如果有两个同名弱引用,那么我们会随机挑选一个,这个会导致不确定的行为。
  4. 静态链接是把需要的库函数挑选出来,进行重定向成为可执行文件,但是这样会导致一份代码多次被导入,浪费内存,动态链接技术,只需要生成位置无关的.so文件,在链接时只需做标记,在需要的时候进行链接。
  5. 库打桩技术,可以将所需要调用的库函数换成自己实现的库函数,从而进行debug等操作。
  6. 异常:异常是一种特殊的控制流,不是调用,或者栈入栈出的函数调用,比如I/O需要的是中断,trap则是系统调用,故障可能是缺页等情况,终止则是不可恢复的错误,最终会导致进程终止。

所在小组

第三组

组内昵称

hy

心得体会

链接

笔记

异常控制流

笔记

所在小组

第四组

组内昵称

Helios

心得体会

  • C源码 通过编译器变为 汇编代码 通过汇编器变为 目标代码
  • (目标代码 + 静态连接库)通过链接器 变为可执行代码
  • 可执行代码 通过加载器 加载到内存中,CPU去读取执行

在Linux平台下可执行文件和目标文件使用的是叫ELF格式的文件,这里面主要包含:

  • .text: 代码段和指令段
  • .data: 输出化好的数据
  • .rel.text: 重定位表,即不知道该跳转到哪里,就放在这里面
  • .symtab: 符号表,函数名和地址的映射
  1. 链接器根据输入的所有文件,把所有符号表里面的信息收集起来构成一个全局符号表。
  2. 链接器再根据重定位表,把所有不确定要跳转的地址的代码,根据符号表里面的地址进行修正
  3. 链接器最后把所有的目标文件的对应段进行一次合并,变成最终的可执行代码。

windows下面可执行文件的格式是PE(Portable Executable Format),因为可执行文件的格式和linux的ELF的格式都不一样,所以不能相互解析

所在小组

第五组

组内昵称

锦锐

心得体会

chapter Linker

why learning about linking ?

  • help you build large programs.
  • avoid dangerous programming errors.
  • how language scoping rules are implemented.
  • understand other important system concept.
  • enable you to exploit shared libraries.

why linkers ?

  • Modularity, can build libraries of common functions
  • Efficiency, time and space

obj file and ELF

there are three kind of object file, .o file and a.out file and .so file .

How linkers resolve multiply defined global symbols

  • rule1: multiple strong symbols are not allowed
  • rule2: given a strong symbol and multiple weak symbols, choose the strong symbol
  • rule3: given multiple weak symbols, choose any of the weak symbols .

the linker’s algorithm

interpositionning

summary

  • linking can happen at different times, such as compile time、load time、run time .
  • Case study: Library interpositioning。 【黑魔法】

所在小组

第四组

组内昵称

魏琮

你的心得体会

链接

  • 编译器驱动程序 从源代码main.c sum.c 由C预处理器 将main.c 翻译称为一个ASCII码的中间文件 main.i 接下来驱动程序运行c编译器 ccl 它将main.i 翻译称为一个ASCII汇编语言文件 main.s ,然后驱动程序运行汇编器(as),将main.s 翻译成为一个可重定位目标文件main.o,最后经过链接器ld,输出可执行文件
  • 静态链接 静态链接就是运行之前将多个文件链接成一个文件。
  • 动态链接 在运行过程中去系统里找对应的内容
  • 目标文件
    • 可重定位目标文件 包含二进制代码和数据
    • 可执行目标文件
    • 共享目标文件
  • 重整 c++与java都允许重载方法,同样的函数名,不同的参数,编译器将每个方法和参数列表组合编码称为一个名字,这个叫重整,相反叫恢复。
  • 连接器如何解析多重定义的全局符号
    • 规则1 不允许由多个同名的强符号
    • 规则2 如果有一个强符号和多个弱符号同名,那么选择强符号
    • 规则3 如果由多个弱符号,那么从这些弱符号中任意挑选一个
    • 规则2与规则3的应用会造成一些不易察觉的运行错误
  • 静态库的优点
    • 将编译器的实现与标准函数的实现分离出来

异常控制流

  • ECF 称为异常控制流
  • 在任何情况下,当处理器检测到有时间发生时,他就会通过一张叫做一场表的跳转表,进行一个间接过程调用,到一个专门设计用来处理这类事件的操作系统子程序,当异常处理程序完成处理后,根据引起异常的事件的类型,会发生一下三种情况
    • 处理程序将控制返回给当前指令
    • 处理程序将控制返回给下一个指令
    • 中断程序
  • 异常类别
类别 原因 同步异步 返回行为
中断 来自I/o设备的信号 异步 总是返回下一条指令
陷阱 有意的异常 同步 总是返回下一条指令
故障 潜在可恢复的错误 同步 可能返回当前指令
终止 不可恢复的错误 同步 不会返回
  • 陷阱和系统调用 陷阱最重要的作用就是提供了一个想过程一样的接口,叫做系统调用 syscall n

所在小组

第三组

组内昵称

kippa

心得体会

这一章概念性的东西比较多

  • 链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(拷贝)到存储器中并执行。链接可以执行于编译时,也就是源代码翻译成机器码时,也可以执行于加载时,也就是程序被加载到存储器并执行时,甚至执行于运行时,由应用程序来执行。
  • 像Linux ld程序这样的静态链接器以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的可以加载和运行的可执行目标文件作为输出。输入的可重定位目标文件由各种不同的代码和数据节组成。指令在一个节中,初始化的全局变量在另一个节中,而未初始化的变量又在另外一个节中。
  • 链接器解析符号引用的方法是将每个引用与它输入的可重定位目标文件的符号表中的一个确定符号定义联系起来
  • 所有的编译系统都提供一种机制,将所有相关的目标模块打包成一个单独的文件,叫做静态库

动态库为了解决静态库的一些缺点:

  • 需要定期维护和更新

  • 几乎每个C程序都使用标准IO函数,在运行时,这些函数的代码会被复制到每个运行进程的文本段中,在一个运行上百个进程的典型系统上,这是对稀缺的内存系统资源的极大浪费

共享库:

  • 一个目标模块。在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来,这个过程叫做动态链接,由一个叫做动态链接器的程序来执行。

所属小组

组内昵称

郑伟钊

心得体会

第七章,链接

1.链接:是一个将代码和数据片段组合成一个单一文件(可以被加载到内存中执行)的过程。

0201031161117.jpg

  • 编译时
  • 加载时
  • 运行时

2.链接器的两主要任务:

  • 符号解析
  • 重定位

3.目标文件:

  • 可重定向目标文件:编译时候可以创建合并成一个可执行文件
  • 可执行目标文件:可以直接被加载到内存执行
  • 共享目标文件:运行时可以被动态加载

4.链接可以分:

  • 动:链接
  • 静:静链接
  • 共享:在动态链接的过程中,我们想要“链接”的,不是存储在硬盘上的目标文件代码,而是加载到内存中的共享库。

所在小组

第三组

组内昵称

wyhqaq

心得体会

  • 链接

    • 链接就是将代码和数据加载到内存中。可以执行于编译时,加载时,运行时。
    • 静态库解决了如何让大量相关程序对应用程序可用的问题,但缺点是需要星定期维护和更新,对上层应用不透明。动态链接库则是解决上述问题的新产物。
    • java的本地接口JNI原理,就是将本地C函数编译到一个共享库中。当运行的Java程序调用foo时,解释器利用dlopen接口动态链接和加载foo.so。
    • 库打桩机制。类似于java中的动态代理,具体为截获对共享库函数的调用,创建一个包装函数,它的原型与目标函数完全一样,取而代之执行自己的代码。
  • 异常流控制

    • 异常分类

      中断通常是I/O设备信号

      陷阱一般是syscall n指令,提供用户程序到内核指点的调用

      故障例子:如缺页中断,需要转到故障处理程序修正。

      终止,不可恢复的致命错误

    • 父进程fork出子进程,子进程完全拷贝父进程的数据结构,但具有不同的pid号。

      父进程如果没有回收子进程就终止了,子进程将变成zombie状态,挂载到init(1)上。

      waitpid可用来终止子进程

所在小组

第五组

组内昵称

肖思成

你的心得体会

  • 链接(linking)是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。

  • 链接可执行于编译时,也可执行于加载时,甚至执行于运行时

  • 在早期的计算机系统中,链接是手动执行的。在现代系统中,链接是由叫做链接器(linker)的程序自动执行

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

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

  • 静态链接器(static linker)以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的、可以加载和运行 的可执行目标文件作为输出。

  • 链接器的两个主要任务:符号解析(symbol resolution)和 重定位(relocation)

  • 目标文件有三种形式:可重定位目标文件、可执行目标文件、共享目标文件

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

所在小组

第六组

组内昵称

黄永平

你的心得体会

1、链接器必须完成的两个主要任务:
符号解析:目的是将每个符号引用正好和一个符号定义关联起来;重定位。
2、目标文件有三种形式:可重定位目标文件、可执行目标文件、共享目标文件。
3、对全局符号的符号解析很棘手,因为多个目标文件可能会定义相同名字的全局符号。
4、Linux链接器如何处理多重定义的符号名:
规则1:不允许有多个同名的强符号
规则2:如果有一个强符号和多个弱符号同名,那么选择强符号
规则3:如果有多个弱符号同名,那么从这些弱符号中任意选择一个

规则2、规则3,对于不警觉的程序员来说,很难理解。尤其是如果重复的符号定义还有不同的类型时。

5、当链接器构造一个输出的可执行文件时,它只复制静态库里被应用程序引用的目标模块。

所在小组

第二组

组内昵称

Joey

你的心得体会

第七章 链接

  1. 连接可以发生在编译时(静态链接)、加载时(动态链接)、运行时(dlsym)

  2. .o文件就是一个地址从0开始编址的二进制文件,里面有数据段,代码段和一些用于引导链接过程的数据结构。连接器要做的主要的事情就是将符号和符号引用关联起来(比如其他模块调用本模块的函数),重定位(链接后的地址时运行时地址了,不能再从0开始了,要把一些指针的值(比如&i)也要换一下

  3. ELF格式中已经初始化和未被初始化的全局变量和静态变量分别在.data和.bss段中,而.bss段里仅变量的占位,在程序运行时被初始化

  4. 栈局部变量既不在.data也不在.bss中,因为栈局部变量的分配是随着栈的生的,所以一般栈局部变量不赋初值都是随机值

  5. 由一个.c/.h文件所构成的模块,所有函数和全局变量要么是模块对外不可见(static修饰),要么对外可见(但是其他模块需要知道其声明,所以一般将extern语句放在本模块的.h里提供给其他模块包含使用),可见extern和static在语义上就是冲突的,所以一个变量不可能既是static也是extern。

  6. C++的函数重载是通过符号重整来实现的,链接器可没有办法实现同名符号的解析。

  7. 避免多个模块同时对外共享同名符号,会造成难以调试的bug,最好使用-Werror

  8. 链接静态库时,并不是将静态库里的所有.o都打包到elf格式的可执行文件里了,而是只复制使用到的.o,但是会将使用到的.o依赖的.o也递归的打包,静态库只能在编译时使用。

  9. 动态库不需要被打包到可执行文件里,而是同版本只需要在内存里由一份就行,可以被多个进程所共享,相对于静态库节约了内存。此外动态库可以在链接时或运行时使用。除此之外之外,动态库需要使用额外的数据结构和性能来实现共享库中变量和函数的动态引用(没有免费的午餐)

第8章 异常控制流

  1. 异常处理可以让一个程序回避通常的栈原则,直接打断当前的指令流执行,然后转到某个异常处理程序中

  2. 异常控制流的概念很宽,从处理器内置的异常(比如除0、段错误)、操作系统的(软件中断)、高级语言提供的(try/catch语句块)

  3. 系统调用是通过陷阱指令(老内核是INT $80指令,现代的是有专用的syscall指令)来完成用户态调起内核态代码的,系统调用号和参数都通过寄存器来传递,操作系统需要实现陷阱指令的中断程序。

所在小组

静默组

组内昵称

清风环佩

心得体会

  1. 编译是指编译器读取源程序代码,对其进行词法分析和语法的分析,将高级语言指令转换为相同功能的汇编代码。
  2. 源代码文件的编译过程包含两个主要阶段:
    • 预处理阶段,在正式的编译阶段之前进行,预处理阶段将根据已放置在文件中的预处理指令来修改源文件的内容。主要是对宏定义指令、条件编译指令、头文件包含指令以及特殊符号的处理。其中头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。
    • 编译和优化阶段,编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。汇编是指汇编器把汇编语言代码翻译成目标机器指令的过程,目标文件中所存放的也就是与源程序等效的目标的机器语言代码。
  3. 链接器的主要作用就是符号解析和重定位。

所在小组
第一组

组内昵称
张仁杰

心得体会

在Java和c++中,编译器会将每一个唯一的方法和参数列表组合编码成一个对链接器来说唯一的名字,这种编码叫做重整,而相反的过程叫做恢复,这就是可以使用重载的原因。

共享库是如何做到被多个进程共享的?
一种是:在每个共享库分配一个事先预备的专用地址空间,然后加载器总在这个地址加载共享库,这样存在的问题便是空间利用率不高。
二是:多个进程共享一个共享模块,可以加载而无需重定位的代码称为位置无关代码(PIC)

进程是目前计算机科学中最深刻、最成功的概念之一。

上下文切换

  1. 保存当前进程的上下文
  2. 恢复某个抢先被占的进程被保存的上下文
  3. 将控制传递给这个新恢复的进程
  1. 所在小组: 静默组
  2. 组内昵称:frances
  3. 心得体会

链接器把程序个各个部分联合成一个文件,处理器可以将这个文件加载到内存并且执行。构建大型程序时经常会遇到缺少模块、库或者不兼容的库版本引起的链接器错误,本章学习帮助我们理解链接器如何解析引用等,以及链接问题在哪些情况下会影响程序的性能以及正确性。

静态链接:以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的、可以加载和运行的可执行目标文件作为输出。

  • 符号解析,目标文件定义和引用符号,每个符号对应一个函数、全局变量或静态变量。符号解析的目的是将每个符号引用和定义相关联。
  • 重定位,编译器和汇编器生成从地址0开始的代码和数据节。链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得它们指向这个内存位置。

目标文件三种形式:可重定位目标文件、可执行目标文件、共享目标文件

可重定位目标文件格式:(ELF)

  • .data:已初始化的全局和静态C变量;(局部变量在运行时被保存在栈中,既不在.data也不在.bss)
  • .bss:未初始化的全局或静态C变量;仅仅是占位符不占用实际空间,运行时在内存分配,初始值为0
  • .symtab:符号表,存放在程序中定义和引用的函数和全局变量的信息

链接器解析符号引用的方法是通过将每个引用和它输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来。对于和引用定义在相同模块的局部符号的引用解析比较简单;而对全局变量解析时,当编译器遇到一个不是在当前模块定义的符号,会假设该符号是在其他某个模块定义的,生成一个链接器符号表条目交给链接器处理,如果链接器在它的任何输入模块中都找不到就输出报错信息并终止。

在编译时全局符号有两种,strong和weak,汇编器把这个信息隐含编码在可重定位目标文件的符号表中,函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号。

  • 不允许有多个重名的强符号
  • 如果一个强符号和多个弱符号同名,选择强符号
  • 如果多个弱符号同名,任选一个

静态库需要定期维护和更新,如果应用程序想要使用一个库的最新版本,必须以某种当时了解到该库的更新情况,然后显示地将他们的程序与更新后的库重新链接。 另外,函数代码需要复制到每个运行进程的文本段中,导致内存资源的浪费。
为了解决静态库缺陷引入了共享库,它是一个目标模块,在运行或加载时可以加载到任意的内存地址,并和一个在内存中运行的程序链接起来,这个过程称为动态链接。(DLL动态链接库)

  • 分发软件:共享库生成新版本后,用户可以下载并替代当前版本,在下一次运行程序时自动链接和加载新版本的共享库
  • 构建高性能Web服务器,基于动态链接更有效完善地生成动态内容,而不是使用fork和execve在子进程的上下文中运行函数(函数会一直缓存在服务器地址空间中)。

位置无关代码:可以把他们加载到内存的任何位置而无需链接器修改,无限多个进程可以共享一个共享模块的单一副本。 GCC -fpic
Linux链接器支持库打桩技术,允许截获对共享库函数的调用,取而代之执行自己的代码。打桩可以发生在编译时、链接时或者程序被加载和执行的运行时。

所在小组

第六组

组内昵称

慎思明辨笃行

你的心得体会

第七章 链接

链接是将各种代码和数据片段搜集并组合成为一个单一文件的过程。
链接器使得分离编译成为可能。

学习链接的目的

  • 理解链接器将帮助你构造大型程序
  • 理解链接器将帮助你避免一些危险的编程错误
  • 理解链接器将帮助你理解语言的作用域规则是如何实现的
  • 理解链接器将帮助你理解其他重要的系统概念
  • 理解链接器将使你能够利用共享库

7.1 编译器驱动程序

7.2 静态链接

静态链接器:以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的、可以加载和运行的可执行目标文件作为输出。
链接器主要任务

  • 符号解析:日标文件定义和引用符号,每个符号对应于一个函数、一个全局变量或一个静态变量(即C语言中任何以static属性声明的变量)。符号解析的目的是将每个符号引用正好和一个符号定义关联起来。
  • 重定位:编译器和汇编器生成从地址0开始的代码和数据节,链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得它们指向这个内存位置。链接器使用汇编器产生的重定位条目的详细指令,不加甄别地执行这样的重定位。

7.3 目标文件

目标文件形式

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

编译器和汇编器生成可重定位目标文件(包括共享目标文件)。
链接器生成可执行目标文件。
目标文件是按照特定的目标格式来组织的,各个系统的目标文件格式都不同。

7.4 可重定位目标文件

.text:已编译程序的机器代码
.rodata:只读数据,比如printf语句中的格式串和开关语句的跳转表
.data:已初始化的全局和静态C变量。局部C变量在运行时被保存在栈中,既不出现在.data节中,也不出现在.bss节中。
.bss:未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量。在目标文件中这个节不占据实际的空间,它仅仅是一个占位符。目标文件格式区分已初始化和未初始化变量是为了空间效率:在目标文件中,未初始化变量不需要占据任何实际的磁盘空间。运行时,在内存中分配这些变量,初始值为0。
.symtab:一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。一些程序员错误地认为必须通过-g选项来编译一个程序,才能得到符号表信息。实际上,每个可重定位目标文件在.symtab中都有一张符号表(除非程序员特意用STRIP命令去掉它)。然而,和编译器中的符号表不同,.symtab符号表不包含局部变量的条目。
.rel.text:一个.text节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面,调用本地函数的指令则不需要修改。注意,可执行目标文件中并不需要重定位信息,因此通常省略,除非用户显式地指示链接器包含这些信息。
.rel.data:被模块引用或定义的所有全局变量的重定位信息。一般而言,任何已初始化的全局变量,如果它的初始值是一个全局变量地址或者外部定义函数的地址,都需要被修改。
.debug:一个调试符号表,其条目是程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的C源文件。只有以-g选项调用编译器驱动程序时,才会得到这张表。
.line:原始C源程序中的行号和.text节中机器指令之间的映射。只有以-g选项调用编译器驱动程序时,才会得到这张表。
.strtab:一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。字符串表就是以nu11结尾的字符串的序列。

7.5 符号和符号表

每个可重定位目标模块m都有一个符号表
由模块m定义并能被其他模块引用的全局符号
全局链接器符号对应于非静态的C函数和全局变量
有其他模块定义并被模块m引用的全局符号
这些符号称为外部符号,对应于在其他模块中定义的非静态C函数和全局变量
只被模块m定义和引用的局部符号
他们对应于带static属性的C函数和全局变量。这些符号在模块m中任何位置可见,但是不能被其他模块引用。

认识到本地链接器符号和本地程序变量不同是很重要的

.symtab中的符号表不包含对应于本地非静态程序变量的任何符号。这些符号在运行时再栈中管理。链接器对此类符号不感兴趣。

定义为带有C static属性的本地过程变量是不在栈中管理的,相反,编译器在.data或.bss中为每个定义分配空间,并在符号表中创建一个有唯一名字的本地链接器符号

COMMON 未初始化的全局变量
.bss 未初始化的静态变量,和初始化为0的全局或静态变量

7.6 符号解析

链接器解析符号引用的方法是将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来。
编译器只允许每个模块中每个局部符号有一个定义。
静态局部变量也会有本地链接器符号,编译器还要确保他们拥有唯一的名字。

对全局符号的引用解析就棘手得多。当编译器遇到一个不是在当前模块中定义的符号(变量或函数名)时,会假设该符号是在其他某个模块中定义的,生成一个链接器符号表条目,并把它交给链接器处理。如果链接器在它的任何输入模块中都找不到这个被引用符号的定义,就输出一条(通常很难阅读的)错误信息并终止。

7.7 重定位

重定位将合并输入模块,并为每个符号分配运行时地址
重定位节和符号定义
链接器将所有相同类型的节合并为同一类型的新的聚合节
链接器将运行时内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号
程序中的每条指令和全局变量都有唯一的运行时内存地址
重定位节中的符号引用
链接器修改代码节和数据节中对每个符号的引用,依赖于可重定位目标模块中称为重定位条目的数据结构

7.8 可执行目标文件

它还包括程序的入口点,也就是当程序运行时要执行的第一条指令的地址。

.init节定义了一个小函数_init,程序初始化代码会调用它。

因为可执行文件是完全链接的,所以不再需要.rel节。

程序头部表描述了可执行文件的连续的片被映射到连续的内存段的映射关系。

7.9 加载可执行目标文件

加载:加载器将可执行目标文件中的代码和数据从磁盘复制到内存中,然后通过跳转到程序的第一条指令或入口点来运行该程序。

7.10 动态链接共享库

共享库是一个目标模块,在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来。这个过程称为动态链接,是由一个叫做动态链接器的程序来执行的。

共享库也称为共享目标,在Linux系统中通常用.so后缀来表示。

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

动态链接是一项强大有用的技术。下面是一些现实世界中的例子
分发软件。微软Windows应用的开发者常常利用共享库来分发软件更新。他们生成一个共享库的新版本,然后用户可以下载,并用它替代当前的版本。下一次他们运行应用程序时,应用将自动链接和加载新的共享库。
构建高性能Web服务器。许多Web服务器生成动态内容,比如个性化的Web页面、账户余额和广告标语。早期的Web服务器通过使用fork和execve创建一个子进程,并在该子进程的上下文中运行CGI程序来生成动态内容。然而,现代高性能的Web服务器可以使用基于动态链接的更有效和完善的方法来生成动态内容。
其思路是将每个生成动态内容的函数打包在共享库中。当一个来自Web浏览器的请求到达时,服务器动态地加载和链接适当的函数,然后直接调用它,而不是使用fork和execve在子进程的上下文中运行函数。

7.13 库打桩机制

库打桩(library interpositioning),它允许你截获对共享库函数的调用,取而代之执行自己的代码。

基本思想:给定一个需要打桩的目标函数,创建一个包装函数,它的原型与目标函数完全一样。使用某种特殊的打桩机制,你就可以欺骗系统调用包装函数而不是目标函数了。包装函数通常会执行它自己的逻辑,然后调用目标函数,再将日标函数的返回值传递给调用者。

打桩可以发生在编译时、链接时或当程序被加载和执行的运行时。

7.14 处理目标文件的工具

在linux系统中有大量可用的工具可以帮助你理解和处理目标文件。特别地,GNU binutils包尤其有帮助,而且可以运行在每个Linux平台上。
AR:创建静态库,插入、删除、列出和提取成员。
STRINGS:列出一个目标文件中所有可打印的字符串。
STRIP:从日标文件中删除符号表信息。
NM:列出一个目标文件的符号表中定义的符号。
SIZE:列出日标文件中节的名字和大小。
READELF:显示一个目标文件的完整结构,包括ELF头中编码的所有信息。包含SIZE和NM的功能。
OBJDUMP:所有二进制工其之母。能够显示一个目标文件中所有的信息。它最大的作用是反汇编.text节中的二进制指令。
LDD:列出一个可执行文件在运行时所需要的共享库。

第八章 异常控制流

从给处理器加电,到断电为止,处理器做的工作其实就是不断地读取并执行一条条指令。这些指令的序列就叫做 CPU 的控制流(control flow)。最简单的控制流是“平滑的”,也就是相邻的指令在存储器中是相邻的。当然,控制流不总是平滑的,不总是一条接一条地执行,总会有出现改变控制流的情况。我们知道的程序内部状态改变的机制有两条:

跳转和分支
调用和返回
这些机制局限于程序本身的控制。当系统状态(system state)发生改变的时候,以上机制就不能很好地应对复杂的情况,例如:

数据从磁盘或者网络适配器到达
有一条指令执行了除以零的操作
用户按下 ctrl+c
系统内部的计时器到时间
现代系统通过使控制流发生突变来应对这些情况。这种机制叫做异常控制流(exceptional control flow)。异常控制流发生在计算机系统的各个层次。