用python脚本查找.so库文件中的符号
- 预备知识
- Linux动态链接库的命名规则及存放/寻找路径
- Linux中搜索文件
- Linux浏览.so库文件中的符号列表
- 用Python调用shell命令
- 完整程序
最近在写Makefile的时候总是报找不到符号的错误,比如:
/usr/bin/ld: /tmp/ccnwoS4i.o: undefined reference to symbol '_ZN4Ogre8RTShader15ShaderGenerator15addSceneManagerEPNS_12SceneManagerE'
/usr/bin/ld: /usr/lib/libOgreRTShaderSystem.so.1.12.6: error adding symbols: DSO missing from command line
遇到长这样的错误一般就是在链接的时候少链接了库文件,只要链接对应的库就好,比如这个错误就可以通过编译时加上-lOgreRTShaderSystem
修复。
因此,在出现这类错误时有必要查找到底少链接了哪一个库文件。
预备知识
Linux动态链接库的命名规则及存放/寻找路径
Linux系统中动态链接库是.so(Shared Object)文件,其默认存放位置为/usr/lib/
和/lib/
。
其命名规则一般是lib
+库名
+.so
,例如库OgreRTShaderSystem
所对应的库文件为libOgreRTShaderSystem.so
。而在gcc
编译的时候传入的是库名而不是库文件名,例如,在编译时要链接库OgreRTShaderSystem
,则在gcc
中要以以下方式调用:
g++ test.cpp -o test -lOgreRTShaderSystem
gcc
将会在以以下顺序寻找libOgreRTShaderSystem.so
这个库文件:
- 编译目标代码时指定的动态库搜索路径;
- 环境变量LD_LIBRARY_PATH指定的动态库搜索路径;
- 配置文件/etc/ld.so.conf.d/*中指定的动态库搜索路径;
- 默认的动态库搜索路径/lib;
- 默认的动态库搜索路径/usr/lib。
Linux中搜索文件
在Linux中可以用find
命令来搜索文件,这里以Manjaro系统为例。
Linux find 命令用来在指定目录下查找文件。任何位于参数之前的字符串都将被视为欲查找的目录名。如果使用该命令时,不设置任何参数,则 find 命令将在当前目录下查找子目录与文件。并且将查找到的子目录和文件全部进行显示。
一般语法为:
find path -option
其中常用参数有:
- -amin n : 在过去 n 分钟内被读取过
- -atime n : 在过去n天内被读取过的文件
- -ipath p, -path p : 路径名称符合 p 的文件,ipath 会忽略大小写
- -name name, -iname name : 文件名称符合 name 的文件。iname 会忽略大小写
- -size n : 文件大小 是 n 单位,b 代表 512 位元组的区块,c 表示字元数,k 表示 kilo bytes,w 是二个位元组
- -type c : 文件类型是 c 的文件
这里我们用到-name
参数,我们用以下命令来查找以libOgre开头的.so库文件:
find /usr/lib/ -name 'libOgre*.so'
Linux浏览.so库文件中的符号列表
在Linux中可以用nm
命令来列出指定库文件中的符号列表。
nm
是names的缩写, nm命令主要是用来列出某些文件中的符号(说白了就是一些函数和全局变量等)。
这里我们会用到-D
(–dynamic)参数,其用于显示动态符号。该任选项仅对于动态目标(例如特定类型的共享库)有意义。
例如查找/usr/lib/libOgreBites.so
中的符号:
nm -D /usr/lib/libOgreBites.so
其结果类似下面这样:
0000000000046020 B __bss_start
U commandWidgetClass
U __cxa_allocate_exception
U __cxa_atexit
U __cxa_begin_catch
U __cxa_call_unexpected
U __cxa_end_catch
w __cxa_finalize
U __cxa_free_exception
U __cxa_guard_abort
U __cxa_guard_acquire
U __cxa_guard_release
U __cxa_rethrow
U __cxa_throw
U __dynamic_cast
0000000000046020 D _edata
...
...
0000000000039160 u _ZZNSt8__detail18__to_chars_10_implIjEEvPcjT_E8__digits
00000000000380e0 u _ZZNSt8__detail18__to_chars_10_implImEEvPcjT_E8__digits
可以用grep
在结果中查找含有指定符号(_ZN4Ogre8RTShader15ShaderGenerator15addSceneManagerEPNS_12SceneManagerE)的行:
nm -D /usr/lib/libOgreBites.so | grep _ZN4Ogre8RTShader15ShaderGenerator15addSceneManagerEPNS_12SceneManagerE
其结果如下:
e8RTShader15ShaderGenerator15addSceneManagerEPNS_12SceneManagerE
U _ZN4Ogre8RTShader15ShaderGenerator15addSceneManagerEPNS_12SceneManagerE
用Python调用shell命令
在Python中可以通过subprocess
模块实现调用shell命令。其方法是直接调用Popen()
函数:
import subprocess
child = subprocess.Popen('ls ~ -a', shell=True)
在前后进程有顺序要求的情况下需要加上wait()
这一过程来确保后一过程在前一过程完成后开始:
child.wait()
而在subprocess
中运行shell命令返回的结果如果要存到变量中,则需要指定Popen()
中stdout
参数为subprocess.PIPE
。并且要用communicate()
来从subprocess.PIPE
中读取结果:
child = subprocess.Popen('ls ~ -a', shell=True, stdout=subprocess.PIPE)
child.wait()
result_binary = child.communicate()
这里的result是一个元组,其第一个元素为保存了运行结果的二进制串,因此需要对其进行解码:
result = result_binary[0].decode("utf-8")
完整程序
import subprocess
sub = subprocess.Popen('find /usr/lib/ -name \'libOgre*.so\'', shell=True, stdout=subprocess.PIPE)
sub.wait()
out = sub.communicate()
res = out[0].decode("utf-8").split('\n')
for item in res:
if len(item):
tmp_sub = subprocess.Popen('nm -D {} | grep _ZN4Ogre8RTShader15ShaderGenerator15addSceneManagerEPNS_12SceneManagerE'.format(item), shell=True, stdout=subprocess.PIPE)
tmp_sub.wait()
tmp = tmp_sub.communicate()[0]
if tmp:
print('file: {}'.format(item))
print('\t{}'.format(tmp))
运行结果:
find: ‘/usr/lib/firmware/b43legacy’: Permission denied
find: ‘/usr/lib/firmware/b43’: Permission denied
file: /usr/lib/libOgreRTShaderSystem.so
b'00000000000c6fa0 T _ZN4Ogre8RTShader15ShaderGenerator15addSceneManagerEPNS_12SceneManagerE\n'
file: /usr/lib/libOgreBites.so
b' U _ZN4Ogre8RTShader15ShaderGenerator15addSceneManagerEPNS_12SceneManagerE\n'
观察结果发现应该是没有链接libOgreRTShaderSystem.so
。