Ошибка сегментации при использовании dlclose (...) на платформе Android

У меня есть некоторые проблемы при использовании 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);
}

Вот исходный код программы, который использует указанную библиотеку.

#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(). Я пробовал пару тестов и нашел следующие результаты.

  • Без использования STL все работает нормально.
  • При использовании STL и не вызывайте dlclose() в конце, все работает нормально.
  • Я попытался скомпилировать с различными флагами компиляции типа -fno-use-cxa-atexit или -fuse-cxa-atexit, результат будет таким же.

Что не так в моем коде, который использует STL?

Ответ 1

Похоже, я нашел причину ошибки. Я попробовал другой пример со следующими исходными файлами: Вот исходный код простого класса: 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, и она скрыта в каком-то другом месте. Затем, после некоторых длительных исследований, я нашел ошибку. Обычно destructors статических переменных С++ вызывается непосредственно перед выходом функции main(...), если они определены в основной программе или если они определены в некоторой библиотеке и вы используете ее, тогда деструкторы должны вызываться непосредственно перед dlclose(...). В Android OS все деструкторы (определенные в основной программе или в какой-либо библиотеке, которую вы используете) статических переменных С++ вызывается во время выхода функции main(...). Так что же происходит в нашем случае? У нас есть статическая переменная С++, определенная в используемой библиотеке. Затем непосредственно перед выводом функции main(...) вызывается функция dlclose(...), в результате библиотека закрывается, а cls становится недействительной. Но указатель cls хранится где-то, и его деструктор следует вызывать во время выхода функции main(...), а поскольку во время вызова он уже недействителен, мы получаем ошибку сегментации. Поэтому решение состоит в том, чтобы не называть dlclose(...), и все должно быть хорошо. К сожалению, с этим решением мы не можем использовать атрибут ((деструктор)) для деинициализации того, что мы хотим деинициализировать, потому что оно вызвано в результате вызова dlclose(...).

Ответ 2

У меня есть общее отвращение к вызову dlclose(). Проблема в том, что вы должны убедиться, что ничто не будет пытаться выполнить код в общей библиотеке после того, как оно было не отображено, или вы получите ошибку сегментации.

Наиболее распространенным способом неудачи является создание объекта, чей деструктор определяется или вызывает код, определенный в общей библиотеке. Если объект все еще существует после dlclose(), ваше приложение будет сбой при удалении объекта.

Если вы посмотрите на logcat, вы увидите трассировку стека debuggerd. Если вы можете декодировать это с помощью инструмента arm-eabi-addr2line, вы сможете определить, находится ли он в деструкторе, и если да, то для какого класса. В качестве альтернативы, возьмите адрес сбоя, отмените высокие 12 бит и используйте это как смещение в библиотеке, которая была dlclose() d, и попытайтесь выяснить, какой код живет по этому адресу.

Ответ 3

Я столкнулся с той же головной болью в Linux. Обход, который фиксирует мой segfault, заключается в том, чтобы поместить эти строки в тот же файл, что и 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 segfault может быть то, что конкретная реализация dlclose() не очищает глобальные переменные внутри общего объекта.

Ответ 4

Вам нужно скомпилировать с -fpic как флаг компилятора для приложения, использующего dlopen() и dlclose(). Вы также должны попробовать обработать ошибки с помощью dlerror() и, возможно, проверить правильность присвоения указателя на функцию, даже если это не NULL, указатель функции может указывать на что-то недействительное от инициализации, dlsym() не гарантирует возврат NULL на андроид, если он не может найти символ. Обратитесь к документации по android, противоположной положению, совместимому с posix, но не все совместимо с posix на андроиде.

Ответ 5

Вы должны использовать extern "C" для объявления функции aaa()