Как скомпилировать двоичный файл 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: во избежание столкновений символов импортированные символы и указатели, которые они назначили, лучше иметь разные имена. См. комментарии здесь.