本文内容基于《程序员的自我修养》7.6.2小节的示例。
首先,按照书上写的命令一顿敲:
1 | gcc -fPIC -shared a1.c -o a1.so |
开开心心地运行:./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.so
和a2.so
没有依赖其他的共享库。问题出在第3、4行。
执行完第3、4行命令之后,通过ldd
命令查看b1.so
和b2.so
的共享库依赖情况。
1 | ldd b1.so |
因为ldd
是调用标准的动态链接器(ld.so
)来查找共享库依赖的,所以用ldd查看的结果就代表你运行时的查找结果,所以这个not found
就说明了问题,正好对应上面运行./main
报的错。
更进一步,用readelf -d
查看b1.so
和b2.so
的.dynamic
段,结果如下:
1 | readelf -d ./b1.so |
这里只说明了依赖的共享库的名字,并没有指明路径,所以在运行的时候ld.so
只会按照系统的路径搜索,其并不会搜索当前路径。查看main
的.dynamic
段,结果如下:
1 | readelf -d ./main |
一个重要的区别,就是第7行的Library runpath: [./]
,这里指示了运行时的共享库搜索路径,但是为什么还是找不到a1.so
(和a2.so
)呢?因为main
中的.dynamic
段只显示依赖了b1.so
和b2.so
,在加载了b1.so
和b2.so
之后,需要继续搜索a1.so
和a2.so
,这时只会按照b1.so
和b2.so
中.dynamic
段的信息来搜索,而其中并没有指示额外的运行时搜索路径runpath
,所以就出现了找不到a1.so
的报错。
要解决这个问题,就是要通知ld.so
b1.so
和b2.so
运行时依赖的共享库的搜索路径。
因为Linux下运行时查找共享库的路径是有固定位置和顺序的(位置可以手动修改),这个在第8章中会讲到,系统默认的位置是不包括当前目录./
的,所以你要手动告诉ld.so
共享库的搜索路径包括当前路径./
,告诉的方法有很多种(在第8章中会详细说明),这里只讲一种简单的,就是像编译main
的时候一样,在结尾添加-Xlinker -rpath ./
参数(或者用-Wl
参数也行,请自行百度)。
PS:这个示例用的都是相对地址,这样得到的可执行程序(例如
main
)只能在编译的路径下运行,假如将这个程序复制到其他路径运行,那么仍然会报找不到共享库的错,所以在写做一个工程项目的时候,都是将共享库安装到一个专门的路径下,然后使用绝对路径来搜索(其他方法在第8章中会讲到)。