Android NDK隐藏jni动态库的内部符号表

来源:互联网 发布:美国购物节网络星期一 编辑:程序博客网 时间:2024/05/17 03:10

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):

  1. $ /path/to/ndk/buid/prebuilt/windows/arm-eabi-4.4.0/bin/arm-eabi-nm libs/armeabi/libsanangeles.so
  2. 00003600 T Java_com_example_SanAngeles_DemoGLSurfaceView_nativePause
  3. 00003638 T Java_com_example_SanAngeles_DemoRenderer_nativeDone
  4. 0000367c T Java_com_example_SanAngeles_DemoRenderer_nativeInit
  5. 000035b4 T Java_com_example_SanAngeles_DemoRenderer_nativeRender
  6. 00003644 T Java_com_example_SanAngeles_DemoRenderer_nativeResize
  7. 00007334 a _DYNAMIC
  8. 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看到的符号表为:
  1. 000033d0 t Java_com_example_SanAngeles_DemoGLSurfaceView_nativePause
  2. 00003408 t Java_com_example_SanAngeles_DemoRenderer_nativeDone
  3. 0000344c t Java_com_example_SanAngeles_DemoRenderer_nativeInit
  4. 00003384 t Java_com_example_SanAngeles_DemoRenderer_nativeRender
  5. 00003414 t Java_com_example_SanAngeles_DemoRenderer_nativeResize
  6. 00007104 a _DYNAMIC
  7. 000071dc a _GLOBAL_OFFSET_TABLE_
  8. 0000554c T _Unwind_Backtrace
  9. 00004748 T _Unwind_Complete
  10. 0000474c T _Unwind_DeleteException
  11. 00005528 T _Unwind_ForcedUnwind
  12. 00004740 T _Unwind_GetCFA
  13. 000055d0 T _Unwind_GetDataRelBase
复制代码

    这里可以看到所有源代码里出现的函数名和全局变量名(符号名)都变成了't',也就是说都是局部符号(类似于static),这样这些函数名主程序是看不到的。我们还需要把jni的入口函数变成'T'类型才行,我们可以修改jni入口函数的属性来导出这些入口函数,比如app-android.c中的Java_com_example_SanAngeles_DemoRenderer_nativeInit函数,可以改为:
  1. void __attribute__ ((visibility ("default")))
  2. Java_com_example_SanAngeles_DemoRenderer_nativeInit ( JNIEnv*  env )
  3. {
  4.     importGLInit();
  5.     appInit();
  6.     gAppAlive    = 1;
  7.     sDemoStopped = 0;
  8.     sTimeOffsetInit = 0;
  9. }
复制代码

    其他几个Java_com_example_SanAngeles_开头的函数也这样修改一下即可。这样编译之后我们看到的符号表里所有Java_com_example_SanAngeles_开头的函数又变成'T'类型了。
    最后我们还有一个问题就是如何隐藏那些局部符号名呢(t类型的符号)?我们可以调用strip -x来去掉这些局部的符号名。我们可以通过修改Android.mk重定义cmd-strip这个命令来实现,修改后的Android.mk如下:
  1. LOCAL_PATH := $(call my-dir)
  2. cmd-strip = $(TOOLCHAIN_PREFIX)strip --strip-debug -x $1
  3. include $(CLEAR_VARS)
  4. LOCAL_MODULE := sanangeles
  5. LOCAL_CFLAGS := -DANDROID_NDK \
  6.                 -DDISABLE_IMPORTGL \
  7.                 -fvisibility=hidden
  8. LOCAL_SRC_FILES := \
  9.     importgl.c \
  10.     demo.c \
  11.     app-android.c \
  12. LOCAL_LDLIBS := -lGLESv1_CM -ldl -llog 
  13. include $(BUILD_SHARED_LIBRARY)
复制代码

    这样每次编译之后会自动strip掉这些局部的符号名,如下:
  1. 00003540 T Java_com_example_SanAngeles_DemoGLSurfaceView_nativePause
  2. 00003578 T Java_com_example_SanAngeles_DemoRenderer_nativeDone
  3. 000035bc T Java_com_example_SanAngeles_DemoRenderer_nativeInit
  4. 000034f4 T Java_com_example_SanAngeles_DemoRenderer_nativeRender
  5. 00003584 T Java_com_example_SanAngeles_DemoRenderer_nativeResize
  6. 000056bc T _Unwind_Backtrace
  7. 000048b8 T _Unwind_Complete
  8. 000048bc T _Unwind_DeleteException
  9. 00005698 T _Unwind_ForcedUnwind
  10. 000048b0 T _Unwind_GetCFA
  11. 00005740 T _Unwind_GetDataRelBase
  12. 00005778 T _Unwind_GetLanguageSpecificData
  13. 00005794 T _Unwind_GetRegionStart
  14. 00005738 T _Unwind_GetTextRelBase
  15. 0000562c T _Unwind_RaiseException
复制代码

    这样局部符号都没有了,只有jni入口函数被导出。这样提高了jni动态库的安全性,同时.so文件的大小也小了不少。
    关于elf文件的更多资料,可以参考这篇文章。如果要了解gcc和strip更多的选项,请移步gccbinutils的官方文档。

0 0
原创粉丝点击