在Android平台上使用dlclose(…)时出现分段错误

Android上使用dynamic加载API( <dlfcn.h>dlopen()dlclose()等)时遇到一些问题。 我正在使用NDK独立工具链(版本8)来编译应用程序和库。 Android版本是2.2.1 Froyo。

这是简单共享库的源代码。

 #include <stdio.h> int iii = 0; int *ptr = NULL; __attribute__((constructor)) static void init() { iii = 653; } __attribute__((destructor)) static void cleanup() { } int aaa(int i) { printf("aaa %d\n", iii); } 

这里是使用提到的库的程序源代码。

 #include <dlfcn.h> #include <stdlib.h> #include <stdio.h> int main() { void *handle; typedef int (*func)(int); func bbb; printf("start...\n"); handle = dlopen("/data/testt/test.so", RTLD_LAZY); if (!handle) { return 0; } bbb = (func)dlsym(handle, "aaa"); if (bbb == NULL) { return 0; } bbb(1); dlclose(handle); printf("exit...\n"); return 0; } 

有了这些源码,一切工作正常,但是当我尝试使用某些STL函数或类时,程序崩溃时会出现分段错误main()函数退出时,例如对共享库使用此源代码。

 #include <iostream> using namespace std; int iii = 0; int *ptr = NULL; __attribute__((constructor)) static void init() { iii = 653; } __attribute__((destructor)) static void cleanup() { } int aaa(int i) { cout << iii << endl; } 

有了这段代码,程序在main()函数退出之后或者之后崩溃了, 我已经尝试了几个testing,发现以下结果。

  1. 没有使用的STL一切工作正常。
  2. 当使用STL,最后不要调用dlclose()时,一切工作正常。
  3. 我试图编译各种编译标志,如-fno-use-cxa-atexit-fuse-cxa-atexit ,结果是一样的。

我的代码中使用STL有什么问题?

Solutions Collecting From Web of "在Android平台上使用dlclose(…)时出现分段错误"

看起来像我找到了错误的原因。 我已经尝试了以下源文件的另一个示例:以下是简单类的源代码:myclass.h

 class MyClass { public: MyClass(); ~MyClass(); void Set(); void Show(); private: int *pArray; }; 

myclass.cpp

 #include <stdio.h> #include <stdlib.h> #include "myclass.h" MyClass::MyClass() { pArray = (int *)malloc(sizeof(int) * 5); } MyClass::~MyClass() { free(pArray); pArray = NULL; } void MyClass::Set() { if (pArray != NULL) { pArray[0] = 0; pArray[1] = 1; pArray[2] = 2; pArray[3] = 3; pArray[4] = 4; } } void MyClass::Show() { if (pArray != NULL) { for (int i = 0; i < 5; i++) { printf("pArray[%d] = %d\n", i, pArray[i]); } } } 

正如你从代码中看到的,我没有使用任何STL相关的东西。 这里是函数库导出的源文件。 func.h

 #ifdef __cplusplus extern "C" { #endif int SetBabe(int); int ShowBabe(int); #ifdef __cplusplus } #endif 

func.cpp

 #include <stdio.h> #include "myclass.h" #include "func.h" MyClass cls; __attribute__((constructor)) static void init() { } __attribute__((destructor)) static void cleanup() { } int SetBabe(int i) { cls.Set(); return i; } int ShowBabe(int i) { cls.Show(); return i; } 

最后,这是使用库的程序的源代码。 main.cpp中

 #include <dlfcn.h> #include <stdlib.h> #include <stdio.h> #include "../simple_lib/func.h" int main() { void *handle; typedef int (*func)(int); func bbb; printf("start...\n"); handle = dlopen("/data/testt/test.so", RTLD_LAZY); if (!handle) { printf("%s\n", dlerror()); return 0; } bbb = (func)dlsym(handle, "SetBabe"); if (bbb == NULL) { printf("%s\n", dlerror()); return 0; } bbb(1); bbb = (func)dlsym(handle, "ShowBabe"); if (bbb == NULL) { printf("%s\n", dlerror()); return 0; } bbb(1); dlclose(handle); printf("exit...\n"); return 0; } 

同样你也可以看到使用该库的程序也没有使用任何STL相关的东西,但是在程序运行后,我在main(...)函数退出时得到了相同的分段错误。 所以这个问题与STL本身并没有关联,而是隐藏在其他地方。 然后经过长时间的研究,我发现了这个错误。 通常情况下,静态C ++变量的destructorsmain(...)函数出口之前立即被调用,如果它们是在主程序中定义的,或者如果它们是在某个库中定义的,并且正在使用它,则应该立即调用析构函数dlclose(...) 。 在Android OS上,在main(...)函数出口期间调用静态C ++变量的所有析构函数(在主程序或某个库中定义的main(...) 。 那么我们的情况会怎样? 我们在我们使用的库中定义了cls静态C ++变量。 然后紧接在main(...)函数退出之前,我们调用dlclose(...)函数,结果库关闭, cls变为无效。 但是cls的指针存储在某个地方,它的析构函数应该在main(...)函数退出时被调用,并且因为在调用的时候它已经是无效的,所以我们得到了段错误。 所以解决方法是不要调用dlclose(...) ,一切都应该没问题。 不幸的是,对于这个解决方案,我们不能使用attribute ((析构函数))去初始化我们想要初始化的东西,因为它被称为dlclose(...)调用的结果。

我有一个普遍厌恶调用dlclose() 。 问题是你必须确保没有任何东西会在共享库中没有映射后尝试执行代码,否则你将会遇到分段错误。

最常见的失败方法是创建一个对象,该对象的析构函数被定义在共享库中,或调用共享库中定义的代码。 如果该对象在dlclose()之后仍然存在,那么当该对象被删除时,您的应用程序将会崩溃。

如果你看看logcat你应该看到一个调试器堆栈跟踪。 如果你可以使用arm-eabi-addr2line工具解码,你应该能够确定它是否在析构函数中,如果是的话,哪些类。 或者,采取崩溃地址,剥离高12位,并将其用作dlclose() d库中的偏移量,并尝试找出该地址处的代码。

我在Linux上遇到了同样的问题。 解决我的段错误的解决方法是将这些行放在与main()相同的文件中,以便在主返回后调用dlclose():

 static void* handle = 0; void myDLClose(void) __attribute__ ((destructor)); void myDLClose(void) { dlclose(handle); } int main() { handle = dlopen(...); /* ... real work ... */ return 0; } 

dlclose引起的段错误的根本原因可能是dlclose()的特定实现不会清除共享对象内的全局变量。

您需要使用-fpic作为使用dlopen()dlclose()的应用程序的编译器标志进行编译。 你也应该尝试通过dlerror()错误处理,并且可能检查你的函数指针的赋值是否有效,即使它不是NULL,函数指针可能指向初始化时无效的东西, dlsym()不保证返回NULL在Android上如果找不到符号。 请参考android文档,而不是posix兼容的东西,而不是所有的东西都符合android的。

你应该使用extern“C”声明你的函数aaa()