Как надежно использовать dlsym, если у вас есть дублированные символы?

Добрый вечер, в настоящее время я работаю над плагином в C ++ / Linux на основе Plux.net модель.

Для простоты я в основном объявляю символ (давайте назовем его pluginInformation) с extern C (чтобы разобрать), и мой менеджер плагинов ищет этот символ в предварительно сконфигурированном импорте (.so).

Дело в том, что основное приложение объявляет тот же символ, причем не только этот, но и любая зависимость, которая у него есть, может иметь символ также. (поскольку в этой информации плагина модули могут публиковать плагины и / или слоты).

Поэтому, когда мой PluginManager запускается, он сначала пытается найти символ в основной программе (передав NULL dlopen), затем он пытается найти символ в любой из своих зависимостей (используя dl_iterate_phdr). И последнее это будет dlopen набор настроек импорта (он будет читать путь .so, который настроил пользователь, dlopen их и, наконец, dlsym информационный символ плагина).

Коллекция pluginInformation, найденная во всех модулях, используется для построения расширения три.

Если я объявил символ в основной программе и загрузил импорт, используя dlopen, это работает (пока я пропускаю флаг RTLD_DEEPBIND при удалении импорта).

Но для зависимостей приложения у меня нет возможности передать флаг (я могу, но он ничего не делает), так как этот .sos был загружен при запуске приложения.

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

Как я могу управлять дубликатами символов между основной программой и режимом импорта с помощью dlsym ?.

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

PD: Извините, я не выложил ни одного кода, но сейчас я не дома, надеюсь, описание того, что я пытаюсь сделать, достаточно ясное, если не завтра я могу выложить какой-нибудь код.

6

Решение

Вот альтернативный подход.

Само приложение экспортирует одну или несколько функций регистрации элементов плагина. Например:

int register_plugin_item(const char *const text,
const char *const icon,
void (*enter)(void *),
void (*click)(void *),
void (*leave)(void *),
void *data);

Для каждого зарегистрированного предмета есть два слотаtext а также icon), три функциональных слота (enter, click, а также leave) и непрозрачная ссылка, которая дается функциям в качестве параметра при вызове.

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

Каждый плагин вызывает register_plugin_item() Функция для каждого элемента, который он хочет, в функции конструктора (которая автоматически запускается во время загрузки библиотеки). Возможно, и часто полезно, чтобы функция сначала проверила среду, в которой она работает, чтобы определить, какие функции нужно зарегистрировать или какие оптимизированные варианты функций использовать для каждого элемента плагина.

Вот тривиальный пример плагина. Обратите внимание, как все символы static, чтобы плагин не загрязнял динамическую таблицу символов и не вызывал конфликтов символов.

#include <stdlib.h>
#include <stdio.h>

extern int register_plugin_item(const char *const,
const char *const,
void (*enter)(void *),
void (*click)(void *),
void (*leave)(void *),
void *);

static void enter(void *msg)
{
fprintf(stderr, "Plugin: Enter '%s'\n", (char *)msg);
}

static void leave(void *msg)
{
fprintf(stderr, "Plugin: Leave '%s'\n", (char *)msg);
}

static void click(void *msg)
{
fprintf(stderr, "Plugin: Click '%s'\n", (char *)msg);
}

static void init(void) __attribute__((constructor));
static void init(void)
{
register_plugin_item("one", "icon-one.gif",
enter, leave, click,
"1");

register_plugin_item("two", "icon-two.gif",
enter, leave, click,
"2");
}

Вышеуказанный плагин экспортирует два элемента. Для тестирования создайте хотя бы пару вариантов из вышеперечисленного; вы увидите, что нет конфликтов символов, даже если плагины используют одинаковые (статические) переменные и имена функций.

Вот пример приложения, которое загружает указанные плагины и проверяет каждый зарегистрированный элемент:

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

struct item {
struct item *next;
const char  *text;
const char  *icon;
void        *data;
void       (*enter)(void *);
void       (*leave)(void *);
void       (*click)(void *);
};

static struct item *list = NULL;

int register_plugin_item(const char *const text,
const char *const icon,
void (*enter)(void *),
void (*click)(void *),
void (*leave)(void *),
void *data)
{
struct item *curr;

curr = malloc(sizeof *curr);
if (!curr)
return ENOMEM;

curr->text = text;
curr->icon = icon;
curr->data = data;
curr->enter = enter;
curr->leave = leave;
curr->click = click;

/* Prepend to list */
curr->next = list;
list = curr;

return 0;
}

int main(int argc, char *argv[])
{
int          arg;
void        *handle;
struct item *curr;

if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, "       %s PLUGIN.so ... \n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "Please supply full plugin paths, unless\n");
fprintf(stderr, "the plugins reside in a standard library directory,\n");
fprintf(stderr, "or in a directory listed in LD_LIBRARY_PATH.\n");
fprintf(stderr, "\n");
return 1;
}

for (arg = 1; arg < argc; arg++) {

handle = dlopen(argv[arg], RTLD_NOW);
if (handle != NULL)
fprintf(stderr, "%s: Loaded.\n", argv[arg]);
else
fprintf(stderr, "%s.\n", dlerror());

/* Note: We deliberately "leak" the handle,
*       so that the plugin is not unloaded. */
}

for (curr = list; curr != NULL; curr = curr->next) {
if (curr->text)
printf("Item '%s':\n", curr->text);
else
printf("Unnamed item:\n");

if (curr->icon)
printf("\tIcon is '%s'\n", curr->icon);
else
printf("\tNo icon\n");

if (curr->data)
printf("\tCustom data at %p\n", curr->data);
else
printf("\tNo custom data\n");

if (curr->enter)
printf("\tEnter handler at %p\n", curr->enter);
else
printf("\tNo enter handler\n");

if (curr->click)
printf("\tClick handler at %p\n", curr->click);
else
printf("\tNo click handler\n");

if (curr->leave)
printf("\tLeave handler at %p\n", curr->leave);
else
printf("\tNo leave handler\n");

if (curr->enter || curr->click || curr->leave) {
printf("\tTest calls:\n");
if (curr->enter)
curr->enter(curr->data);
if (curr->click)
curr->click(curr->data);
if (curr->leave)
curr->leave(curr->data);
printf("\tTest calls done.\n");
}
}

return 0;
}

Если приложение app.cи у вас есть плагины plugin-foo.c а также plugin-bar.cВы можете скомпилировать их, например, используя

gcc -W -Wall -rdynamic app.c -ldl -o app

gcc -W -Wall -fpic -c plugin-foo.c
gcc -shared -Wl,-soname,plugin-foo.so plugin-foo.o -o plugin-foo.so

gcc -W -Wall -fpic -c plugin-bar.c
gcc -shared -Wl,-soname,plugin-bar.so plugin-bar.o -o plugin-bar.so

и запустить, например, с помощью

./app --help

./app ./plugin-foo.so

./app ./plugin-foo.so ./plugin-bar.so

Обратите внимание, что если один и тот же плагин определен более одного раза, конструктор выполняется только один раз для этой библиотеки. Там не будет дубликатов регистрации.


Интерфейс между плагинами и приложением полностью зависит от вас. В этом примере есть только одна функция. Реальное приложение, вероятно, будет иметь больше. Приложение также может экспортировать другие функции, например, для плагина для запроса конфигурации приложения.

Разработка хорошего интерфейса — это совсем другая тема, и она, безусловно, заслуживает как минимум столько же размышлений, сколько вы вкладываете в реализацию.

Платформа плагинов Plux.NET позволяет плагинам также экспортировать свои собственные слоты. Этот альтернативный подход позволяет это во многих отношениях. Одним из них является экспорт функции регистрации плагинов — то есть для регистрации плагины вместо отдельных элементов — для этого требуется указатель на функцию:

int register_plugin(const char *const name,
int (*extend)(const char *const, ...));

Если плагин предоставляет слоты, он предоставляет свою собственную функцию регистрации в качестве extend указатель на функцию Приложение также экспортирует функцию, например

int plugin_extend(const char *const name, ...);

которые плагины могут использовать для вызова функций регистрации других плагинов. (Реализация plugin_extend() в основном приложении подразумевает поиск подходящего extend функция уже зарегистрирована, потом вызываем ее / их.)

С точки зрения реализации, использование плагинов для экспорта слотов немного усложняет реализацию. В частности, когда и в каком порядке должны появиться слоты, экспортируемые плагинами? Существует ли определенный порядок загрузки плагинов, чтобы обеспечить экспорт всех возможных слотов? Что происходит, если есть круговая зависимость? Должны ли плагины указывать, на какие другие плагины они полагаются до начала регистрации?

Если каждый плагин представляет собой отдельную сущность, которая не экспортирует свои собственные слоты, а только подключает к основным слотам приложения, вы избежите большей части сложности в реализации.

Порядок, в котором проверяются зарегистрированные предметы, — это деталь, о которой вам, вероятно, стоит подумать. В приведенном выше примере программы используется связанный список, в котором элементы оказываются в обратном порядке по сравнению с порядком регистрации, а порядок регистрации совпадает с порядком, в котором имена файлов подключаемых модулей сначала указываются в командной строке. Если у вас есть каталог плагинов, который автоматически сканируется (например, с помощью opendir()/readdir()/dlopen()/closedir() loop), то порядок регистрации плагинов является полуслучайным (зависит от файловой системы; обычно меняется только при добавлении или удалении плагинов).

Поправки? Вопросы? Комментарии?

5

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]