请大家以回帖的方式将你在共读第一周的心得体会用你自己的话表达出来。
样例:
所在小组
第一组
组内昵称
张三
你的心得体会
可以基于不同知识点进行,有更新请在原回贴更新,每人每周只发一个帖子
一段自己的阐述
第二段自己的阐述
…
请大家以回帖的方式将你在共读第一周的心得体会用你自己的话表达出来。
样例:
第一组
张三
可以基于不同知识点进行,有更新请在原回贴更新,每人每周只发一个帖子
…
第七组
杨文
链接器将帮助我们理解构造大型程序时,怎么将第三方库、模块的引用是如何进行的。
现在的服务器都是基于 Linux 的,理解好了链接器将帮助我们避免一些编程错误。
链接器将会在加载、运行程序、虚拟内存、分页、内存映射等方面发挥作用。
为了构造可执行文件,链接器必须完成两个主要任务:
我们要认识到链接器的一个基本事实是:目标文件纯粹是字节块的集合。
目标文件有以下三种形式:
一个目标模块就是一个字节序列,一个目标文件就是一个以文件形式存放在磁盘中的目标模块。
从贝尔实验室诞生的第一个 Unix 系统使用的是 a.out 格式(直到今天,可执行文件仍然成为 a.out 文件)。而 Windows 使用可移植可执行(Portable Executable PE)格式。MacOS-X 使用 Mach-O 格式。现代 x86-64 Linux 和 Unix 系统使用可执行可链接格式(Executable and Linkable Format, ELF)。
第七组
吴奇驴
第六组
吴彬
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);
}
一次调用,多次返回
第六组
杨凯伟
链接有助于理解程序如何在机器上运行。文件本文介绍了可执行文件的编码,链接过程,静态和动态库,以及库打桩技术。
编译器/链接器可以输出三种目标文件,这三种目标文件都是二进制文件。
库打桩机制
库打桩技术可以分别在编译,链接,或动态链接时拦截函数调用,插入用于调试,记录,测试等代码,实现类似装饰器的效果。
静默组
Tang_D
第四组
Helios
在Linux平台下可执行文件和目标文件使用的是叫ELF格式的文件,这里面主要包含:
windows下面可执行文件的格式是PE(Portable Executable Format),因为可执行文件的格式和linux的ELF的格式都不一样,所以不能相互解析
第五组
锦锐
there are three kind of object file, .o file and a.out file and .so file .
第四组
魏琮
类别 | 原因 | 同步异步 | 返回行为 |
---|---|---|---|
中断 | 来自I/o设备的信号 | 异步 | 总是返回下一条指令 |
陷阱 | 有意的异常 | 同步 | 总是返回下一条指令 |
故障 | 潜在可恢复的错误 | 同步 | 可能返回当前指令 |
终止 | 不可恢复的错误 | 同步 | 不会返回 |
第三组
kippa
这一章概念性的东西比较多
动态库为了解决静态库的一些缺点:
需要定期维护和更新
几乎每个C程序都使用标准IO函数,在运行时,这些函数的代码会被复制到每个运行进程的文本段中,在一个运行上百个进程的典型系统上,这是对稀缺的内存系统资源的极大浪费
共享库:
郑伟钊
第三组
wyhqaq
链接
异常流控制
异常分类
中断通常是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
第七章 链接
连接可以发生在编译时(静态链接)、加载时(动态链接)、运行时(dlsym)
.o文件就是一个地址从0开始编址的二进制文件,里面有数据段,代码段和一些用于引导链接过程的数据结构。连接器要做的主要的事情就是将符号和符号引用关联起来(比如其他模块调用本模块的函数),重定位(链接后的地址时运行时地址了,不能再从0开始了,要把一些指针的值(比如&i)也要换一下
ELF格式中已经初始化和未被初始化的全局变量和静态变量分别在.data和.bss段中,而.bss段里仅变量的占位,在程序运行时被初始化
栈局部变量既不在.data也不在.bss中,因为栈局部变量的分配是随着栈的生的,所以一般栈局部变量不赋初值都是随机值
由一个.c/.h文件所构成的模块,所有函数和全局变量要么是模块对外不可见(static修饰),要么对外可见(但是其他模块需要知道其声明,所以一般将extern语句放在本模块的.h里提供给其他模块包含使用),可见extern和static在语义上就是冲突的,所以一个变量不可能既是static也是extern。
C++的函数重载是通过符号重整来实现的,链接器可没有办法实现同名符号的解析。
避免多个模块同时对外共享同名符号,会造成难以调试的bug,最好使用-Werror
链接静态库时,并不是将静态库里的所有.o都打包到elf格式的可执行文件里了,而是只复制使用到的.o,但是会将使用到的.o依赖的.o也递归的打包,静态库只能在编译时使用。
动态库不需要被打包到可执行文件里,而是同版本只需要在内存里由一份就行,可以被多个进程所共享,相对于静态库节约了内存。此外动态库可以在链接时或运行时使用。除此之外之外,动态库需要使用额外的数据结构和性能来实现共享库中变量和函数的动态引用(没有免费的午餐)
第8章 异常控制流
异常处理可以让一个程序回避通常的栈原则,直接打断当前的指令流执行,然后转到某个异常处理程序中
异常控制流的概念很宽,从处理器内置的异常(比如除0、段错误)、操作系统的(软件中断)、高级语言提供的(try/catch语句块)
系统调用是通过陷阱指令(老内核是INT $80指令,现代的是有专用的syscall指令)来完成用户态调起内核态代码的,系统调用号和参数都通过寄存器来传递,操作系统需要实现陷阱指令的中断程序。
静默组
清风环佩
所在小组
第一组
组内昵称
张仁杰
心得体会
在Java和c++中,编译器会将每一个唯一的方法和参数列表组合编码成一个对链接器来说唯一的名字,这种编码叫做重整,而相反的过程叫做恢复,这就是可以使用重载的原因。
共享库是如何做到被多个进程共享的?
一种是:在每个共享库分配一个事先预备的专用地址空间,然后加载器总在这个地址加载共享库,这样存在的问题便是空间利用率不高。
二是:多个进程共享一个共享模块,可以加载而无需重定位的代码称为位置无关代码(PIC)
进程是目前计算机科学中最深刻、最成功的概念之一。
上下文切换
链接器把程序个各个部分联合成一个文件,处理器可以将这个文件加载到内存并且执行。构建大型程序时经常会遇到缺少模块、库或者不兼容的库版本引起的链接器错误,本章学习帮助我们理解链接器如何解析引用等,以及链接问题在哪些情况下会影响程序的性能以及正确性。
静态链接:以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的、可以加载和运行的可执行目标文件作为输出。
目标文件三种形式:可重定位目标文件、可执行目标文件、共享目标文件
可重定位目标文件格式:(ELF)
…
链接器解析符号引用的方法是通过将每个引用和它输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来。对于和引用定义在相同模块的局部符号的引用解析比较简单;而对全局变量解析时,当编译器遇到一个不是在当前模块定义的符号,会假设该符号是在其他某个模块定义的,生成一个链接器符号表条目交给链接器处理,如果链接器在它的任何输入模块中都找不到就输出报错信息并终止。
在编译时全局符号有两种,strong和weak,汇编器把这个信息隐含编码在可重定位目标文件的符号表中,函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号。
静态库需要定期维护和更新,如果应用程序想要使用一个库的最新版本,必须以某种当时了解到该库的更新情况,然后显示地将他们的程序与更新后的库重新链接。 另外,函数代码需要复制到每个运行进程的文本段中,导致内存资源的浪费。
为了解决静态库缺陷引入了共享库,它是一个目标模块,在运行或加载时可以加载到任意的内存地址,并和一个在内存中运行的程序链接起来,这个过程称为动态链接。(DLL动态链接库)
位置无关代码:可以把他们加载到内存的任何位置而无需链接器修改,无限多个进程可以共享一个共享模块的单一副本。 GCC -fpic
Linux链接器支持库打桩技术,允许截获对共享库函数的调用,取而代之执行自己的代码。打桩可以发生在编译时、链接时或者程序被加载和执行的运行时。
第六组
慎思明辨笃行
链接是将各种代码和数据片段搜集并组合成为一个单一文件的过程。
链接器使得分离编译成为可能。
学习链接的目的
静态链接器:以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的、可以加载和运行的可执行目标文件作为输出。
链接器主要任务
目标文件形式
编译器和汇编器生成可重定位目标文件(包括共享目标文件)。
链接器生成可执行目标文件。
目标文件是按照特定的目标格式来组织的,各个系统的目标文件格式都不同。
.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结尾的字符串的序列。
每个可重定位目标模块m都有一个符号表
由模块m定义并能被其他模块引用的全局符号
全局链接器符号对应于非静态的C函数和全局变量
有其他模块定义并被模块m引用的全局符号
这些符号称为外部符号,对应于在其他模块中定义的非静态C函数和全局变量
只被模块m定义和引用的局部符号
他们对应于带static属性的C函数和全局变量。这些符号在模块m中任何位置可见,但是不能被其他模块引用。
认识到本地链接器符号和本地程序变量不同是很重要的
.symtab中的符号表不包含对应于本地非静态程序变量的任何符号。这些符号在运行时再栈中管理。链接器对此类符号不感兴趣。
定义为带有C static属性的本地过程变量是不在栈中管理的,相反,编译器在.data或.bss中为每个定义分配空间,并在符号表中创建一个有唯一名字的本地链接器符号
COMMON 未初始化的全局变量
.bss 未初始化的静态变量,和初始化为0的全局或静态变量
链接器解析符号引用的方法是将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来。
编译器只允许每个模块中每个局部符号有一个定义。
静态局部变量也会有本地链接器符号,编译器还要确保他们拥有唯一的名字。
对全局符号的引用解析就棘手得多。当编译器遇到一个不是在当前模块中定义的符号(变量或函数名)时,会假设该符号是在其他某个模块中定义的,生成一个链接器符号表条目,并把它交给链接器处理。如果链接器在它的任何输入模块中都找不到这个被引用符号的定义,就输出一条(通常很难阅读的)错误信息并终止。
重定位将合并输入模块,并为每个符号分配运行时地址
重定位节和符号定义
链接器将所有相同类型的节合并为同一类型的新的聚合节
链接器将运行时内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号
程序中的每条指令和全局变量都有唯一的运行时内存地址
重定位节中的符号引用
链接器修改代码节和数据节中对每个符号的引用,依赖于可重定位目标模块中称为重定位条目的数据结构
它还包括程序的入口点,也就是当程序运行时要执行的第一条指令的地址。
.init节定义了一个小函数_init,程序初始化代码会调用它。
因为可执行文件是完全链接的,所以不再需要.rel节。
程序头部表描述了可执行文件的连续的片被映射到连续的内存段的映射关系。
加载:加载器将可执行目标文件中的代码和数据从磁盘复制到内存中,然后通过跳转到程序的第一条指令或入口点来运行该程序。
共享库是一个目标模块,在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来。这个过程称为动态链接,是由一个叫做动态链接器的程序来执行的。
共享库也称为共享目标,在Linux系统中通常用.so后缀来表示。
动态链接是一项强大有用的技术。下面是一些现实世界中的例子
分发软件。微软Windows应用的开发者常常利用共享库来分发软件更新。他们生成一个共享库的新版本,然后用户可以下载,并用它替代当前的版本。下一次他们运行应用程序时,应用将自动链接和加载新的共享库。
构建高性能Web服务器。许多Web服务器生成动态内容,比如个性化的Web页面、账户余额和广告标语。早期的Web服务器通过使用fork和execve创建一个子进程,并在该子进程的上下文中运行CGI程序来生成动态内容。然而,现代高性能的Web服务器可以使用基于动态链接的更有效和完善的方法来生成动态内容。
其思路是将每个生成动态内容的函数打包在共享库中。当一个来自Web浏览器的请求到达时,服务器动态地加载和链接适当的函数,然后直接调用它,而不是使用fork和execve在子进程的上下文中运行函数。
库打桩(library interpositioning),它允许你截获对共享库函数的调用,取而代之执行自己的代码。
基本思想:给定一个需要打桩的目标函数,创建一个包装函数,它的原型与目标函数完全一样。使用某种特殊的打桩机制,你就可以欺骗系统调用包装函数而不是目标函数了。包装函数通常会执行它自己的逻辑,然后调用目标函数,再将日标函数的返回值传递给调用者。
打桩可以发生在编译时、链接时或当程序被加载和执行的运行时。
在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)。异常控制流发生在计算机系统的各个层次。