0%

包含共享库时动态链接编译的搜索路径问题——《程序员的自我修养》7.6.2示例的问题

本文内容基于《程序员的自我修养》7.6.2小节的示例。

首先,按照书上写的命令一顿敲:

1
2
3
4
5
$ gcc -fPIC -shared a1.c -o a1.so
$ gcc -fPIC -shared a2.c -o a2.so
$ gcc -fPIC -shared b1.so a1.so -o b1.so
$ gcc -fPIC -shared b2.so a2.so -o b2.so
$ gcc main.c b1.so b2.so -o main -Xlinker -rpath ./

开开心心地运行:./main,然后,就出现了这一行:

./main: error while loading shared libraries: a1.so: cannot open shared object file: No such file or directory

顿时懵逼。

书上不是说加了-Xlinker -rpath ./就不会报错嘛?(这个三个参数是在编译时通知链接器 额外的共享库的搜索路径,以至于在编译以及运行时不会出现找不到共享库的问题)

但,为什么还是出了问题呢?可能是由于OS、GCC以及动态链接器版本不同问题?毕竟书的年代有些久远。

PS:我的OS是Ubuntu 20.04.1 LTS on Windows 10 x86_64,GCC版本为9.3.0,ld为2.34

经过我对前文不断尝试,终于理解了gcc编译链接共享库以及运行时查找共享库的机制。

我们一行命令一行命令来看,首先前两行是没有问题的,因为a1.soa2.so没有依赖其他的共享库。问题出在第3、4行。

执行完第3、4行命令之后,通过ldd命令查看b1.sob2.so的共享库依赖情况。

1
2
3
4
5
6
$ ldd b1.so
linux-vdso.so.1 (0x00007ffff08e5000)
a1.so => not found
$ ldd b2.so
linux-vdso.so.1 (0x00007ffde39a0000)
a2.so => not found

因为ldd是调用标准的动态链接器(ld.so)来查找共享库依赖的,所以用ldd查看的结果就代表你运行时的查找结果,所以这个not found就说明了问题,正好对应上面运行./main报的错。

更进一步,用readelf -d查看b1.sob2.so.dynamic段,结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ readelf -d ./b1.so

Dynamic section at offset 0x2e50 contains 21 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [a1.so]
0x000000000000000c (INIT) 0x1000
0x000000000000000d (FINI) 0x1130
... //后面的省略

$ readelf -d ./b2.so
Dynamic section at offset 0x2e50 contains 21 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [a2.so]
0x000000000000000c (INIT) 0x1000
0x000000000000000d (FINI) 0x1130
... //后面的省略

这里只说明了依赖的共享库的名字,并没有指明路径,所以在运行的时候ld.so只会按照系统的路径搜索,其并不会搜索当前路径。查看main.dynamic段,结果如下:

1
2
3
4
5
6
7
8
9
10
$ readelf -d ./main
Dynamic section at offset 0x2d90 contains 30 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [b1.so]
0x0000000000000001 (NEEDED) Shared library: [b2.so]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000001d (RUNPATH) Library runpath: [./]
0x000000000000000c (INIT) 0x1000
0x000000000000000d (FINI) 0x1208
... //后面的省略

一个重要的区别,就是第7行的Library runpath: [./],这里指示了运行时的共享库搜索路径,但是为什么还是找不到a1.so(和a2.so)呢?因为main中的.dynamic段只显示依赖了b1.sob2.so,在加载了b1.sob2.so之后,需要继续搜索a1.soa2.so,这时只会按照b1.sob2.so.dynamic段的信息来搜索,而其中并没有指示额外的运行时搜索路径runpath,所以就出现了找不到a1.so的报错。

要解决这个问题,就是要通知ld.so b1.sob2.so运行时依赖的共享库的搜索路径。

因为Linux下运行时查找共享库的路径是有固定位置和顺序的(位置可以手动修改),这个在第8章中会讲到,系统默认的位置是不包括当前目录./的,所以你要手动告诉ld.so共享库的搜索路径包括当前路径./,告诉的方法有很多种(在第8章中会详细说明),这里只讲一种简单的,就是像编译main的时候一样,在结尾添加-Xlinker -rpath ./参数(或者用-Wl参数也行,请自行百度)。

PS:这个示例用的都是相对地址,这样得到的可执行程序(例如main)只能在编译的路径下运行,假如将这个程序复制到其他路径运行,那么仍然会报找不到共享库的错,所以在写做一个工程项目的时候,都是将共享库安装到一个专门的路径下,然后使用绝对路径来搜索(其他方法在第8章中会讲到)。