为什么不把所有的文件都编译到一个可执行文件中:
1. 效果:分成多个模块,有利于开发不同的优秀的模块,开发不同的库。
2. 效率:改代码时,只需要重新编译一个模块,替换即可。
链接器做的2件事:
1. 符号解析。
2. 重定位:在重定位之前,每个模块中的函数和数据只对应了在本模块的偏移。链接器将所有的模块的函数和数据进行合并,并把他们重定位。
目标文件有三种,所有的目标文件都是elf格式的:
1. 可重定位目标文件(.o):
2. 可执行目标文件(.out可执行文件):
3. 共享目标文件(.so):
.p .out .so 都是elf文件
elf文件格式:
![](https://i-blog.csdnimg.cn/blog_migrate/1d993ee145c437275bd12b46be64dfbb.jpeg)
int func1()
{
static int x = 0;
xxx
return 0;
}
int func2()
{
static int x = 0;
xxx
return 0;
}
func1和func2中的x只是在函数内有效,存储在.data段中。符号可能时x_func1,x_func2
多重定义的符号:
已初始化的全局变量优先级高于未初始化的全局变量
链接器的规则:
1. 多个强符号的定义,报错。
2. 如果有一个强符号和多个弱符号同名,则选择强符号
3. 如果有多个弱符号同名,则选择弱符号(如果使用gcc的-fno-common选项,则多个弱符号同名则会报错)(或时殷弘-Werror选项,将所有警告变成错误)
强弱符号可能导致的bug:
![](https://i-blog.csdnimg.cn/blog_migrate/650e142039e412ea9e31df7cdf0a2e60.jpeg)
可执行目标文件和进程虚拟地址空间的对应关系:
![](https://i-blog.csdnimg.cn/blog_migrate/ca0d557ec4c118b4ac99afd10b0f7bf0.jpeg)
所有的代码段都是从0x400000开始的。
所有的栈底端地址都是2的48次方-1(64位机)或3G(32位机)
动态申请内存时,如果申请<128k内存,则申请的内存位于堆区。若申请>128k,则申请内存位于堆和栈之间的某处。
readelf -s xxx.so | grep FUNC
查看so文件的符号表中的函数
当使用dlclose卸载某个so,但是so里头开了线程,必须把线程先退出,否则调用dlclose就会出现问题,会coredump
练习:
1. dlopen模拟上面的coredump
2. 库打桩