如何编译ELF二进制文件,使其可以作为动态库加载?
How to compile ELF binary so that it can be loaded as dynamic library?
这是理论题。我知道也许最佳实践是使用共享库。但是我 运行 进入这个问题并且似乎无法在任何地方找到答案。
如何在C/C++中构造代码并编译成ELF格式的程序以便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 executable 不是 relocatable 并且它们通常被编译为从相同的起始地址开始(x86_64 为 0x400000),这意味着它在技术上是不可能的将其中两个加载到同一地址 space.
你可以做的是:
编译你想要dlopen()
的executable作为executable共享库(-pie
)。从技术上讲,此文件是一个 ELF 共享对象,但可以执行。您可以检查该程序是 ELF executable 还是带有 readelf -h my_program
或 file my_program
的 ELF 共享对象。 (作为奖励,通过将您的程序编译为共享对象,您将能够从 ASLR 中受益)。
通过将你的主程序编译为一个共享对象(这样它就被加载到虚拟地址space的另一个地方)你应该能够动态地link其他执行官table。 GNU 动态 linker 不想 dlopen
一个 executable 文件所以你必须自己制作动态 linking(你可能不想这样做这个).
或者您可以 link 您的一个执行者 table 通过使用 linker 脚本来使用另一个基地址。与以前一样,您必须自己完成动态 linker 的工作。
解决方案 1:将动态加载的 executable 编译为 PIE
调用的executable:
// hello.c
#include <string.h>
#include <stdio.h>
void hello()
{
printf("Hello world\n");
}
int main()
{
hello();
return 0;
}
调用者executable:
// 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的时候,动态linker并没有在动态符号table的基础上加上地狱符号(.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
[...]
一些参考文献
有关更多信息,程序库 HOWTO 中的 4. Dynamically Loaded (DL) Libraries 是开始阅读的好地方。
根据评论和其他答案中提供的链接,这里是如何在编译时不链接这些程序的情况下完成的:
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
参考文献:
- building a .so that is also an executable
- Compile C program using dlopen and dlsym with -fPIC
PS:为了避免符号冲突,导入的符号和分配给它们的指针最好有不同的名称。查看评论 here.
这是理论题。我知道也许最佳实践是使用共享库。但是我 运行 进入这个问题并且似乎无法在任何地方找到答案。
如何在C/C++中构造代码并编译成ELF格式的程序以便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 executable 不是 relocatable 并且它们通常被编译为从相同的起始地址开始(x86_64 为 0x400000),这意味着它在技术上是不可能的将其中两个加载到同一地址 space.
你可以做的是:
编译你想要
dlopen()
的executable作为executable共享库(-pie
)。从技术上讲,此文件是一个 ELF 共享对象,但可以执行。您可以检查该程序是 ELF executable 还是带有readelf -h my_program
或file my_program
的 ELF 共享对象。 (作为奖励,通过将您的程序编译为共享对象,您将能够从 ASLR 中受益)。通过将你的主程序编译为一个共享对象(这样它就被加载到虚拟地址space的另一个地方)你应该能够动态地link其他执行官table。 GNU 动态 linker 不想
dlopen
一个 executable 文件所以你必须自己制作动态 linking(你可能不想这样做这个).或者您可以 link 您的一个执行者 table 通过使用 linker 脚本来使用另一个基地址。与以前一样,您必须自己完成动态 linker 的工作。
解决方案 1:将动态加载的 executable 编译为 PIE
调用的executable:
// hello.c
#include <string.h>
#include <stdio.h>
void hello()
{
printf("Hello world\n");
}
int main()
{
hello();
return 0;
}
调用者executable:
// 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的时候,动态linker并没有在动态符号table的基础上加上地狱符号(.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 [...]
一些参考文献
有关更多信息,程序库 HOWTO 中的 4. Dynamically Loaded (DL) Libraries 是开始阅读的好地方。
根据评论和其他答案中提供的链接,这里是如何在编译时不链接这些程序的情况下完成的:
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
参考文献:
- building a .so that is also an executable
- Compile C program using dlopen and dlsym with -fPIC
PS:为了避免符号冲突,导入的符号和分配给它们的指针最好有不同的名称。查看评论 here.