【问题标题】:Replacing shared object (.so file) while main program is running在主程序运行时替换共享对象(.so 文件)
【发布时间】:2011-12-07 17:09:25
【问题描述】:

我有一个共享对象 gateway.so(在 Linux/C 中)。 a.out 应用程序正在使用它。

问题 A

我猜:当进程 a.out 启动时,加载程序会加载 gateway.so(我没有使用像 dlopen 这样的 dl 函数)。所以所有对 gateway.so 的运行时符号解析都将发生在内存中。它不再需要从磁盘访问 gateway.so。

我说的对吗?

所以我不能用更新版本替换 gateway.so,而 a.out 正在运行,对吧?

问题 B

另一个相关问题:有一次当我替换和过时的 gateway.so 文件版本时,我收到了消息

"a.out: 无法解析符号'Test_OpenGateway'"

哪个程序组件(加载器/链接器...)发送此输出?该组件作为同一进程上下文的一部分执行?

【问题讨论】:

  • @all answerer,如果可能的话,请在这种情况下提供一些你的答案证明......

标签: c linux shared-libraries


【解决方案1】:

一个。正确的。在这种情况下,您必须使用 dl_*() 的东西并尽快关闭文件。

b.如果您替换了上述文件,并且它不包含所需的符号,则加载失败并出现上述错误。

【讨论】:

    【解决方案2】:

    不,一旦运行时链接器 (ld.so) 将文件映射到进程的地址空间,可能仍需要从磁盘读取文件。这种映射发生的方式是通过mmap(2) 系统调用和标志PROT_EXEC 来允许执行。

    映射不会在映射后将整个文件放入内存,但实际上会创建一个内存区域,如果请求的内存尚未复制,则该内存区域将按需调用页面错误,并且该页面错误通过读取文件中的适当偏移量在内核空间中进行处理。

    关于第二个问题,运行时链接器 (ld.so) 对此有所抱怨。加载ld.so 的代码由编译时链接器(ld)作为程序启动代码发出,因此它在调用main 之前在用户空间中执行。

    【讨论】:

    • 如果可能,请提供该主题的一些证明/文档
    • 最好的证明应该是在程序执行时尝试删除gateway.so文件。因为它是mmap-ped,内核应该已经锁定它并且不应该允许删除。
    • 当主程序运行并且 a.out 继续运行没有问题时,我可以删除 gateway.so。但是下次我启动 a.out 时,我收到错误“无法加载库”
    • (上面的评论:通过术语“删除 gateway.so”,我实际上重命名了 gateway.so 。没有删除)
    • 这可能是一个“幸运”的情况,如果内核已经确定所有mmap-ped 区域都很短并且可以直接放入内存,它可以安全地解锁文件,但在原理上,mmap(2)背后的思想是实现按需分页
    【解决方案3】:

    给 A: 是的,一旦共享库映射到内存,你就不能再替换它了。甚至可能系统已经为其他进程加载了以前版本的 lib,并检测到 so 已经映射到内存并将其重新映射为启动过程的一部分。这就是为什么你总是必须在关键更新后重新启动(甚至 *nixes);)

    给 B: 可执行文件使用的符号记录在二进制文件的符号表中。系统加载程序扫描此表并尝试解析所需函数的地址。如果它找不到它,您会收到此错误。所以答案是,消息是由动态链接加载器产生的。

    【讨论】:

      【解决方案4】:

      问题 A

      如果您以正确的方式操作,您可以在应用程序使用库时替换它。

      在我们到达那里之前,让我们看一下主程序二进制文件。这是一个示例程序:

      #include <unistd.h>
      
      void justsit(void) {
        for (;;) {
          sleep(1);
        }
      }
      
      int main(int argc, char **argv) {
        printf("My PID is %d\n", getpid());
        justsit();
        return 0;
      }
      

      编译并启动它:

      $ gcc -Wall -o example example.c
      $ ./example
      My PID is 4339
      

      现在它会坐在那里,所以打开一个新终端来执行此操作:

      $ gcc -Wall -o example-updated example.c
      $ cp example-updated example
      cp: cannot create regular file `example': Text file busy
      

      现在发生了什么?内核拒绝更改文件example,因为它有一个进程正在运行该文件。

      现在让我们尝试删除它:

      $ rm example
      

      什么?那行得通吗?为什么文件可以删除,但不能替换?是的,或者更确切地说,文件并没有真正删除,只是“名称”,内核告诉文件系统保留文件的内容。当文件不再打开时,内容也会被删除。 (dentry 被立即删除,但 inode 在没有用户时被释放,正如人们所说的文件系统)

      这可以在 /proc 中看到:(这就是程序打印其 PID 以便您可以轻松检查的原因)

      $ readlink /proc/4339/exe
      /tmp/t/example (deleted)
      

      无论如何。它这样工作的事实意味着人们可以通过删除旧的二进制文件并将新的二进制文件放在同一位置来安全地升级程序。有一个程序可以处理这个问题:install(1)。

      好的,回到您的问题 - 共享对象。

      让我们把例子分成两部分,main.c 和 shared.c:

      /* main.c */
      #include <stdio.h>
      #include <sys/types.h>
      #include <unistd.h>
      
      void justsit(void);
      
      int main(int argc, char **argv) {
        printf("My PID is %d\n", getpid());
        justsit();
        return 0;
      }
      

      /* shared.c */
      #include <stdio.h>
      #include <unistd.h>
      
      void justsit(void) {
        for (;;) {
          sleep(1);
        }
      }
      

      像这样编译它们:

      $ gcc -Wall --shared -o libshared.so shared.c 
      $ gcc -Wall -L. -o main main.c -lshared
      

      现在希望如果我们尝试替换 libshared.so 我们会得到类似的“文本文件忙”错误?让我们来看看。首先启动主程序 - 当前目录不在 lib 搜索路径中,所以告诉动态链接器在那里搜索:

      $ LD_LIBRARY_PATH=. ./main 
      My PID is 5697
      

      转到另一个终端并用明显损坏的东西替换库:

      $ echo "junk" > libshared.so 
      $
      

      首先 - 它并没有像替换程序二进制文件那样被拒绝。 在另一个终端发生了一些有趣的事情,程序停止运行并显示以下错误消息:

      Segmentation fault
      $
      

      因此,不禁止替换程序正在使用的库!但从上面的例子可以看出,它可能会产生灾难性的后果。

      幸运的是,用于替换正在运行的二进制文件的相同“技巧”可用于替换正在使用的库。重新启动主程序(不要忘记重新编译 libshared.so,因为它已被 junk 替换)并查看在库上执行 rm 是如何安全的。可以检查 /proc/PID/maps 以查看进程正在使用哪些共享对象:

      $ cat /proc/5733/maps  | grep libshared.so
      008a8000-008a9000 r-xp 00000000 08:01 2097292    /tmp/t/libshared.so
      008a9000-008aa000 r--p 00000000 08:01 2097292    /tmp/t/libshared.so
      008aa000-008ab000 rw-p 00001000 08:01 2097292    /tmp/t/libshared.so
      $ rm libshared.so 
      $ cat /proc/5733/maps  | grep libshared.so
      008a8000-008a9000 r-xp 00000000 08:01 2097292    /tmp/t/libshared.so (deleted)
      008a9000-008aa000 r--p 00000000 08:01 2097292    /tmp/t/libshared.so (deleted)
      008aa000-008ab000 rw-p 00001000 08:01 2097292    /tmp/t/libshared.so (deleted)
      

      主程序继续正常运行。这又是因为从磁盘中删除的只是名称(dentry),而不是实际内容(inode)。删除后,可以安全地创建一个名为 libshared.so 的新文件,而不会影响正在运行的程序。

      所以,总结一下 - 只需使用 install 命令安装程序和二进制文件。

      问题 B

      是的,由动态链接器在用户空间打印。

      #include <stdio.h>
      #include <unistd.h>
      
      int main(int argc, char **argv) {
          execl("./main", "main", NULL);
          printf("exec failed?\n");
          return 0;
      }
      

      gcc -Wall -o execit execit.c 编译它。请记住,execl 将当前进程替换为指定的命令。

      $ ./execit 
      main: error while loading shared libraries: libshared.so: cannot open shared object file: No such file or directory
      $ rm main
      $ ./execit 
      exec failed?
      

      发生了什么,它告诉我们什么?首先是error while loading shared libraries,没有exec failed?。没有“exec failed”表明进程被成功替换。这意味着内核将控制权转移给了失败的动态链接器。删除“main”后,它会提前失败,并且不会替换该进程。

      【讨论】:

      • 我只想评论这是一个很棒的答案,因为对屏幕后面发生的事情的解释。但我想进一步澄清:所以rm 将文件从dentry 中取消链接,而不是从inode 取消链接。文件在什么时候被完全删除?是在应用程序停止时(即 /proc//... 被删除)吗?或者我们是否开始使用每个rm 占用文件系统中的更多空间 - 我试图弄清楚文件何时最终完全删除。谢谢!
      • @code_fodder 我认为现代 Unix 文件系统会进行引用计数。当进程退出时,它是对文件的最后一次引用,因此文件被释放。
      • 由于某种原因,现代版本的内核不再发出 Text file busy 错误。
      • 我认为替换.so文件时出现段错误的原因是它尚未加载。如果已加载,则更改磁盘上的文件不会影响已加载它的任何进程。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-11-16
      • 1970-01-01
      • 1970-01-01
      • 2012-05-12
      • 2019-09-11
      • 2019-04-23
      • 1970-01-01
      相关资源
      最近更新 更多