引言
做 Pwn 题时,经常会需要切换 libc,总会看见诸如 libc.so.6 的名称,一直不理解它背后的含义,今天学习了 Linux 下共享库的组织才恍然大悟。
在说 libc.so.6 之前,需要先了解共享库版本号的概念和 so-name 命名机制。
共享库版本号
版本号类型
共享库一般会不断更新以修复 bug、更改接口。有些更新是向后兼容的;有些更新是不兼容的,会导致依赖该库的程序无法运行或需要重新编译才能运行。
根据这些更新的兼容性,划分为不同的版号:主版本号(Major Version Number)、次版本号(Minor Version Number)和发布版本号(Release Version Number)。
主版本号表示库的重大升级。不同主版本号的库之间互不兼容,需要更改接口,并重新编译。依赖于旧共享库的程序可能需要在系统中保留一份旧版的共享库才能运行。
次版本号表示库的增量升级,即增加一些新的接口符号,并保持原有符号不变。依赖于低次版本号共享库的程序可以在高次版本号的共享库下正常运行。
发布版本号表示库的一些错误的修正、性能的改进等,接口不做变化。不同发布版本号之间完全兼容。
共享库文件命名规则
Linux 有一套规则来命名系统中的每一个共享库,它规定共享库的文件命名规则如下:
libname.so.x.y.z
即前缀"lib"+库名称+后缀".so"+三个数字组成的版本号,其中,x 表示主版本号,y 表示次版本号,z 表示发布版本号。
例如,libfoo.so.2.6.1 表示的就是版本号为 2.6.1 的共享库 foo。
但也存在一些不遵守这套命名规定的,比如最基本的 C 语言库 Glibc 使用 libc-x.y.z.so 这种命名方式。
SO-NAME 命名机制
新的操作系统,包括 Solaris 和 Linux,普遍采用一种叫做 SO-NAME 的命名机制,就是把共享库的文件名去掉次版本号和发布版本号,只保留主版本号。
例如,一个共享库叫 libfoo.so.2.6.1,那么它的 SO-NAME 就是 libfoo.so.2。
在 Linux 系统中,系统会为每个共享库在它所在的目录创建一个跟它的 ”SO-NAME” 一样的软链接指向它。当共享库更新时,这个软链接会始终指向主版本号相同、次版本号和发布版本号最新的那个库文件。
例如,目录中同时有 /lib/libfoo.so.2.6.1 和 /lib/libfoo.so.2.5.3 的两个不同版本的共享库,软链接 /lib/libfoo.so.2 就会指向 /lib/libfoo.so.2.6.1 的那个共享库。
依赖于某个共享库的文件只需要保存这个共享库的 SO-NAME,就可以保证始终在使用系统中最新的主版本号的共享库了,而无需在系统中保存各种版本的共享库。
总结
说回 libc.so.6,我们知道现在我们使用的 libc 是 GNU 的 libc,也叫 glibc,glibc 目前版本是 2.x,按之前说的 SO-NAME 机制,应该是 libc.so.2 才对,但实际上却是 libc.so.6。
这是因为,早期 Linux 内核开发者们 fork 了一份 glibc,独立维护了第 2 版到第 5 版的 Linux libc(第 1 版的 libc 还是是 glibc)。但因为版权归属的问题,没有合并回去。后来 glibc 2.0 发布了,把 POSIX 标准实现得很好,他们就又用回 glibc 了。继此之后,glibc 自己的版本号虽然是 2.x,但由于最后使用的 Linux libc 的 soname 已经叫 libc.so.5 了,它就只能从 .6 开始计数了。
参考
- 《程序员的自我修养》第8章
- glibc - 维基百科, https://en.wikipedia.org/wiki/Glibc