20155218 《信息安全系统设计基础》第十四周学习总结
一、 总结新的收获
1. 链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个过程可被加载到内存并执行。
2. 在现在系统中,链接是由链接器的程序自动执行的,它使得分离编译成为可能。
3. 编译器驱动程序:
main.c
int sum(int *a,int n);
int array[2]={1,2};
int main()
{
int val = sum(array,2);
return val;
}
sum.c
int sum(int *a,int n){
int i,s=0;
for(i =0 ;i < n;i++){
s+=a[i];
}
return s;
}
在Mac环境下,输入如下命令来调用GCC驱动程序:
gcc -o prog main.c sum.c
4. hello.c(源程序)->hello.i(ASCII码的中间文件)->hello.s(ASCII汇编语言文件)->hello.o(可重定位目标文件)->可执行目标文件
(1)预处理:
gcc -E hello.c -o hello.i
(2)编译:
gcc -s hello.i -o hello.s
(3) 汇编:
gcc -c hello.s -o hello.o
5. 静态链接:
- 链接器必须完成的两个主要任务:1.符号解析;2.重定位;
- 目标文件纯粹是字节块的集合;
6. 目标文件:
- 可重定位目标文件:包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件。
- 可执行目标文件:包含二进制代码和数据,其形式可以直接复制到内存并执行;
- 目标共享文件:一种特殊类型的可重定位目标文件,可以在加载或者运行时被动态地加载进内存并链接;
- 编译器和汇编器生成可重定位目标文件。
- 链接器生成可执行目标文件。
7. 可重定位目标文件:
- ELF可重定位目标文件:
.text:已编译程序的机器代码
.rodada:只读数据,比如printf语句中的格式串。
.data:已经初始化的全局C变量。局部变量在运行时保存在栈中。即不再data节也不在bss节
.bss:未初始化的全局C变量。不占据实际的空间,仅仅是一个占位符。所以未初始化变量不需要占据任何实际的磁盘空间。C++弱化BSS段。可能是没有,也可能有。
.symtab:一个符号表,它存放“在程序中定义和引用的函数和全局变量的信息”。
.rel.text:一个.text节中位置的列表。(将来重定位使用的)
.rel.data:被模块引用或定义的任何全局变量的重定位信息。
.debug:调试符号表,其内容是程序中定义的局部变量和类型定义。
.line:原始C源程序的行号和.text节中机器指令之间的映射。
.strtab:一个字符串表.
8. 符号和符号表:
每个重定位目标模块m都有一个符号表,他包含m定义和引用的符号的信息。在链接器的上下文中,有三种不同的符号:
- 由模块m定义并能被其他模块引用的全局变量;
- 由其他模块定义并被模块m引用的全局符号;
- 只被模块m定义和引用的局部符号;
9. 符号表是由汇编器构成的,使用汇编器输出到汇编语言.s文件中的符号.
10. 符号解析:
- 连接器解析符号引用的方法是将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来
- 对于局部符号,编译器只允许每个模块中每个局部变量有一个定义;
- 对于全局符号的引用就会复杂,当编译器遇到一个不是在本模块定义的符号,回假设它由其他的模块定义的,生成一个链接器符号表条目,并把它交给链接器处理。
- 编译如下代码出现无法解析foo的错误:
void foo(void);
int main(){
foo();
return 0;
}
11. 链接器如何解析多重定义的全局符号:
- 不允许有多个同名的强符号;
- 如果有一个强符号和多个弱符号,那么选择强符号;
如果有多个弱符号同名,那么从这些弱符号中任意选择一个;
12. 与静态库链接
- 所有编译系统都提供一种机制,将所有相关的目标模块打包成一个单独的文件,称为静态库。
- 为什么要使用静态库:静态库的提出以解决存在的一些问题,相关的函数可以被编译为独立的目标模块,然后封装称为一个单独的静态库文件。然后,应用程序可以通过在命令行上指定单独的文件名字来使用这些在库中的文件;
- 在linux系统中,静态库以一种称为存档的特殊文件格式存放在磁盘中。存档文件由后缀.a标识;
addvec.c
void addvec(int *x, int *y,
int *z, int n)
{
int i;
for (i = 0; i < n; i++)
z[i] = x[i] + y[i];
}
- multvec.c
void multvec(int *x, int *y,
int *z, int n)
{
int i;
for (i = 0; i < n; i++)
z[i] = x[i] * y[i];
}
- main2.c
#include
#include "vector.h"
int x[2] = {1, 2};
int y[2] = {3, 4};
int z[2];
int main()
{
addvec(x, y, z, 2);
printf("z = [%d %d]\n", z[0], z[1]);
return 0;
}
- 编译链接图:
13. 重定位由两步组成:
- 重定位节和符号定义;
重定位节中的符号引用;
14. 重定位条目:
- R_X86_64_PC32:重定位一个使用32位PC相对地址的引用;
R_X86_64_32:重定位一个使用32位绝对地址的引用;
15. 可执行目标文件:
- 可执行目标文件的格式类似于可重定位目标文件的格式,ELF头描述文件的总体格式;
- 还包括程序的入口点,也就是当程序运行时要执行的第一条指令的地址;
ELF可执行稳紧啊很容易加载到内存中,可执行文件的连续的片被映射到连续的内存段。
16. 动态链接共享库
- 共享库是一个目标模块,在运行或加载时,可以家在到人意的内存地址,并和一个在内存中的程序链接起来。
- 共享库也称为共享目标,在linux系统中通常用.so后缀来表示;
共享库以两种不同的方式来“共享的”的:首先,在任何给定的文件系统中,对于一个库只有一个.so文件。所有引用该库的可执行目标文件共享这个so文件中的代码和数据。其次,在内存中,一个共享库的.text节的一个副本可以被不同的正在运行的进程共享。
17. 从应用程序中加载和链接共享库:
利用接口动态链接libvector.so共享库,然后调用addvec。dll.c
#include
#include
int x[2] = {1, 2};
int y[2] = {3, 4};
int z[2];
int main()
{
void *handle;
void (*addvec)(int *, int *, int *, int);
char *error;
handle = dlopen("./libvector.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "%s\n", dlerror());
exit(1);
}
addvec = dlsym(handle, "addvec");
if ((error = dlerror()) != NULL) {
fprintf(stderr, "%s\n", error);
exit(1);
}
addvec(x, y, z, 2);
printf("z = [%d %d]\n", z[0], z[1]);
if (dlclose(handle) < 0) {
fprintf(stderr, "%s\n", dlerror());
exit(1);
}
return 0;
}
17. 位置无关代码:
- 可以加载而无需重定位的代码称为位置无关代码;
- PIC数据引用;
PIC函数调用;
18. 库打桩机制
- 基本思想:给定一个需要打桩的目标函数,创建一个包装函数,他的原型与目标函数完全一样。
- 编译时打桩:包装函数调用目标函数
- 链接时打桩;
运行时打桩;
二、 同伴反馈
我对同伴的反馈:
郝博雅同学在这次的博客中同样是以问题为导向,在内容里使用问题来引出小节的重点知识,并给出了一些解决的方法。同时,图片的使用也能帮助我们更好的理解,读完之后,受益匪浅。另外,郝博雅同学帮助我调整了我的博客的形式,解决了我的问题。
同伴对我的评价:
这一次的排版明显比上一次更有层次更好看,而且对于详细的知识点的深入学习也很到位~如果能对比较重点的:如强链接、弱链接进行更深入的学习并写上自己的感想就更好了!