我有一个链接到我的可执行myexe的共享对象(a.so)。 a.so暴露了一个名为get_val()的方法,myexe正在使用它。
现在当a.so被加载到myexe的进程地址空间时?是当myexe调用get_val()API或myexe启动时。
答案 0 :(得分:2)
有两种类型的库:
.a
/ .lib
),它本身就成为二进制文件的一部分。严格来说,它不是整个库,而是库中需要满足未解析链接的那些对象。.so
/ .dll
),它有两种版本,区别于加载库的时间:
__main()
来libc
dlopen()
的一部分。dlopen()
。(这些术语看起来有点模糊,我看到使用不同术语的不同文献;上面的术语是我记忆它以便记住概念的方式。)
因此,如果您在没有自己调用a.so
的情况下使用dlopen()
,a.so
是一个动态链接库,因此它会在程序启动时加载。在这种情况下,从系统中删除a.so
将阻止您的程序启动 - 它将被加载,但在main()
被调用之前它将失败。
如果您使用a.so
自己致电dlopen()
,则完全由您控制。
关于你的问题
Q1:如果您使用dlopen()
自己致电RTLD_LAZY
,则会在a.so
可以解决的第一个未解决的来电时加载a.so
。如果您自己致电dlopen()
,RTLD_NOW
会立即加载a.so
,即dlopen()
返回之前。如果您不是自己致电dlopen()
但让libc
为您完成工作,则会在计划开始时加载a.so
。
Q2:您删除了a.so
。如果您使用dlopen()
致电RTLD_LAZY
,并且没有运行需要a.so
的代码,程序将会愉快地运行,否则会产生信号。如果您不致电dlopen()
但让libc
为您完成工作,程序将无法成功启动。
问题3:实际上,如果不调用a.so
(或者替代它的等效内容),就无法加载dlopen()
。问题是,您是自己致电dlopen()
,还是让环境(即libc
)为您完成工作。
免责声明:我不是这方面的专家,我的答案的某些部分可能是错误的。我将验证我的答案中我自己怀疑的那些部分,即是libc
调用dlopen()
还是其他内容,以及即使你是否可以进行延迟绑定不要自己使用dlopen()
。我得到结果后,我会更新答案。
答案 1 :(得分:0)
我猜你是在Linux / x86-64上。它是特定于操作系统的。
通常,ELF共享库在执行开始时加载ld-linux.so(8)。实际上,共享库应该是position independent code(PIC)。
但它可能取决于dlopen(3)其旗帜RTLD_NOW
或RTLD_LAZY
阅读Drepper's paper: How To Write Shared Libraries和x86-64 ABI specification
您可以使用strace(1)来了解您自己的Linux系统上发生的情况。
原则上,您可以使用mmap(2)动态加载foo.so
并自行处理relocations。我已经(差不多)在上个世纪(对于SPARC)做了,并且相信我这是一项繁琐的工作。
BTW,dlopen
在GNU libc和musl-libc中实施。两者都是免费软件,你可以研究他们的源代码。
另请阅读Program Library HowTo。它简要说明了一些细节:
使用
将共享对象的源文件编译为PIC gcc -Wall -fPIC -O src1.c -o src1.pic.o
gcc -Wall -fPIC -O src2.c -o src2.pic.o
使用
将它们链接到共享库foo.so
中
gcc -shared src1.pic.o src2.pic.o -o foo.so
使用dlopen
的完整路径,例如
void* dlh = dlopen("./foo.so", RTLD_NOW);
if (!dlh) { fprintf(stderr, "dlopen failed: %s\n", dlerror());
exit(EXIT_FAILURE);
然后你可以有一个约定,说你的foo.so
插件应该具有签名功能
typedef int sayhello_sig_t(const char*);
名为say_hello
,您可以使用以下方式获取其地址:
sayhello_sig_t* funptr = dlsym(dlh, "say_hello");
if (!funptr) {
fprintf(stderr,
"dlsym say_hello failure: %s\n, dlerror();
exit(EXIT_FAILURE);
}