动态链接5 程序员的自我修养第八章笔记

0.序

本文承接上一篇动态链接4,这应该是关于动态链接笔记的最后一篇了。略去了书上关于符号版本的讨论

10. linux下的动态库管理

通常linux下动态库遵循命名规则libname.so.x.y.z,该动态库对应的SONAMElibname.so.x,链接时使用-lname, 比如我机器上的/usr/lib/libpcap.so.1.10.1,它的SONAME则是libpcap.so.1。链接时使用参数-lpcap。因此根据上述规则,主版本号不同的动态库对应的 SONAME也不同。这主要是因为主版本号不相同的动态库的ABI一般不互相兼容,因此用SONAME做一个区分。

根据之前的讨论可以想到,如果使用上述规则,那么要正常使用一个动态库,应该至少存在两个符号链接,libname.solibname.so.x。这是因为,使用-lname链接时,link editor会去寻找动态库libname.so,在DT_NEEDED中记录的则是libname.so.x,因此dynamic linker会去寻找libname.so.x这个库。

这样曲折的管理方式主要便于动态库的更新和升级,设想我把libpcap.so.1.10.1升级为libpcap.so.1.11.1后,只需要修改libpcap.so.1指向新版的动态库,而以前所有依赖libpcap的可执行程序不需要做任何改动就可以直接使用新版的libpcap。而libpcap.so的存在则帮助我们忽略版本号。

glibc中有一个动态库版本管理工具ldconfig,可以帮助我们管理动态库的符号链接并创建/etc/ld.so.cache

下面通过一个例子演示一下:

// lostsymlib.c 
// gcc -Wl,-soname=liblost.so.2 -fPIC -shared -o liblost.so.2.1.1 lostsymlib.c
// gcc -Wl,-soname=liblost.so.1 -fPIC -shared -o liblost.so.1.2.3 lostsymlib.c
// 由此得到两个版本的动态库
#include <stdio.h>

void print_hello(){
	printf("hello, i am .so.2 lib\n");
}

现在我们有动态库liblost.so.2.1.1, liblost.so.1.2.3。接下来使用ldconfig(man ldconfig可以查到相应参数的含义)。

$ ldconfig -v -n .
.:
	liblost.so.2 -> liblost.so.2.1.1 (changed)
	liblost.so.1 -> liblost.so.1.2.3 (changed)

可以看到,当前目录下增加了两个符号链接,liblost.so.1, liblost.so.2,此即这两个库的SONAME
然后我们用之前的指令再增加一个新的动态库,liblost.1.3.5

$ ldconfig -v -n .
.:
	liblost.so.2 -> liblost.so.2.1.1
	liblost.so.1 -> liblost.so.1.3.5 (changed)

ldconfig修改了liblost.so.1的符号链接指向,使其指向了版本更新的liblost.so.1.3.5

不过让人疑惑的是,ldconfig并不会自动生成或者修改符号链接liblost.so。这意味着,如果此前的liblost.so指向liblost.so.1.2.3,后来新增了liblost.2.1.1,那么必须要手动修改liblost.so的指向,否则-llost仍会链接到低版本的动态库。这个行为有点迷惑,stackoverflow上也有相关讨论Using ldconfig on Linux

最后说一下/etc/ld.so.cache/etc/ld.so.conf这两个文件和ldconfig的关系,默认情况下ldconfig扫描的是/etc/ld.so.conf下记录的目录,更新这些目录下的共享库文件的符号链接,并且把相应的链接关系记录到/etc/ld.so.cache中,这样便于dynamic linker快速搜索。

11. 关于动态库的entry point

Why and how are some shared libraries runnable, as though they are executables?
这里讨论了如何生成一个可以直接运行的动态库。在举一个实际的例子前,不妨从一个较高层次上思考动态库可不可以有entry point。相比于可执行文件,动态库除了具有位置无关这一特性外,还有什么其它的分别吗?

如果我们把动态库看作一堆函数的集合,选择其中某个函数作为可执行程序的入口点,如果直接执行则从该函数开始。若是作为动态库加载那就正常加载。这样一想其实动态库完全可以也有entry point。

看下面的例子

// entry.c
// gcc -fPIC -pie -o entry.so entry.c
#include <stdio.h>
#include <stdlib.h>
void print_hello(char* s){
	printf("hello %s!\n", s);
}
int main(int argc, char **argv)
{
	print_hello(argv[0]);
	return 0;
}

// havea.c
// gcc -o havea havea.c ./entry.so
#include <stdio.h>
void print_hello(char*);

int main(int argc, char* argv[]){
	print_hello(argv[0]);
	return 0;
}

运行结果如下

$ ./entry.so 
hello ./entry.so!
$ ./havea 
hello ./havea!

根据全局符号介入的规则,entry.so作为共享库时,_startmain函数等被覆盖了。

如果结合到ASLR,默认情况下gcc生成的可执行文件都应该是地址无关的,因此理论上来讲都可以作为动态库使用。

$ readelf -h havea
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x730
  Start of program headers:          64 (bytes into file)
  Start of section headers:          6384 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         29
  Section header string table index: 28

Type属性可以看到,其实编译出的havea其实就可以看作是一个动态库(我们知道,四种目标文件类型ET_REL, ET_EXEC, ET_DYN, ET_CORE,分别对应.o, .exe, .so, core dump文件)。

那怎样得到一个EXEC type文件呢?我们可以用如下的方法编译

$ gcc -o havea -no-pie havea.c ./entry.so
$ readelf -h havea
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400600
  Start of program headers:          64 (bytes into file)
  Start of section headers:          6312 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         29
  Section header string table index: 28

可以看到,这下生成的文件类型变为了EXEC, 并且entry point固定了,即该执行文件不满足ASLR的要求。

OK,与动态库相关的东西差不多到此结束。关于动态库的初始化,等看到书的第四部分再继续写吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
程序员自我修养:链接,装载与库》是一本由林锐、郭晓东、郑蕾等人合著的计算机技术书籍,在该书中,作者从程序员的视角出发,对链接、装载与库等概念进行了深入的阐述和解析。 在计算机编程中,链接是指将各个源文件中的代码模块组合成一个可执行的程序的过程。链接可以分为静态链接动态链接两种方式。静态链接是在编译时将所有代码模块合并成一个独立的可执行文件,而动态链接是在运行时根据需要加载相应的代码模块。 装载是指将一个程序从磁盘上加载到内存中准备执行的过程。在装载过程中,操作系统会为程序分配内存空间,并将程序中的各个模块加载到相应的内存地址上。装载过程中还包括解析模块之间的引用关系,以及进行地址重定位等操作。 库是指一组可重用的代码模块,通过链接和装载的方式被程序调用。库可以分为静态库和动态库。静态库是在编译时将库的代码链接到程序中,使程序与库的代码合并为一个可执行文件。动态库则是在运行时通过动态链接的方式加载并调用。 《程序员自我修养:链接,装载与库》对于理解链接、装载和库的原理和机制具有极大的帮助。通过学习这些概念,程序员可以更好地优化代码结构和组织,提高程序的性能和可维护性。同时,了解链接、装载和库的工作原理也对于进行调试和故障排除具有重要意义。 总之,链接、装载与库是计算机编程中的重要概念,对于程序员来说掌握这些知识是非常必要的。《程序员自我修养:链接,装载与库》这本书提供了深入浅出的解释和实例,对于想要学习和掌握这些知识的程序员来说是一本非常有价值的参考书籍。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值