Вопрос или проблема
Ниже приведён минимальный воспроизводимый пример, где цель состоит в том, чтобы вызвать 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()
, необходимо либо предоставить указатель на эту функцию при вызове, либо воспользоваться механизмами динамической загрузки.
Решение
Чтобы добиться желаемого поведения, можно использовать следующий подход:
-
Передача указателя на функцию через
dlsym
: Вместо того чтобы полагаться на компилятор, мы можем использоватьdlsym
в функцииa()
для поиска адреса функцииb_util()
вb.so
. Таким образом, мы можем динамически связывать символы во время выполнения. -
Использование дополнительной функции для инициализации: Можно создать функцию инициализации в
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
, можно избежать необходимости передачи указателей на функции в качестве аргументов. Вместо этого адреса функций ищутся динамически, что предоставляет более гибкий контроль над поведением библиотек.