第四部分 静态链接
给出一个简单的例子:a.c b.c 分别如下:
/* a.c */ extern int shared; int main() { int a = 100; swap(&a, &shared); }
/* b.c */ int shared = 1; void swap(int *a, int *b) { *a ^= *b ^= *a ^= *b; }
空间与地址分配
按序叠加:简单的将输入的目标文件按照次序叠加起来.问题是会有长白上千个零散的段,这种做法十分浪费空间.会造成大量的内部碎片.
相似段合并:将相同性质的段合并到一起.
链接两步走:
Step1.空间与地址分配;
Step2.符号解析与重定位
gcc -c a.c b.c
ld a.o b.o -e main -o ab
链接时报错:"__stack_chk_fail" 栈保护机制在起作用.在很早以前,生活很轻松,栈就在那里静静地等着有人来报错,没有保护机制.好在我们可以通过在编译的时候如下命令来关闭gcc的栈保护机制:
gcc -c a.c -o a.o -zexecstack -fno-stack-protector -g
VMA= virtual memory address即虚拟地址. LMA=Load memory Address加载地址 正常情况下两者应该是一样的.
把各个段汇总到一起,就得到了链接后的可执行文件 ab.最后将段中的地址映射到内存的 VMA.交给CPU就可以执行啦.在Linux下,ELF可执行文件的默认地址是 从0x08048000开始分配的.
重定位
在完成空间和地址的分配之后,链接器就进入了符号解析与重定位的步骤,这也是静态链接的核心内容.
使用 objdump -d 参数可以查看到 a.o 的反汇编代码:
objdump -d a.o
程序代码中使用的是VAM,在未进行空间分配之前,main函数的地址是0000000000000000
最左边1列是每条指令的偏移量; 看 黑色部分分别对应了两条指令 ,分别是使用 shared和调用swap函数
E8 FC FF FF FF
这条指令共5个字节,前面的0xE8是操作码,可以从Intel的软件开发手册上查阅得到.这条指令是一条近址相对位移调用指令,后面四个字接是北调能够用函数的相对与调用指令的下一条指令的偏移量.这里的两个地址的都仅仅是临时的替代.当之后链接器会做修正.
同样可以使用objdump -d ab 命令来查看链接之后的反汇编.
重定位表
重定位表保存了所有需要重定位的成员.
代码消重
函数级别的链接, GCC编译器有两个选项"-ffunction-sections"和"-fdata-sections"作用就是将每个函数或变量分别保持到独立的段中.
静态库链接
一种语言的开发环境往往会附带言语库.这些库就是对操作系统的API的包装.+
1.产生系统调用的库函数; 比如 printf函数,在LInux下最终会调用系统的write调用, 在Windows下WriteConsole
2.不产生系统调用的库函数. 比如 strlen函数;
静态库可以简单的看成一组目标文件的集合.比如Linux下最常用的C语言静态库libc位于 /usr/lib/libc.a 它属于glibc项目的一部分.里面包含了很多不同功能的
目标文件,通常通过ar压缩程序压缩成.a文件.