【问题标题】:Segmentation fault when using dlclose(...) on android platform在 android 平台上使用 dlclose(...) 时出现分段错误
【发布时间】:2011-09-21 00:07:11
【问题描述】:

在 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 函数或类时,程序会因分段错误而崩溃,当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等各种编译标志进行编译,结果是一样的。

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

【问题讨论】:

  • +1 格式良好的问题 ;)
  • so文件头中是否有STL头?你能把它带到cpp文件吗? (所以STL不会在接口中。)定义和声明是分开的吗?
  • 我猜你说的是 aaa(...) 函数,如果是,那么声明和定义在不同的文件中。定义头文件为#ifdef __cplusplus extern "C" #endif int aaa(int i);
  • 你的主程序也是C++,还是编译成C?
  • 程序和库都是用C++编译器编译的。

标签: android c++ linux android-ndk


【解决方案1】:

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

【讨论】:

  • 在相应的头文件中完成,我没有在问题中添加那个文件的内容。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    我一般不喜欢打电话给dlclose()。问题是您必须确保共享库中的代码在取消映射后不会尝试执行代码,否则您将遇到分段错误。

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

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

    【讨论】:

      【解决方案4】:

      看来我找到了错误的原因。我用以下源文件尝试了另一个示例: 下面是简单类的源代码: 我的类.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 相关的东西。 这是函数库导出的源文件。 函数.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(...) 函数退出之前立即调用,如果它们是在主程序中定义的,或者如果它们是在某个库中定义的并且您正在使用它,那么应该在 @ 之前立即调用析构函数987654329@。 在 Android 操作系统上,在main(...) 函数退出期间调用静态 C++ 变量的 所有 析构函数(在主程序或您正在使用的某些库中定义)。那么在我们的案例中会发生什么?我们在使用的库中定义了 cls 静态 C++ 变量。然后在 main(...) 函数退出之前,我们调用 dlclose(...) 函数,结果库关闭并且 cls 变得无效。但是 cls 的指针存储在某个地方,它的析构函数应该在 main(...) 函数退出期间调用,因为在调用时它已经无效,我们得到分段错误。所以解决方案是不要打电话给dlclose(...),一切都应该没问题。不幸的是,对于这个解决方案,我们不能使用 attribute((destructor)) 来取消初始化我们想要取消初始化的东西,因为它是作为dlclose(...) 调用的结果而被调用的。

      【讨论】:

      • 随着时间的推移,Android 的 libc 中的析构函数处理已经有很多修复,因此确切的行为可能取决于您使用的特定 Android 版本。我建议在 b.android.com 上提交一个错误,并附上示例代码,解释您看到的行为和预期的行为。
      【解决方案5】:

      我在 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() 的特定实现没有清理共享对象内部的全局变量。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-08-15
        • 2015-05-06
        • 2015-07-26
        • 1970-01-01
        • 2017-06-06
        • 2017-08-26
        • 2018-06-18
        相关资源
        最近更新 更多