这是一个理论问题。我知道也许最好的做法是使用共享库。但我遇到了这个问题,似乎无法在任何地方找到答案。
如何构建代码并以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"
}
答案 0 :(得分:4)
ELF可执行文件不可重定位,它们通常编译为从相同的起始地址开始(对于x86_64为0x400000),这意味着它在技术上无法在同一地址空间中加载其中的两个。 / p>
你能做的是:
将您想要的可执行文件dlopen()
编译为可执行共享库(-pie
)。从技术上讲,此文件是ELF共享对象,但可以执行。您可以使用readelf -h my_program
或file my_program
检查程序是ELF可执行文件还是ELF共享对象。 (作为奖励,通过将您的程序编译为共享对象,您将能够从ASLR中受益)。
通过将主程序编译为共享对象(以便将其加载到虚拟地址空间中的其他位置),您应该能够动态链接其他可执行文件。 GNU动态链接器不希望dlopen
成为可执行文件,因此您必须自己进行动态链接(您可能不想这样做)。
或者,您可以使用链接描述文件链接其中一个可执行文件以使用其他基址。与以前一样,您必须自己完成动态链接器的工作。
被调用的可执行文件:
// 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。