Как скомпилировать двоичный файл ELF, чтобы он мог быть загружен как динамическая библиотека? [дубликат]
этот вопрос уже есть ответ здесь:
это теоретический вопрос. Я знаю, что, возможно, лучшей практикой было бы использование общих библиотек. Но я столкнулся с этим вопросом и нигде не могу найти ответа.
как создайте код и скомпилируйте в формате ELF программу на C / C++, чтобы ее можно было загрузить с помощью dlopen()
?
например, если один исполняемый файл содержит реализацию некоторых функций int test()
и я хотел бы вызвать эту функцию из моей программы (и желательно получить результат функции), если это вообще возможно, как бы я это сделал?
в псевдокоде я мог бы описать это следующим образом:
ELF исполняемый источник:
void main() {
int i = test();
printf("Returned: %d", i);//Prints "Returned: 5"
}
int test() {
return 5;
}
внешний программа:
// ... Somehow load executable from above
void main() {
int i = test();
printf("Returned: %d", i);//Must print "Returned: 5"
}
2 ответов
эльф исполняемый файл не перемещается, и они обычно компилируются для запуска с одного и того же начального адреса (0x400000 для x86_64), что означает, что технически невозможно загрузить два из них в одном адресном пространстве.
то, что вы могли бы сделать, это либо:
скомпилировать исполняемый файл, который вы хотите
dlopen()
в качестве исполняемой общей библиотеки (-pie
). Технически этот файл является общим объектом ELF, но может быть выполнен. Вы можете проверить, если программа-это исполняемый файл ELF или общий объект ELF сreadelf -h my_program
илиfile my_program
. (В качестве бонуса, компилируя вашу программу как общий объект, вы сможете извлечь выгоду из ASLR).компилируя основную программу как общий объект (чтобы она загружалась в другом месте виртуального адресного пространства), вы должны иметь возможность динамически связывать другой исполняемый файл. Динамический компоновщик GNU не хочет
dlopen
исполняемый файл, поэтому вам придется сделать динамический связывание себя (вы, вероятно, не хотите этого делать).или вы можете связать один из ваших исполняемых файлов использовать другой базовый адрес с помощью скрипта линкера. Как и раньше, вам придется самому выполнять работу динамического компоновщика.
Решение 1: скомпилируйте динамически загружаемый исполняемый файл как PIE
называется исполняемый файл:
// hello.c
#include <string.h>
#include <stdio.h>
void hello()
{
printf("Hello world\n");
}
int main()
{
hello();
return 0;
}
исполняемый файл вызывающего абонента:
// caller.c
#include <dlfcn.h>
#include <stdio.h>
int main(int argc, char** argv)
{
void* handle = dlopen(argv[1], RTLD_LAZY);
if (!handle) {
fprintf(stderr, "%s\n", dlerror());
return 1;
}
void (*hello)() = dlsym(handle, "hello");
if (!hello) {
fprintf(stderr, "%s\n", dlerror());
return 1;
}
hello();
return 0;
}
пытаюсь сделать это работа:
$ gcc -fpie -pie hello.c -o hello $ gcc caller.c -o caller $ ./caller ./hello ./hello: undefined symbol: hello
причина в том, что при компиляции hello в виде пирога динамический компоновщик не добавляет символ ада в таблицу динамических символов (.dynsym
):
$ readelf -s Symbol table '.dynsym' contains 12 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000200 0 SECTION LOCAL DEFAULT 1 2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab 3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2) 4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2) 5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 6: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses 7: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable 8: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2) 9: 0000000000200bd0 0 NOTYPE GLOBAL DEFAULT 24 _edata 10: 0000000000200bd8 0 NOTYPE GLOBAL DEFAULT 25 _end 11: 0000000000200bd0 0 NOTYPE GLOBAL DEFAULT 25 __bss_start Symbol table '.symtab' contains 67 entries: Num: Value Size Type Bind Vis Ndx Name [...] 52: 0000000000000760 18 FUNC GLOBAL DEFAULT 13 hello [...]
чтобы исправить это, вам нужно пройти -E
флаг ld
(см. @AlexKey по отрытии):
$ gcc -fpie -pie hello.c -Wl,-E hello.c -o hello $ gcc caller.c -o caller $ ./caller ./hello Hello world $ ./hello Hello world $ readelf -s ./hello Symbol table '.dynsym' contains 22 entries: Num: Value Size Type Bind Vis Ndx Name [...] 21: 00000000000008d0 18 FUNC GLOBAL DEFAULT 13 hello [...]
ссылки
дополнительные сведения 4. Динамически загружаемые (DL) библиотеки С библиотека программ HOWTO - это хорошее место, чтобы начать чтение.
на основе ссылок, представленных в комментариях и других ответах вот как это можно сделать, не связывая эти программы время компиляции:
test1.c:
#include <stdio.h>
int a(int b)
{
return b+1;
}
int c(int d)
{
return a(d)+1;
}
int main()
{
int b = a(3);
printf("Calling a(3) gave %d \n", b);
int d = c(3);
printf("Calling c(3) gave %d \n", d);
}
условие_2.c:
#include <dlfcn.h>
#include <stdio.h>
int (*a_ptr)(int b);
int (*c_ptr)(int d);
int main()
{
void* lib=dlopen("./test1",RTLD_LAZY);
a_ptr=dlsym(lib,"a");
c_ptr=dlsym(lib,"c");
int d = c_ptr(6);
int b = a_ptr(5);
printf("b is %d d is %d\n",b,d);
return 0;
}
сборник:
$ gcc -fPIC -pie -o test1 test1.c -Wl,-E
$ gcc -o test2 test2.c -ldl
результаты исполнения:
$ ./test1
Calling a(3) gave 4
Calling c(3) gave 5
$ ./test2
b is 6 d is 8
ссылки:
PS: во избежание столкновений символов импортированные символы и указатели, которые они назначили, лучше иметь разные имена. См. комментарии здесь.