.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_NOWRTLD_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_LAZYa.so会在第一个可以被a.so解析的未解析调用时加载.如果您自己调用 dlopen(),使用 RTLD_NOWa.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()。得到结果后我会更新答案。