2017-2018-1 学号 《信息安全系统设计基础》第1周学习总结
[博客目录]
教材学习内容总结
本周学习的主要为教材中的第一章和第七章:
第一章主要内容:
- 了解编译系统。书本从一个helloworld程序说起,从编辑到运行,每一个环节中系统是如何工作的,我主要了解到从源程序需要经过多个阶段(由低级到高级)才能实现一个程序。
- 了解编译系统的益处:可以优化程序性能,理解程序出错的原因,避免安全漏洞等等。
- 硬件组成,这个我在汇编课程的学习中有所了解。
- 了解一个程序在硬件系统是如何运行的。从CPU 的寄存器到总线再到主存储器,这是一个系统而复杂的过程。
- 了解存储层次。大(慢)-> 小(快)
- 进程和线程。为了让不同的指令和程序合理的占用硬件资源而抽象出的概念,是操作系统管理硬件的关键。
第七章主要内容:
链接(linking) 是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(或被拷贝)到存储器并执行。链接可以执行于编译时(compile time) ,也就是在源代码被翻译成机器代码时:也可以执行于加载时(load time) ,也就是在程序被加载器(loader) 加载到存储器并执行时:甚至执行于运行时(run time) ,由应用程序来执行。在早期
的计算机系统中,链接是手动执行的。在现代系统中,链接是由叫做链接器(linker) 的程序自动执行的。
一个运行Linux 的x86 系统,使用标准的ELF 目标文件格式。
编译器驱动程序
-
大多数编译系统提供编译驱动程序,它代表用户在需要时调用语言预处理器、编译器、汇编器和链接器。比如,要用GNU 编译系统构造示例程序,我们就要通过在外壳中输人下列命令行来调用GCC 驱动程序:
gcc -02 -g -0 p main.c swap.c
-
将C 源程序main.c 翻译成一个ASCII 码的中间文件main.i:
cpp [other arguments] main.c /tmp/main.i
-
接下来,驱动程序运行C 编译器(ccl) ,它将main.i 翻译成一个ASCII 汇编语言文件main.s
ccl /tmp/main.i main.c -02 [other arguments] -0 /tmp/main.s
-
驱动程序经过相同的过程生成swap.o.最后,它运行链接器程序ld,将main和swap以及一些必要的系统目标文件组合起来,创建一个可执行目标文件.
ld -0 p [system object files .d argsJ /tmp/main.o /tmp/swap.o
静态链接
以一组可重定位目标文件和命令行参数作为输人,生成一个完全链接的可以加载和运行的可执行目标文件作为输出。
为了构造可执行文件,链接器必须完成两个主要任务:
- 符号解析(symbol resolution) 。目标文件定义和引用符号。符号解析的目的是将每个符号引用刚好和一个符号定义联系起来。
- 重定位(relocation) 。编译器和汇编器生成从地址。开始的代码和数据节。
目标文件
-
目标文件有三种形式:
- 可重定位目标文件。包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件。
- 可执行目标文件。包含二进制代码和数据,其形式可以被直接拷贝到存储器并执行。
- 共享目标文件。一种特殊类型的可重定位目标文件,可以在加载或者运行时被动态地加载到存储器并链接。
-
链接器生成可执行目标文件。从技术上来说,一个目标模块(object module) 就是一个字节序列,而一个目标文件(object)的就是一个存放在磁盘文件中的目标模块。
可重定位目标文件
-
ELF 头(ELF header) 以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。包括:
- ELF 头的大小
- 目标文件的类型(如可重定位、可执行或者是共享的〉
- 机器类型〈如IA32)
- 节头部在(section header table) 的文件偏移
- 节头部表中的条目大小和数量。
-
一个典型的ELF 可重定位目标文件包含下面几个节:
. text: 已编译程序的机器代码。
.rodata: 只读数据,比如printf 语句中的格式串开关语句的跳转表
.data: 已初始化的全局C 变量
.bss: 未初始化的全局C 变量。在目标文件中这个节不占据实际的空间,它仅仅是一个占位符。
.symtab: 一个符号哀,它存放在程序中定义和引用典型的ELF 可重定位目标文件的函数和全局变量的信息。
. rel . text :一个.text 节中位置的列表,当链接器把这个目标文件和其他文件结合时,需要修改这些位置。
.rel.data: 被模块引用或定义的任何全局变量的重定位信息。
.debug: 一个调试符号表,其条目是程序中定义的局部变量和类型定义, 程序中定义和引用的全局变量,以及原始的C 源文件。
.line: 原始C 源程序中的行号和.text 节中机器指令之间的映射。
.strtab: 一个字符串表,其内容包括.symtab 和.debug 节中的符号表,以及节头部中的节名字。
符号和符号表
-
每个可重定位目标模块 m 都有一个符号表,它包含m 所定义和引用的符号的信息。在链接器的上下文中,有三种不同的符号:
- 由m 定义并能被其他模块引用的全局符号。全局链接器符号对应于非静态的C以及被定义为不带C static 属性的全局变量。
- 由其他模块定义并被模块m 引用的全局符号。这些符号称为外部符号,对应于定义在其他模块中的C 函数和变量。
- 只被模块m 定义和引用的本地符号。有的本地链接器符号对应于带static 属性的C 函数和全局变量。这些符号在模块m 中随处可见,但是不能被其他模块引用。目标文件中对应于模块m 的节和相应的源文件的名字也能获得本地符号。
-
name 是字符串表中的字节偏移,指向符号的以null 结尾的字符串名字.value 是符号的地址。对于可重定位的模块来说, value 是距定义目标的节的起始位置的偏移。
-
每个符号都和目标文件的某个节相关联,由section 字段表示,该字段也是一个到节头部表的索引.有三个特殊的伪节(pseudo section) ,它们在节头部表中是没有条目的: ABS 代表不该被重定位的符号; UNDEF 代表未定义的符号,也就是在本目标模块中引用,但是却在其他地方定义的符号; COMMON 表示还未被分配位置的未初始化的数据目标.
符号解析
链接器解析符号引用的方法是将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义联系起来.编译器只允许每个模块中每个本地符号只有一个定义。编译器还确保静态本地变量,也会有本地链接器符号,拥有唯一的名字。
-
根据强弱符号的定义, Unix 链接器使用下面的规则来处理多重定义的符号:
- 规则1 :不允许有多个强符号。
- 规则2 :如果有一个强符号和多个弱符号,那么选择强符号。
- 规则3 :如果有多个弱符号,那么从这些弱符号中任意选择一个。
-
所有的编译系统都提供一种机制,将所有相关的目标模块打包成为一个单独的文件,称为静态库(static ),它可以用做链接器的输入。将所有的标准C 画数都放在一个单独的可重定位目标模块中(如libc中),应用程序员可以把这个模块链接到他们的可执行文件中:
gcc main.c /usr/lib/libc.o
-
缺点是系统中每个可执行文件现在都包含着一份标准函数集合的完全拷贝,这对磁盘空间是很大的浪费。我们可以通过为每个标准函数创建一个独立的可重定位文件,把它们存放在一个为大家都知道的目录中来解决其中的一些问题
gcc main.c /usr/lib/printf.o /usr/lib/scanf.o
-
在Unix 系统中,静态库以一种称为存档(archive) 的特殊文件格式存放在磁盘中。。存档文件是一组连接起来的可重定位目标文件的集合,有一个头部用来描述每个成员目标文件的大小和位置。存档文件名由后缀.a 标识。为了创建这个可执行文件,我们要编译和链接输入文件main.o
gcc -02 -c main2 .c
gcc -static -0 p2 main2 .o ./libvector.a
重定位
-
重定位由两步组成:
- 重定位节和符号定义。在这一步中,链接器将所有相同类型的节合并为同一类型的新的聚合节。
- 重定位节中的符号引用。在这一步中,链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。
-
当汇编器生成一个目标模块时,它并不知道数据和代码最终将存放在存储器中的什么位置。它也不知道这个模块引用的任何外部定义的函数或者全局变量的位置。代码的重定位条目放在.rel.text 中。已初始化数据的重定位
条目放在.rel.data 中。 -
两种最基本的重定位类型:
- R_386_PC32: 重定位一个使用32 位PC 相对地址的引用.一个PC 相对地址就是距程序计数器(PC) 的当前运行时值的偏移量。当CPU 执行一条使用PC 相对寻址的指令时,它就将在指令中编码的32 位值上加上PC 的当前运行时值,得到有效地址。
- R_386_32: 重定位一个使用32 位绝对地址的引用。通过绝对寻址, CPU 直接使用在指令中编码的32 位值作为有效地址,不需要进一步修改。
可执行目标文件
执行目标文件的格式类似于可重定位目标文件的格式。ELF 头部描述文件的总体格式。它还包括程序的入口点(Centry point) ,也就是当程序运行时要执行的第一条指令的地址。
.init 节定义了一个小函数,叫做init ,程序的初始化代码会调用它。因为可执行文件是完全链接的(已被重定位了),所以它不再需要.rel。
加载可执行目标文件
-
要运行可执行目标文件p ,可以在Unix 外壳的命令行中输入它的名字:
. /p
-
因为p 不是一个内置的外壳命令,所以外壳会认为p 是一个可执行目标文件,通过调用某个驻留在存储器中称为加载器(loader) 的操作系统代码来运行它.加载器将可执行目标文件中的代码和数据从磁盘拷贝到存储器中,然后通过跳转到程序的第一条指令或入口点来运行该程序。这个将程序拷贝到存储器并运行的过程叫做加截(loading) 。
-
在32 位Linu系统中,代码段总是从地址Ox08048000 处开始。数据段是在接下来的下一个4KB 对齐的地址处。运行时堆在读/写段之后接下来的第一个4KB 对齐的地址处,并通过调用malloc 库往上增长。
动态链接共事库
共享库是致力于解决静态库缺陷的一个现代创新产物。共享库是一个目标模块,在运行时,可以加载到任意的存储器地址,并和一个在存储器中的程序链接起来。这个过程称为动态链接(dynamic linking) ,是由一个叫做动态链接器(dynamic linker) 的程序来执行的。
共享库也称为共享目标 ,在Unix 系统中通常用. s。后缀来表示。微软的操作系统大量地利用了共享库,它们称为DLL (动态链接库)。
-
在任何给定的文件系统中,对于一个库只有一个.5。文件。所有引用该库的可执行目标文件共享这个.5。文件中的代码和数据,而不是像静态库的内容那样被拷贝和嵌人到引用它们的可执行的文件中。其次,在存储中,一个共享库的.text 节的一个副本可以被不同的正在运行的进程共享.
-
给链接器如下特殊指令:
gcc -sbared -ÎPIC -0 libvector.so addvec.c multvec.c
//-fPIC 选项指示编译器生戚与位置无关的代码 -5hared 选项指示链接器创建一个享的目标文件。
gcc -0'p2 main2 .c ./libvector.so
//这样就创建了一个可执行目标文件p2
- 然后,动态链接器通过执行下面的重定位完成链接任务:
- 重定位libc.5 文本和数据到某个存储器段。
- 重定位lib飞Tector.5 文本和数据到另一个存储器段。
- 重定位p2 中所有对由libc.5和libvector.5 定义的符号的引用。
教材每章提问汇总
要求:快速浏览一遍教材,课本每章提出至少一个自己不懂的或最想解决的问题并在期末回答这些问题
- 第1章 计算机系统漫游:一个程序从编写到执行的过程在计算机中是怎么实现的?
- 第2章 信息的表示和处理:如何理解计算机的算术运算?
- 第3章 程序的机器级表示:如何理解汇编在计算机运算中的地位?
- 第4章 处理器体系结构:为什么要虚构出Y86指令系统?
- 第5章 优化程序性能:如何根据处理器的原理优化程序?
- 第6章 存储器层次结构:存储器为何要采取这种分层结构?
- 第7章 链接:链接的原理及实现
- 第8章 异常控制流:为什么计算机要使用进程这个概念?
- 第9章 虚拟内存:虚拟内存的优点的?
- 第10章 系统级I/O:C的标准I/O库与Linux 文件I/O的区别?
- 第11章 网络编程:Web服务器是通过什么原理实现的?
- 第12章 并发编程:指令级并行,线程级并行,数据级并行的区别?
教材学习中的问题和解决过程
我在学习第七章链接的时候遇到了一些问题:
- 问题1:教材P465提到的传统静态链接和共享库的动态链接,这两种链接有什么区别?
- 问题1解决方案:我通过网络查询,了解了以下信息:
- 动态链接库(Dynamic Link Library,缩写为DLL)是一个可以被其它应用程序共享的程序模块,其中封装了一些可以被共享的例程和资源。
- 静态链接是指把要调用的函数或者过程链接到可执行文件中,成为可执行文件的一部分。
- 他们的区别就是:动态链接所调用的函数代码并没有被拷贝到应用程序的可执行文件中去,而是仅仅在其中加入了所调用函数的描述信息(往往是一些重定位信息)。仅当应用程序被装入内存开始运行时,在Windows的管理下,才在应用程序与相应的DLL之间建立链接关系。当要执行所调用DLL中的函数时,根据链接产生的重定位信息,Windows才转去执行DLL中相应的函数代码。
- 我的理解:静态链接程序的函数和过程的代码就在程序的exe文件中,该文件包含了运行时所需的全部代码。而动态链接所调用的函数代码并没有被拷贝到应用程序的可执行文件中去,而是在其他位置。
- 问题2:应用程序中加载和链接共享库
这个是我在浏览博客的时候无意中看到的,也算是学到了一点知识。
linux系统为动态链接器提供了一个简单的接口,允许应用程序在运行时加载和链接共享库。
#include <dlfcn.h>
void *dlopen(const char * filename, int floag);
//成功,返回指向句柄的指针,若出错则为NULL
void * dlsym(void *handle, char *symbol);
//第一个参数是上面函数返回的句柄的指针,第二个参数是符号的名字
//成功,返回指向句柄的指针,若出错则为NULL
int dlclose(void *handle);
//如果没有其他共享库正在使用这个共享库,那么就卸载该共享库。
//成功返回0,出错返回-1
const char* dlerror(void);
//上面的3个函数运行之后,运行这个函数,可以看看最近发生的最近的错误,如果没有错误,就返回NULL
代码调试中的问题和解决过程
本周没有写代码
- 安装本科所用的虚拟机。
- 建立码云项目,建立本科源代码管理体系。
本周结对学习情况
- 结对学习博客
20155302 - 结对学习图片
- 结对学习内容
- 教材第一章、第七章
代码托管
感悟
本周是开学第三周,前两周为实习,相当于这一周是整个学期新的开始,这个开始是忙碌而又充实的。
这周每节课都开始学习新知识,并且都需要进行一些准备工作,工作量比较高。而又恰逢校庆,作为一个参加两场节目的演职人员,我必需投入大量精力去保持节目效果,一遍遍练习。在这不断重复的练习中,我想到了上个学期娄老师和我们说的一个观念:学习一门语言需要有一定的代码量。这和我们每天保持一定强度的训练以保证节目效果是有相似原理的。如果我们不通过练习掌握每一个动作每一个表情,就无法把我们电科院人的精神面貌表现正确的展现出来,而学习知识若不通过练习,则如无源之水,过后就忘记了,忘记了就说什么也没有了。
这周为了校庆付出了很多,牺牲了很多学习和休息的时间,导致之后需要花很多时间补习,但我认为这是值得的,因为通过校庆我不仅认识到了许多有趣的人,也再次代表学院展现了新一代学生的风采。今后我的重心也将重回学习生活之中,将本学期的每门课程扎实巩固的学习到位。
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 0/0 | 1/1 | 10/10 |
-
计划学习时间:10小时
-
实际学习时间:10小时