Существует ли флаг компилятора, позволяющий разделяемой библиотеке вызывать функцию, которая поступает из открывшей её разделяемой библиотеки?

Вопрос или проблема

Ниже приведён минимальный воспроизводимый пример, где цель состоит в том, чтобы вызвать b_util() из a(). Мой вопрос заключается в том, возможно ли сделать это без явной передачи указателя на функцию b_util() в a.c.

Я ищу флаг, подобный -rdynamic, как обсуждалось в этом посте на Stack Overflow, который позволяет общей библиотеке вызывать функции из основного исполняемого файла. Компиляция a.c или b.c с этим флагом приводит к той же ошибке времени выполнения, но, возможно, есть способ использовать флаг?

Вы можете скомпилировать и запустить приведённый ниже код с помощью gcc c.c -o c.out && gcc b.c -o b.so -shared -fPIC && gcc a.c -o a.so -shared && ./c.out, но проблема в том, что он выводит dlopen: ./a.so: undefined symbol: b_util:

// a.c

void b_util(void);

void a(void) {
    b_util();
}
// b.c

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

void assert_dl(void *p, char *fn_name) {
    if (!p) {
        fprintf(stderr, "%s: %s\n", fn_name, dlerror());
        exit(EXIT_FAILURE);
    }
}

void b(void) {
    void *dll = dlopen("./a.so", RTLD_NOW);
    assert_dl(dll, "dlopen");

    void (*a)(void) = dlsym(dll, "a");
    assert_dl(a, "dlsym");

    a();
}

void b_util(void) {
    printf("b_util\n");
}
// c.c

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

void assert_dl(void *p, char *fn_name) {
    if (!p) {
        fprintf(stderr, "%s: %s\n", fn_name, dlerror());
        exit(EXIT_FAILURE);
    }
}

int main(void) {
    void *dll = dlopen("./b.so", RTLD_NOW);
    assert_dl(dll, "dlopen");

    void (*b)(void) = dlsym(dll, "b");
    assert_dl(b, "dlsym");

    b();
}

Ответ или решение

Вопрос о том, может ли общий динамический библиотечный модуль (shared library) вызывать функции из библиотеки, которая открыла его (в данном случае библиотеку a.so из b), является интересным и достаточно сложным с точки зрения управления символами и загрузки динамической библиотеки.

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

Решение

Чтобы добиться желаемого поведения, можно использовать следующий подход:

  1. Передача указателя на функцию через dlsym: Вместо того чтобы полагаться на компилятор, мы можем использовать dlsym в функции a() для поиска адреса функции b_util() в b.so. Таким образом, мы можем динамически связывать символы во время выполнения.

  2. Использование дополнительной функции для инициализации: Можно создать функцию инициализации в b.so, которая будет передавать указатель на b_util() в a.so.

Пример кода

Ниже приведен исправленный вариант вашего кода, учитывающий упомянутые решения:

a.c

#include <dlfcn.h>
#include <stdio.h>

void b_util(void); // Объявление функции, чтобы компилятор знал о ней

void a(void) {
    // Важно: используем `dlsym` для поиска адреса функции
    void (*func)(void);
    void *handle = dlopen("./b.so", RTLD_NOW); // Открываем b.so
    if (handle) {
        func = dlsym(handle, "b_util"); // Ищем b_util
        if (func) {
            func(); // Вызываем b_util
        } else {
            fprintf(stderr, "Не удалось найти b_util: %s\n", dlerror());
        }
        dlclose(handle); // Закрываем библиотеку
    } else {
        fprintf(stderr, "Не удалось открыть b.so: %s\n", dlerror());
    }
}

b.c

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

void assert_dl(void *p, const char *fn_name) {
    if (!p) {
        fprintf(stderr, "%s: %s\n", fn_name, dlerror());
        exit(EXIT_FAILURE);
    }
}

void b_util(void) {
    printf("b_util\n");
}

void b(void) {
    // Открываем a.so и вызываем a()
    void *dll = dlopen("./a.so", RTLD_NOW);
    assert_dl(dll, "dlopen");

    void (*a)(void) = dlsym(dll, "a");
    assert_dl(a, "dlsym");

    a(); // Теперь a() попытается вызвать b_util через dlsym
}

c.c

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

void assert_dl(void *p, const char *fn_name) {
    if (!p) {
        fprintf(stderr, "%s: %s\n", fn_name, dlerror());
        exit(EXIT_FAILURE);
    }
}

int main(void) {
    // Загружаем b.so и вызываем b()
    void *dll = dlopen("./b.so", RTLD_NOW);
    assert_dl(dll, "dlopen");

    void (*b)(void) = dlsym(dll, "b");
    assert_dl(b, "dlsym");

    b();
    dlclose(dll); // Не забываем закрыть библиотеку
    return 0;
}

Компиляция и выполнение

Теперь при компиляции и выполнении программы:

gcc c.c -o c.out -ldl && gcc b.c -o b.so -shared -fPIC && gcc a.c -o a.so -shared
./c.out

Вы должны увидеть вывод b_util, что и требовалось.

Заключение

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

Оцените материал
Добавить комментарий

Капча загружается...