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

前端之家收集整理的这篇文章主要介绍了如何编译ELF二进制文件以便它可以作为动态库加载?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
参见英文答案 > building a .so that is also an executable3个
这是一个理论问题.我知道也许最好的做法是使用共享库.但我碰到了这个问题,似乎无法在任何地方找到答案.

如何构建代码并以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"
}

解决方法

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

你能做的是:

>将要执行dlopen()的可执行文件编译为可执行的共享库(-pie).从技术上讲,此文件是ELF共享对象,但可以执行.您可以使用readelf -h my_program或文件my_program检查程序是ELF可执行文件还是ELF共享对象. (作为奖励,通过将您的程序编译为共享对象,您将能够从ASLR中受益).
>通过将主程序编译为共享对象(以便在虚拟地址空间中的另一个位置加载),您应该能够动态链接其他可执行文件. GNU动态链接器不想删除可执行文件,因此您必须自己进行动态链接(您可能不希望这样做).
>或者,您可以使用链接描述文件链接其中一个可执行文件以使用另一个基址.与以前一样,您必须自己完成动态链接器的工作.

解决方案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,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的anwser):

$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. Dynamically Loaded (DL) Libraries HOWTO是一个开始阅读的好地方.

原文链接:https://www.f2er.com/c/118169.html

猜你在找的C&C++相关文章