.so 文件何时加载 Linux?
When do .so files get loaded Linux?
我有一个共享对象 (a.so),它链接到我的可执行文件 myexe。
a.so 公开了一个名为 get_val() 的方法,myexe 正在使用该方法。
现在什么时候a.so会加载到myexe的进程地址space?是当 myexe 调用 get_val() API 时,还是当 myexe 启动时。
我猜你在 Linux/x86-64。它是 OS 具体的。
通常,ELF shared library is loaded at start of execution time, by ld-linux.so(8). Practically, the shared library should be position independent code (PIC)。
但它可能取决于,并且有 dlopen(3) 及其标志 RTLD_NOW
或 RTLD_LAZY
阅读Drepper's paper: How To Write Shared Libraries and the x86-64 ABI specification
您可以使用 strace(1) 来了解您自己的 Linux 系统上发生了什么。
原则上您可以使用 mmap(2) and processing by yourself the relocations 动态加载 foo.so
。我在上个世纪(对于 SPARC)已经(几乎)完成了这项工作,相信我,这是一项乏味的任务。
顺便说一句,dlopen
是在 GNU libc and in 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
link 它们在共享库中 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);
}
有两(三)种类型的库:
- 静态库(后缀:
.a
/ .lib
),它本身成为二进制文件的一部分。严格来说,它不是整个库,而是库中需要满足未解决的 links. 的那些对象
- 共享(动态)库(后缀:
.so
/ .dll
),有两种形式,以加载库的时间区分:
- dynamic link library,它们是您告诉编译器和 linker 的库,您称它们为静态库,但它们不是库的一部分 - 它们由 loader/linker 加载(在 Linux 中通常作为
__main()
的一部分从 libc
通过调用 dlopen()
)。
- 动态加载库,你自己调用
dlopen()
。
(术语有点模糊,我看过不同的文献使用不同的术语,以上术语是我为了记住概念而背的。)
因此,如果您使用 a.so
而没有自己调用 dlopen()
,a.so
是一个动态 link 库,因此它会在程序启动时加载。在这种情况下,从系统中删除 a.so
将阻止您的程序启动 - 它会被加载,但会在调用 main()
之前失败。
如果您使用 a.so
并自己调用 dlopen()
,它完全在您的控制之下。
关于你的问题
Q1:如果你自己调用dlopen()
,使用RTLD_LAZY
,a.so
会在第一个可以被a.so
解析的未解析调用时加载.如果您自己调用 dlopen()
,使用 RTLD_NOW
,a.so
会立即加载,即在 dlopen()
returns 之前。如果您自己不调用 dlopen()
而是让 libc
为您完成工作,则 a.so
将在程序启动时加载。
Q2:你删除了a.so
。如果你用RTLD_LAZY
调用dlopen()
,而不是运行通过需要a.so
的代码,程序会运行愉快的,否则会产生一个信号.如果您不调用 dlopen()
而让 libc
为您完成工作,程序将无法成功启动。
Q3:实际上,如果不调用 dlopen()
(或替代它的等效内容),就无法加载 a.so
。问题只是,您是自己调用 dlopen()
,还是让环境(即 libc
)为您完成工作。
免责声明:我不是这方面的专家,我的部分回答可能是错误的。我将验证我自己怀疑的答案的那些部分,即是否是 libc
调用 dlopen()
或其他东西,以及是否可以进行延迟绑定,即使你'您自己没有使用 dlopen()
。得到结果后我会更新答案。
我有一个共享对象 (a.so),它链接到我的可执行文件 myexe。 a.so 公开了一个名为 get_val() 的方法,myexe 正在使用该方法。
现在什么时候a.so会加载到myexe的进程地址space?是当 myexe 调用 get_val() API 时,还是当 myexe 启动时。
我猜你在 Linux/x86-64。它是 OS 具体的。
通常,ELF shared library is loaded at start of execution time, by ld-linux.so(8). Practically, the shared library should be position independent code (PIC)。
但它可能取决于,并且有 dlopen(3) 及其标志 RTLD_NOW
或 RTLD_LAZY
阅读Drepper's paper: How To Write Shared Libraries and the x86-64 ABI specification
您可以使用 strace(1) 来了解您自己的 Linux 系统上发生了什么。
原则上您可以使用 mmap(2) and processing by yourself the relocations 动态加载 foo.so
。我在上个世纪(对于 SPARC)已经(几乎)完成了这项工作,相信我,这是一项乏味的任务。
顺便说一句,dlopen
是在 GNU libc and in musl-libc 中实现的。都是免费软件,你可以研究一下他们的源代码。
另请阅读 Program Library HowTo。它解释了一些细节,简单地说:
使用
将共享对象的源文件编译为 PICgcc -Wall -fPIC -O src1.c -o src1.pic.o gcc -Wall -fPIC -O src2.c -o src2.pic.o
link 它们在共享库中
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);
}
有两(三)种类型的库:
- 静态库(后缀:
.a
/.lib
),它本身成为二进制文件的一部分。严格来说,它不是整个库,而是库中需要满足未解决的 links. 的那些对象
- 共享(动态)库(后缀:
.so
/.dll
),有两种形式,以加载库的时间区分:- dynamic link library,它们是您告诉编译器和 linker 的库,您称它们为静态库,但它们不是库的一部分 - 它们由 loader/linker 加载(在 Linux 中通常作为
__main()
的一部分从libc
通过调用dlopen()
)。 - 动态加载库,你自己调用
dlopen()
。
- dynamic link library,它们是您告诉编译器和 linker 的库,您称它们为静态库,但它们不是库的一部分 - 它们由 loader/linker 加载(在 Linux 中通常作为
(术语有点模糊,我看过不同的文献使用不同的术语,以上术语是我为了记住概念而背的。)
因此,如果您使用 a.so
而没有自己调用 dlopen()
,a.so
是一个动态 link 库,因此它会在程序启动时加载。在这种情况下,从系统中删除 a.so
将阻止您的程序启动 - 它会被加载,但会在调用 main()
之前失败。
如果您使用 a.so
并自己调用 dlopen()
,它完全在您的控制之下。
关于你的问题
Q1:如果你自己调用dlopen()
,使用RTLD_LAZY
,a.so
会在第一个可以被a.so
解析的未解析调用时加载.如果您自己调用 dlopen()
,使用 RTLD_NOW
,a.so
会立即加载,即在 dlopen()
returns 之前。如果您自己不调用 dlopen()
而是让 libc
为您完成工作,则 a.so
将在程序启动时加载。
Q2:你删除了a.so
。如果你用RTLD_LAZY
调用dlopen()
,而不是运行通过需要a.so
的代码,程序会运行愉快的,否则会产生一个信号.如果您不调用 dlopen()
而让 libc
为您完成工作,程序将无法成功启动。
Q3:实际上,如果不调用 dlopen()
(或替代它的等效内容),就无法加载 a.so
。问题只是,您是自己调用 dlopen()
,还是让环境(即 libc
)为您完成工作。
免责声明:我不是这方面的专家,我的部分回答可能是错误的。我将验证我自己怀疑的答案的那些部分,即是否是 libc
调用 dlopen()
或其他东西,以及是否可以进行延迟绑定,即使你'您自己没有使用 dlopen()
。得到结果后我会更新答案。