跳至正文

Linux库静态替换解析:为什么覆盖动态链接库so会导致进程闪退

  • Linux

总结

遇到一个这样的问题,我最近把公司git ci自动化的流程向更先进的开源社区的方式推进了一点,我们修改了服务端so库,将新编译的so用cp指令覆盖掉旧的so会直接导致所有进程闪退。

但是我们发现先rm旧的文件,再复制新的文件不会导致闪退,这个就非常纳闷了,处于不理解和好奇就深入研究了一下,最终总结是这样:

这个闪退应该是分了几个流程:

  1. 加载so的时候是把整个so用文件映射到内存里,然后利用文件系统的lazy_load读内存
  2. cp的时候会复用旧的inode,操作系统对这个文件的索引会被改变
  3. 程序读取到原来没有的函数就会触发系统的load,然后去对应的偏移量读对应的block
  4. 闪退

rm不闪退是因为:

  1. 旧的so因为还被引用(记录在INode上),所以不会立马删除对应的block
  2. 新的so会使用新的inode
  3. 程序中断读取文件的时候,会读取到旧文件的数据

内核中mmap对应的结构:
file

可以看到其中有一个struct file表明一个打开的文件套接口,进一步看这个接口是这样:
file
整体的脉络相对就理清楚了。

扩展:链接库

众所周知,一般库文件会分为两种,静态库和动态库。

静态库是在编译时将库的代码打包成。a文件,最终链接到可执行文件中。

动态库在编译时需要开启-fPIC开启内存无关的编译方式编译成so文件,在程序运行时动态加载。

静态链接库,它会伴随可执行文件运行被加载,它已经被ld链接器把库和执行文件放在一起了,我们将ELF文件映射到相应的运行地址,然后将PC寄存器指向entry即可。

动态链接库的加载过程就比较复杂,可以看参考[7]文章的内容,讲的非常细致,简单地说动态链接库是真的动态,100个进程共用一个动态链接库,这个库在内存中实际占用的大小也只有一份。
它在内核中的实现是按照制度的方式映射同一个文件中的部分(如动态代码段),这个过程中会创造引用计数。

非常详细的解释可以读参考[7]!!!

参考文章

[1], https://cloud.tencent.com/developer/article/1040842
[2], https://www.linuxidc.com/Linux/2011-01/31622.htm
[3], https://www.cs.toronto.edu/~simon/html/understand/node33.html
[4], https://elixir.bootlin.com/linux/latest/source/include/linux/fs.h
[5], https://zhuanlan.zhihu.com/p/401446080
[6], https://zhuanlan.zhihu.com/p/287863861
[7], https://blog.ideawand.com/2020/02/15/how-does-linux-shared-library-versioning-works/
[8], https://blog.csdn.net/weixin_44966641/article/details/120631079

End.

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

目录