参见英文答案 >
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动态链接器不想删除可执行文件,因此您必须自己进行动态链接(您可能不希望这样做).
>或者,您可以使用链接描述文件链接其中一个可执行文件以使用另一个基址.与以前一样,您必须自己完成动态链接器的工作.
被叫可执行文件:
// 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是一个开始阅读的好地方.