重新加载共享库时,函数是否再次标记为 __attribute__(constructor) 运行?

Are functions marked with __attribute__(constructor) run again when a shared library is reloaded?

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

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

cc -o mypgm mypgm.o -lshlib

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

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

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

甚至手动调用析构函数也没有效果,因为构造函数和析构函数是独立的。 (例如,构造函数可以执行 able = malloc(...),而析构函数可以执行 free(able)。但是,析构函数可以执行 free(baker)。)调用析构函数不会 "reset" 构造函数。

要获得 "reload" 效果,库需要 动态 loaded/unloaded 通过 dlopen/dlsym/dlclose。也就是说,link 命令将是:

cc -o mypgm mypgm.o

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

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


更新:

Note that calling dlclose does not necessarily unload the library or call destructors.

我刚刚检查了 [在 glibc] 中的代码。图书馆有refcount。如果在进入 dlclose 时引用计数为 1,则库 将被卸载 ,上面的 libshlib.sodlopen 应该是这种情况 [因为没有人否则会增加它。

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

请注意,如果 libshlib.so 想要 glibc,但程序也是如此,卸载 libshlib 会降低 glibc 引用计数,但 glibc 会保持不变,因为它的引用计数是 [still] >0.

There are conditions where the library can't be unloaded (in fact, these conditions are much more common then conditions when the library can be unloaded).

同样,这取决于引用计数和[可能]某些状态。当库从 "static" linkage 加载时(与 dlopen 相比),refcount 会额外增加,因此不会被抽取。

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

对于给定的 libA,如果它需要 libB,B 的引用计数将由 A 的 load/unload.

得到 upped/downed

If the library is not unloaded, then it's not well defined whether destructors will run, and whether the subsequent dlopen will run constructors again

libshlib 以这种方式使用 dlopen 的全部意义在于 保证 dlopen 加载并在 [=27] 卸载=] [连同 constructor/destructor 操作]。如果没有对它的静态引用或循环依赖,这 为真,这是起始条件。


更新#2:

The part about "as nobody else bumps it up" is way too simplistic.

不要混淆散文和实质内容。

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

这意味着只有shlibdlopen/dlclose的executable/object引用了shlib中的一个符号。

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

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

Look at all the places where DF_1_NODELETE is set during symbol resolution.

是的,我看了

DF_1_NODELETE只在以下地方设置。 None 适用于这种情况 [或大多数 dlopen 情况]。

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

并且,撇开 OP 的用法不谈,dlopen/dlclose 有合理的理由可以像我设置的那样工作 up/described。

在这里查看我的回答:

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