共享库的链接问题:奇怪的现象
在linux中进行共享库的链接时最常使用两个选项:-L选项指定库的查找目录,-l选项指定将要使用的共享库。除了使用这种标准方式进行库的链接外,还有一种方式:将共享库文件当成目标文件直接进行链接。就个人理解,这两种方式的效果应该是一样的,但是实际开发中却遇到了不一致的奇怪现象,有待大家帮忙解答,以下列出现象。
例子使用UDT库进行链接,UDT是用C++语言写的一个网络通讯库。使用第一种方式的链接选项和结果如下:
# Makefile中的LIBS选项
LIBS = -L../../UDT4/src/ -ludt -lpthread -lstdc++ -lm
# make构建结果
~/work/udt/udt_server_client/server$ make clean;make
rm -rf server.o udtserver
gcc -Wall -g -O2 -c -o server.o server.c
gcc -Wall -g -O2 -o udtserver server.o -L../../UDT4/src/ -ludt -lpthread -lstdc++ -lm
server.o: In function `recv_data':
~/work/udt/udt_server_client/server/server.c:138: undefined reference to `udtp_udzero'
~/work/udt/udt_server_client/server/server.c:139: undefined reference to `udtp_udset'
~/udt/udt_server_client/server/server.c:142: undefined reference to `udtp_select'
.........
collect2: error: ld returned 1 exit status
make: *** [udtserver] Error 1
# 共享库符号check
~/work/udt/udt_server_client/server$ nm ../../UDT4/src/libudt.a |grep udtp_udzero
00000530 T udtp_udzero
~/work/udt/udt_server_client/server$ nm ../../UDT4/src/libudt.so |grep udtp_udzero
0002db90 t udtp_udzero
使用第二种方式进行链接的选项和结果:
# Makefile选项
LIBS = ../../UDT4/src/libudt.a -lpthread -lstdc++ -lm
~/work/udt/udt_server_client/server$ make clean;make
rm -rf server.o udtserver
gcc -Wall -g -O2 -c -o server.o server.c
gcc -Wall -g -O2 -o udtserver server.o ../../UDT4/src/libudt.a -lpthread -lstdc++ -lm
# 共享库符号check
~/work/udt/udt_server_client/server$ nm ../../UDT4/src/libudt.a |grep udtp_udzero
00000530 T udtp_udzero
~/work/udt/udt_server_client/server$ nm ../../UDT4/src/libudt.so |grep udtp_udzero
0002db90 t udtp_udzero
第一种方式出现符号找不到的问题,而第二种方式却OK。除了UDT库的使用例子外,在开发中使用到FFmpeg库时也遇到这个问题,昨天同事构建使用ncurses库时也遇到了同样的问题。我无法解释清楚为什么,但只能选择第二种方式进行链接和使用库。
交叉编译中的共享库查找问题:链接方式的差别
上面的例子是第一种链接方式不行,而第二种OK,而这里的现象则是第二种方式不行,而第一种方式OK。不过这里与上面的区别在于:上面显式使用静态库,而且是PC平台,而这里使用动态库,是ARM开发板平台。
例子中使用到开发板提供商的方案库,Makefile中的库链接选项为:
LIBS := $(AKUIO_PATH)/lib/libakuio.so \
$(AKUIO_PATH)/lib/libak2dsys.so \
$(AKUIO_PATH)/lib/image.lib \
$(AKUIO_PATH)/lib/libakuio.so.0 \
$(AKUIO_PATH)/lib/libak2dsys.so.0.1.0 \
$(AKUIO_PATH)/lib/libak2dsys.so \
$(AKUIO_PATH)/lib/libakuio.so \
$(AKUIO_PATH)/lib/libakdp.so \
$(AKUIO_PATH)/lib/libakgui.so \
$(AKUIO_PATH)/lib/libakstream.so \
$(AKUIO_PATH)/lib/libakaec2.a \
$(AKUIO_PATH)/lib/libakimage.so \
$(ALSA_PATH)/usr/lib/libasound.so.2.0.0 \
$(AKMEDIA_PATH)/usr/lib/libakaudioAdpcmEnc.so.0.1.0
在Ubuntu12.04服务器上交叉编译可以编译和链接成功,在ARM开发板上可以正常运行,但是要保证这些库在开发板上的存在路径和交叉编译时PC上的库存在路径完全一致,否则会出现找不到共享库的问题,即使这些库在开发板的/usr/lib目录下已经存在,程序依然找不到它们:
[root@anyka /home/sea/system]$ ./ipcr002
./ipcr002: error while loading shared libraries: /home/sea/work/rock/rock/system/../media/ak_lib/akuiolib/lib/libakdp.so: cannot open shared object file: No such file or directory
[root@anyka /home/sea/system]$find /usr/lib -name libakdp.so
/usr/lib/libakdp.so
但是如果将共享库的链接方式改成第一种方式,则不会出现这个问题,Makefile中库选项:
LIBS := $(AKUIO_PATH)/lib/image.lib \
-L$(AKUIO_PATH)/lib -lakuio -lak2dsys \
-lakdp -lakgui -lakstream -lakaec2 -lakimage \
-L$(ALSA_PATH)/usr/lib -lasound \
-L$(AKMEDIA_PATH)/usr/lib -lakaudioAdpcmEnc
此时交叉编译、链接、运行一切都没有问题。虽然问题解决了,但我还是不太理解其中的原理。
这里的问题除了使用第一种链接方式外,还有另外一种解决方案,虽然比较笨拙,但在我的上一个项目中使用嵌入式QT库时是使用的这种方式:在开发板上建立一个同名的软连接到共享库所在的位置。如下:
[root@anyka /home/sea/system]$ mkdir -p /home/sea/work/rock/rock/media/ak_lib/akuiolib/lib
[root@anyka /home/sea/system]$ mkdir -p /home/sea/work/rock/rock/system
[root@anyka /home/sea/system]$ ln -s /usr/lib/libakdp.so /home/sea/work/rock/rock/media/ak_lib/akuiolib/lib/libakdp.so
[root@anyka /home/sea/system]$ ./ipcr002
./ipcr002: error while loading shared libraries: /home/sea/work/rock/rock/system/../media/ak_lib/akuiolib/lib/libakgui.so: cannot open shared object file: No such file or directory
现在能找到libakdp.so动态库了,下一个找不到的是libakgui.so库,只是对每个库建立软连接有点繁琐,当时嵌入式QT只有两个库。因此最佳的解决方法还是使用通用的共享库链接方式。
建议
链接共享库时使用通用的选项链接方式,如果行不通再考虑使用直接的目标文件链接方式。