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

Hra*_*yan 11 c++ linux android android-ndk

(我使用动态加载API时存在一些问题<dlfcn.h>:dlopen(),dlclose()等),在Android上.我正在使用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);
}
Run Code Online (Sandbox Code Playgroud)

这是使用上述库的程序源代码.

#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;
}
Run Code Online (Sandbox Code Playgroud)

有了这些来源,一切都运行正常,但是当我尝试使用一些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;
}
Run Code Online (Sandbox Code Playgroud)

使用此代码,程序在main()函数退出后或在函数退出期间崩溃并出现分段错误.我尝试了几个测试,发现了以下结果.

  1. 没有使用STL一切都很好.
  2. 当使用STL并且dlclose()最后没有调用时,一切正常.
  3. 我试着用各种编译标志编译,-fno-use-cxa-atexit或者-fuse-cxa-atexit,结果是一样的.

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

Hra*_*yan 7

看起来我找到了这个bug的原因.我尝试了以下源文件的另一个例子:这是简单类的源代码:myclass.h

class MyClass
{
public:
    MyClass();
    ~MyClass();
    void Set();
    void Show();
private:
    int *pArray;
};
Run Code Online (Sandbox Code Playgroud)

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]);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

从代码中可以看出,我没有使用任何与STL相关的东西.这是函数库导出的源文件.func.h

#ifdef __cplusplus
extern "C" {
#endif

int SetBabe(int);
int ShowBabe(int);

#ifdef __cplusplus
}
#endif
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

最后这是使用该库的程序的源代码.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;
}
Run Code Online (Sandbox Code Playgroud)

再次,你可以看到使用该库的程序也没有使用任何STL相关的东西,但在程序运行后,我在main(...)函数退出期间得到了相同的分段错误.所以这个问题与STL本身无关,而且它隐藏在其他地方.经过一番长时间的研究,我发现了这个bug.通常,destructorsmain(...)函数退出之前立即调用静态C++变量,如果它们在主程序中定义,或者如果它们在某个库中定义并且您正在使用它,那么应该在之前立即调用析构函数dlclose(...).在Android OS上,在函数退出期间调用静态C++变量的所有析构函数(在主程序中或在您正在使用的某些库中定义)main(...).那么我们的情况会发生什么?我们在我们使用的库中定义了cls静态C++变量.然后在main(...)函数退出之前,我们调用dlclose(...)function,结果库关闭,cls变为无效.但cls的指针存储在某个地方,它的析构函数应该在main(...)函数退出时调用,因为在调用时它已经无效,我们得到了分段错误.因此解决方案是不要打电话dlclose(...),一切都应该没问题.不幸的是,使用这个解决方案,我们不能使用属性((析构函数))来取消初始化我们想要取消初始化的东西,因为它是作为dlclose(...)调用的结果而调用的.

  • 随着时间的推移,Android的libc中的析构函数处理有很多修复,因此确切的行为可能取决于您正在使用的Android的特定版本.我建议使用示例代码在b.android.com上提交错误,解释您正在看到的行为以及您期望的行为. (2认同)

小武哥*_*小武哥 -2

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