如何编译ELF二进制文件以便它可以作为动态库加载?

时间:2015-03-09 08:28:05

标签: c linux elf dlopen dynamic-library

这是一个理论问题。我知道也许最好的做法是使用共享库。但我遇到了这个问题,似乎无法在任何地方找到答案。

如何构建代码并以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 个答案:

答案 0 :(得分:4)

ELF可执行文件不可重定位,它们通常编译为从相同的起始地址开始(对于x86_64为0x400000),这意味着它在技术上无法在同一地址空间中加载其中的两个。 / p>

你能做的是:

  • 将您想要的可执行文件dlopen()编译为可执行共享库(-pie)。从技术上讲,此文件是ELF共享对象,但可以执行。您可以使用readelf -h my_programfile my_program检查程序是ELF可执行文件还是ELF共享对象。 (作为奖励,通过将您的程序编译为共享对象,您将能够从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编译为PIE时,动态链接器不会将地狱符号添加到动态符号表(.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&#39; sw):

$ 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
[...]

一些参考文献

有关详细信息,程序库HOWTO 中的4. Dynamically Loaded (DL) Libraries是开始阅读的好地方。

答案 1 :(得分:1)

基于评论和其他答案中提供的链接,这里是如何在不链接这些程序编译时间的情况下完成的:

<强> 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);
}

<强> test2.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 :为了避免符号冲突,他们分配的导入符号和指针更好地具有不同的名称。请参阅评论here