在主程序运行时替换共享对象(.so文件)

时间:2011-10-14 12:05:23

标签: c linux shared-libraries

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

问题A

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

我是对的吗?

所以我无法用更新版本替换gateway.so,而a.out正在运行,对吗?

问题B

另一个相关问题:一旦我替换了版本的gateway.so文件,我得到了消息

  

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

哪个程序组件(loader / linker ...)发送此输出?该组件是作为相同流程上下文的一部分执行的吗?

4 个答案:

答案 0 :(得分:28)

问题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

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

现在让我们尝试删除它:

$ 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
$

因此不禁止更换程序使用的库!但从上面的例子可以看出,它可能带来灾难性的后果。

幸运的是,用于替换正在运行的二进制文件的相同“技巧”可用于替换正在使用的lib。重新启动主程序(不要忘记重新编译libshared.so,因为它被垃圾代替)并看看如何安全地在库上执行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?。没有“执行失败”表示该过程已成功替换。这意味着内核将控制转移到失败的动态链接器。删除“main”后,它会提前失败并且不会替换该过程。

答案 1 :(得分:10)

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

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

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

答案 2 :(得分:2)

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

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

答案 3 :(得分:0)

一个。对。在这种情况下,您必须使用dl_*()内容并尽快关闭文件。

湾如果您替换所述文件,并且它不包含必需的符号,则加载失败并且您收到所述错误。