http://bbs.chinavideo.org/viewthread.php?tid=10844
写过win32程序的朋友对dll导出函数名都很熟悉,大家都可以通过.def文件或者__declspec(dllexport)来指定导出的函数名。在android下,可执行文件或者动态链接库用的是elf格式,和win32的pe格式有所不同。当编译动态链接库时,缺省的编译选项下默认所有的符号表都会导出。以android-ndk下的san-angeles例子为例,用ndk编译之后生成的jni动态库导出的符号表可以用下面命令看到(默认开发环境为win32 cygwin):
- $ /path/to/ndk/buid/prebuilt/windows/arm-eabi-4.4.0/bin/arm-eabi-nm libs/armeabi/libsanangeles.so
- 00003600 T Java_com_example_SanAngeles_DemoGLSurfaceView_nativePause
- 00003638 T Java_com_example_SanAngeles_DemoRenderer_nativeDone
- 0000367c T Java_com_example_SanAngeles_DemoRenderer_nativeInit
- 000035b4 T Java_com_example_SanAngeles_DemoRenderer_nativeRender
- 00003644 T Java_com_example_SanAngeles_DemoRenderer_nativeResize
- 00007334 a _DYNAMIC
- 0000740c a _GLOBAL_OFFSET_TABLE_
-
复制代码
这里可以看到几乎所有的函数名全局变量名都会被导出。其中有Java_com_example_SanAngeles_为前缀的JNI接口函数,有importGLInit这些普通函数,有freeGLObject这些局部(static)函数,还有sStartTick等全局变量名。其实在这个动态发布的时候,只需要导出java_com_开头的jni函数就可以了,里面这些细节函数名完全不需要暴露出来。
如何做到这一点呢?首先,我们需要了解gcc新引进的选项-fvisibility=hidden,这个编译选项可以把所有的符号名(包括函数名和全局变量名)都强制标记成隐藏属性。我们可以在Android.mk中可以通过修改LOCAL_CFLAGS选项加入-fvisibility=hidden来做到这一点,这样编译之后的.so看到的符号表为:
- 000033d0 t Java_com_example_SanAngeles_DemoGLSurfaceView_nativePause
- 00003408 t Java_com_example_SanAngeles_DemoRenderer_nativeDone
- 0000344c t Java_com_example_SanAngeles_DemoRenderer_nativeInit
- 00003384 t Java_com_example_SanAngeles_DemoRenderer_nativeRender
- 00003414 t Java_com_example_SanAngeles_DemoRenderer_nativeResize
- 00007104 a _DYNAMIC
- 000071dc a _GLOBAL_OFFSET_TABLE_
- 0000554c T _Unwind_Backtrace
- 00004748 T _Unwind_Complete
- 0000474c T _Unwind_DeleteException
- 00005528 T _Unwind_ForcedUnwind
- 00004740 T _Unwind_GetCFA
- 000055d0 T _Unwind_GetDataRelBase
-
复制代码
这里可以看到所有源代码里出现的函数名和全局变量名(符号名)都变成了't',也就是说都是局部符号(类似于static),这样这些函数名主程序是看不到的。我们还需要把jni的入口函数变成'T'类型才行,我们可以修改jni入口函数的属性来导出这些入口函数,比如app-android.c中的Java_com_example_SanAngeles_DemoRenderer_nativeInit函数,可以改为:
- void __attribute__ ((visibility ("default")))
- Java_com_example_SanAngeles_DemoRenderer_nativeInit ( JNIEnv* env )
- {
- importGLInit();
- appInit();
- gAppAlive = 1;
- sDemoStopped = 0;
- sTimeOffsetInit = 0;
- }
-
复制代码
其他几个Java_com_example_SanAngeles_开头的函数也这样修改一下即可。这样编译之后我们看到的符号表里所有Java_com_example_SanAngeles_开头的函数又变成'T'类型了。
最后我们还有一个问题就是如何隐藏那些局部符号名呢(t类型的符号)?我们可以调用strip -x来去掉这些局部的符号名。我们可以通过修改Android.mk重定义cmd-strip这个命令来实现,修改后的Android.mk如下:
- LOCAL_PATH := $(call my-dir)
- cmd-strip = $(TOOLCHAIN_PREFIX)strip --strip-debug -x $1
- include $(CLEAR_VARS)
- LOCAL_MODULE := sanangeles
- LOCAL_CFLAGS := -DANDROID_NDK \
- -DDISABLE_IMPORTGL \
- -fvisibility=hidden
- LOCAL_SRC_FILES := \
- importgl.c \
- demo.c \
- app-android.c \
- LOCAL_LDLIBS := -lGLESv1_CM -ldl -llog
- include $(BUILD_SHARED_LIBRARY)
-
复制代码
这样每次编译之后会自动strip掉这些局部的符号名,如下:
- 00003540 T Java_com_example_SanAngeles_DemoGLSurfaceView_nativePause
- 00003578 T Java_com_example_SanAngeles_DemoRenderer_nativeDone
- 000035bc T Java_com_example_SanAngeles_DemoRenderer_nativeInit
- 000034f4 T Java_com_example_SanAngeles_DemoRenderer_nativeRender
- 00003584 T Java_com_example_SanAngeles_DemoRenderer_nativeResize
- 000056bc T _Unwind_Backtrace
- 000048b8 T _Unwind_Complete
- 000048bc T _Unwind_DeleteException
- 00005698 T _Unwind_ForcedUnwind
- 000048b0 T _Unwind_GetCFA
- 00005740 T _Unwind_GetDataRelBase
- 00005778 T _Unwind_GetLanguageSpecificData
- 00005794 T _Unwind_GetRegionStart
- 00005738 T _Unwind_GetTextRelBase
- 0000562c T _Unwind_RaiseException
-
复制代码
这样局部符号都没有了,只有jni入口函数被导出。这样提高了jni动态库的安全性,同时.so文件的大小也小了不少。
关于elf文件的更多资料,可以参考
这篇文章
。如果要了解gcc和strip更多的选项,请移步
gcc
和
binutils
的官方文档。