Почему этот код загрузки динамической библиотеки работает с gcc? - PullRequest
7 голосов
/ 24 февраля 2011

Справочная информация:

Я столкнулся с незавидной задачей переноса приложения C ++ GNU / Linux на Windows. Это приложение выполняет поиск общих библиотек по определенным путям, а затем динамически загружает из них классы с помощью вызовов posix dlopen () и dlsym (). У нас есть очень веская причина для такой загрузки, поэтому я не буду вдаваться в подробности.

Проблема:

Для динамического обнаружения символов, сгенерированных компилятором C ++ с помощью dlsym () или GetProcAddress (), они должны быть обработаны с помощью внешнего блока связей «C». Например:

#include <list>
#include <string>

using std::list;
using std::string;

extern "C" {

    list<string> get_list()
    {
        list<string> myList;
        myList.push_back("list object");
        return myList;
    }

}

Этот код отлично подходит для C ++, компилируется и работает на многочисленных компиляторах как в Linux, так и в Windows. Однако он не компилируется с MSVC, потому что «тип возвращаемого значения не является допустимым C». Обходной путь, который мы нашли, состоит в том, чтобы изменить функцию так, чтобы она возвращала указатель на список вместо объекта списка:

#include <list>
#include <string>

using std::list;
using std::string;

extern "C" {

    list<string>* get_list()
    {
        list<string>* myList = new list<string>();
        myList->push_back("ptr to list");
        return myList;
    }

}

Я пытался найти оптимальное решение для загрузчика GNU / Linux, которое будет либо работать как с новыми функциями, так и со старым прототипом унаследованной функции, либо, по крайней мере, обнаруживать устаревшую функцию и выдавать предупреждение. Для наших пользователей было бы неприлично, если бы в коде произошел сбой при попытке использовать старую библиотеку. Моя первоначальная идея состояла в том, чтобы установить обработчик сигнала SIGSEGV во время вызова get_list (я знаю, что это странно - я открыт для лучших идей). Так что просто для того, чтобы подтвердить, что загрузка старой библиотеки приведет к сбою, где я думал, что я запустил библиотеку, используя старый прототип функции (возвращая объект списка), через новый код загрузки (который ожидает указатель на список), и, к моему удивлению, это просто работал. У меня вопрос почему ?

Код загрузки ниже работает с обоими прототипами функций, перечисленными выше. Я подтвердил, что он работает на Fedora 12, RedHat 5.5 и RedHawk 5.1 с использованием версий gcc 4.1.2 и 4.4.4. Скомпилируйте библиотеки, используя g ++ с -shared и -fPIC, и исполняемый файл должен быть связан с dl (-ldl).

#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <list>
#include <string>

using std::list;
using std::string;

int main(int argc, char **argv)
{
    void *handle;
    list<string>* (*getList)(void);
    char *error;

    handle = dlopen("library path", RTLD_LAZY);
    if (!handle)
    {
        fprintf(stderr, "%s\n", dlerror());
        exit(EXIT_FAILURE);
    }

    dlerror();

    *(void **) (&getList) = dlsym(handle, "get_list");

    if ((error = dlerror()) != NULL)
    {
        printf("%s\n", error);
        exit(EXIT_FAILURE);
    }

    list<string>* libList = (*getList)();

    for(list<string>::iterator iter = libList->begin();
          iter != libList->end(); iter++)
    {
        printf("\t%s\n", iter->c_str());
    }

    dlclose(handle);

    exit(EXIT_SUCCESS);
}

1 Ответ

4 голосов
/ 24 февраля 2011

Как говорит Ашеплер, это потому, что вам повезло.

Как оказалось, ABI, используемый для gcc (и большинства других компиляторов) для x86 и x64, возвращает "большие" структуры (слишком большие, чтобы уместитьсяв регистр) путем передачи дополнительного аргумента «скрытого» указателя в функцию, которая использует этот указатель в качестве пространства для хранения возвращаемого значения, а затем возвращает сам указатель.Таким образом, получается, что функция вида

struct foo func(...)

примерно эквивалентна

struct foo *func(..., struct foo *)

, где ожидается, что вызывающая сторона выделит место для 'foo' (возможно, в стеке)) и передайте указатель на него.

Так что бывает, что если у вас есть функция, которая ожидает вызова таким образом (ожидает возврата структуры), и вместо этого вызовите ее через указатель функции, который возвращаетуказатель, он МОЖЕТ казаться работающим - если биты мусора, которые он получает для дополнительного аргумента (случайное содержимое регистра, оставленное там вызывающим), указывают на место для записи, вызываемая функция с радостью запишет туда свое возвращаемое значение и затем вернетэтот указатель, поэтому вызываемый код вернет что-то, похожее на действительный указатель на ожидаемую структуру.Таким образом, на первый взгляд может показаться, что код работает, но на самом деле он, вероятно, забивает случайный бит памяти, что может быть важно позже.

...