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

时间:2011-06-23 07:40:14

标签: android c++ linux android-ndk

在Android上使用动态加载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函数或类时,程序崩溃时出现分段错误 ,当{{1函数退出,例如将此源代码用于共享库时。

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()函数退出期间崩溃并出现分段错误。 我尝试了几项测试并找到了以下结果。

  1. 不使用STL,一切正常。
  2. 当使用STL并且最后不要调用dlclose()时,一切正常。
  3. 我尝试使用各种编译标志进行编译,例如-fno-use-cxa-atexit-fuse-cxa-atexit,结果是一样的。
  4. 我的代码使用STL有什么问题?

5 个答案:

答案 0 :(得分:7)

看起来我找到了这个bug的原因。我尝试了以下源文件的另一个示例: 这是简单类的源代码: 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本身无关,而且它隐藏在其他地方。经过一番长时间的研究,我发现了这个bug。 通常,在destructors函数退出之前立即调用main(...)静态C ++变量,如果它们在主程序中定义,或者如果它们在某个库中定义并且您正在使用它,那么析构函数应该是在dlclose(...)之前立即调用。 在Android OS 所有析构函数(在主程序中定义或在您使用的某些库中定义)的静态C ++变量在main(...)函数退出期间被调用。那么我们的情况会发生什么?我们在我们使用的库中定义了 cls 静态C ++变量。然后在main(...)函数退出之前,我们调用dlclose(...)函数,结果库关闭, cls 变为无效。但 cls 的指针存储在某个地方,它的析构函数应该在main(...)函数退出期间调用,因为在调用时它已经无效,我们会得到分段错误。因此,解决方案是不要调用dlclose(...),一切都应该没问题。不幸的是,使用此解决方案,我们无法使用 属性((析构函数))来取消初始化我们想要取消初始化的内容,因为它是dlclose(...)调用的结果。

答案 1 :(得分:0)

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

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

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

答案 2 :(得分:0)

我在Linux上遇到了同样的问题。解决我的段错误的解决方法是将这些行放在与main()相同的文件中,以便在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()的特定实现不会清除共享对象中的全局变量。

答案 3 :(得分:-1)

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

答案 4 :(得分:-2)

您应该使用extern“C”来声明函数aaa()