【问题标题】:Are functions marked with __attribute__(constructor) run again when a shared library is reloaded?重新加载共享库时,标有 __attribute__(constructor) 的函数是否会再次运行?
【发布时间】:2016-08-27 17:10:03
【问题描述】:

假设尚未加载与共享库libshlib 链接的其他可执行文件。并假设libshlib 包含一个标有__attribute__(constructor) 的函数和一个标有__attribute__(destructor) 的函数。当与libshlib 链接的可执行文件启动时,libshlib 将被加载,并且标有__attribute(constructor) 的相应函数将运行一次完全。但是如果可以重新加载共享库会发生什么,例如通过用户定义的信号,例如SIGUSR1?从我的测试来看,__attribute__(constructor) 似乎没有再次运行。这是正确的还是有其他标准说法?

【问题讨论】:

  • 如何重新加载共享库?

标签: c gcc clang shared-libraries


【解决方案1】:

我假设您有一个通过(例如)链接的程序:

cc -o mypgm mypgm.o -lshlib

在执行时,一旦 ELF 解释器加载了 libshlib.so 并执行了构造函数,库就不会再次加载。 旁注:要找到您的口译员,请执行以下操作:readelf -a mypgm | grep interpreter:

如果程序接收到一个信号(例如SIGUSR1),该信号要么被信号处理程序捕获(假设signalsigaction已被调用来设置一个),要么采取默认操作(这是SIGUSR1的[IIRC]程序终止。这不会导致库被重新加载。

没有其他操作也可能导致库被重新加载。只有析构函数会在程序退出时被调用(例如main 返回或exit 被调用)。

即使手动调用析构函数也没有效果,因为构造函数和析构函数是独立的。 (例如,构造函数可以执行able = malloc(...),析构函数可以执行free(able)。但是,析构函数可以执行free(baker)。)。调用析构函数不会“重置”构造函数。

要获得“重新加载”效果,需要通过dlopen/dlsym/dlclose动态加载/卸载库。也就是说,链接命令是:

cc -o mypgm mypgm.o

然后,mypgm 将 [在某些时候] 调用 dlopen("libshlib.so")(并且将调用构造函数)。当 [and if] mypgm 调用 dlclose 时,libshlib.so 将被卸载(并调用析构函数)。

如果mypgm 然后调用dlopen("libshlib.so") 次,构造函数被调用[再次]。


更新:

请注意,调用 dlclose 并不必然卸载库或调用析构函数。

我刚刚检查了代码 [in glibc]。该库有一个引用计数。如果在进入 dlclose 时引用计数为 1,则库被卸载,对于上面的 libshlib.sodlopen 应该是这种情况[因为没有其他人将其颠倒]。

换句话说,要强制执行“所需”行为,其他任何东西都不应通过-lshlib 引用libshlib。不是程序或任何其他.so。这奠定了基础。

请注意,如果libshlib.so 想要glibc,但程序也是如此,卸载libshlib 将降低glibc 引用计数,但glibc 将保留,因为它的引用计数[仍然] >0。

存在无法卸载库的情况(实际上,这些情况比可以卸载库的情况更常见)。

同样,这取决于引用计数和 [可能] 某些状态。当从“静态”链接加载库时(与 dlopen 相比),引用计数会增加一个额外的增量,因此不会被拉动。

代码还处理构造函数在其自己的库上调用dlopen的情况。

对于给定的libA,如果它需要libB,B 的引用计数会随着 A 的加载/卸载而增加/减少。

如果库没有卸载,那么析构函数是否会运行,以及后续的dlopen是否会再次运行构造函数都没有很好的定义

以这种方式对libshlib 使用dlopen 的全部意义在于保证dlopen 处加载并在dlclose 处卸载[以及构造函数/析构函数操作]。如果没有对其的静态引用或循环依赖(这是起始条件),则此为真。


更新 #2:

关于“因为没有其他人把它搞砸”的部分太简单了。

不要将散文与实质混淆。

如上所述:如果没有对它的静态引用或循环依赖,这个为真

这意味着只有shlib 执行dlopen/dlclose 的可执行文件/对象引用shlib 中的符号。

而且,这仅限通过dlsym。否则,它是一个静态引用(即在对象的符号表中作为 UNDEF)]。

而且,noshlib 拖入的共享库是指shlib [循环依赖] 中定义的符号。

查看符号解析期间设置了 DF_1_NODELETE 的所有位置。

是的,我确实看过了。

DF_1_NODELETE 设置在以下位置。它们都不适用于这种情况[或大多数dlopen 场景]。

  1. 如果dlopenflags 参数有RTLD_NODELETE,我们可以避免。
  2. 如果启用了分析,profile 映射 [not 与我们的 dlopen] 相关联得到 DF_1_NODELETE
  3. 如果符号具有类型 10 (STB_GNU_UNIQUE) 的绑定类型(例如 LOCALGLOBALWEAK 等)
  4. 符号被非动态对象引用为 [在其符号表中] 无法删除的 UNDEF [因为所指对象设置了 DF_1_NODELETE]。由于上述先决条件,这不适用。
  5. 添加依赖项时出现malloc 失败[来自_不同对象]。此代码甚至不会针对此处的案例执行。

而且,除了 OP 的使用,dlopen/dlclose 有正当理由按照我的设置/描述工作。

在这里查看我的答案:Is it possible to perform munmap based on information in /proc/self/maps?

在那里,OP 需要有一个可以运行数月/数年的不间断程序(例如,高可靠性、任务关键型应用程序)。如果 [通过包管理器等] 安装了其中一个共享库的更新版本,则程序必须动态、即时、重新执行,能够加载较新的版本。

【讨论】:

  • 请注意,调用dlclose 不会必然卸载库或调用析构函数。存在无法卸载库的情况(实际上,这些情况比卸载库可以的情况更常见)。如果库没有被卸载,那么析构函数是否会运行,以及后续dlopen是否会再次运行构造函数都没有很好的定义。
  • @EmployedRussian 我做了一些研究并更新了我的答案以解决您的问题 [希望 :-)]。
  • 谢谢,我还不清楚一件事:我已经实现了一个信号处理程序,可以重新加载 SIGUSR1 上的 shlib。
  • @CraigEstey 关于“因为没有人把它搞砸”的部分太简单了。查看符号解析期间设置DF_1_NODELETE的所有位置。
  • @lord.garbage 如果“重新加载的信号处理程序”是指您的符号处理程序 dlclosees 和 dlopens 库,请注意您的程序正在使用未定义的行为(并且会爆炸最多在不合时宜的时候当着你的面):你不能从信号处理程序调用dlclosedlopen或任何其他非异步信号安全函数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-03
  • 1970-01-01
  • 1970-01-01
  • 2014-01-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多