最近遇到了一个奇怪的问题,有一个以前是静态链接的动态链接库STL(c _static),改为动态链接STL(c _shared)之后,在>=Android 11手机会报dlopen报找不到__emutls_get_address符号。
首先用nm查看动态链接库中的符号:
$ nm libxxx.so |grep __emutls_get_address U __emutls_get_address
这个U表示不定义,即符号定义在其他库中。我们使用它ndk21b,到ndk的目录看下libc _shared.so里的符号:
$ cd toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/lib/arm-linux-androideabi $ nm libc _shared.so |grep __emutls_get_address 00070364 t __emutls_get_address
小写t表示内部符号,不暴露给外部使用(如果暴露给外部,应该是大写T)。这有点奇怪。我们的动态库没有这个符号,然后依赖它libc _shared.so里面的符号不暴露在外面,肯定找不到。
最终发现的原因是:
当我们编译自己的动态链接库时,我们使用旧版本ndk里的ibc _shared.so,而打包进apk的,是ndk21b里的libc _shared.so。
这个老版本libc _shared.so会暴露这个__emutls_get_address符号。因为旧版本的库没有符号,所以不能使用nm但是我们可以使用命令查看readelf查看:
$ readelf -s libc _shared.so | grep __emutls_get_address 2110: 000846a0 316 FUNC GLOBAL DEFAULT 11 __emutls_get_address
可以发现这个符号的可见性是GLOBAL DEFAULT。
我们可以readelf再看下ndk21b的libc _shared.so里的符号:
$ readelf -s libc _shared.so| grep __emutls_get_address s 37081: 00070364 324 FUNC LOCAL HIDDEN 13 __emutls_get_address
发现区别没,这里的可见性是LOCAL HIDDEN,所以它不会暴露在外面。事实上,在这种情况下,链接器会自动帮助我们链接 libgcc_real.a,而libgcc_real.a这个符号包含在里面:
$ cd toolchains/llvm/prebuilt/darwin-x86_64/lib/gcc/arm-linux-androideabi/4.9.x/armv7-a $ nm libgcc_real.a|grep __emutls_get_address 0000020c T __emutls_get_address
结论:
编译时用的stl版本必须打包apk里的stl保持版本一致,否则会出现一些莫名其妙的版本兼容性问题。
P.S. 如果不知道符号在哪个库中,可以lldb attach到app上,然后用image lookup --symbol 'xxx'
搜索所有依赖库。