0

UPD (SOLVED): This issue has been solved by this - In short, the case CMAKE will produce visiable UNIQUE objects, and either -fvisibility=hidden (then add attribute visibility=default manually) or --no-gnu-unique can avoid the problem.

I found this when importing a large project into myselfs, and the following codes should be a minimal demo to illustrate it:

/* ---- header.h ---- */
struct A {
  typedef (void (*func_t)();
  static void empty() {}
  static constexpr func_t funcs[1] = {empty};
  const func_t *func_list;
  A() { func_list = funcs; }
};
struct B {
  B();
};
struct X {
  A a;
  B b;
};
/* ----- header.cpp ----- */
#include "header.h"
B::B() {}

/* ----- main.cpp ----- */
#include "header.h"
extern "C" {

void test() {
  auto x = new X;
  delete x;
  // print(version_nubmer); 
}
}

The library is built from CMake

add_library(main SHARED main.cpp header.cpp)
target_compile_options(main PRIVATE -fPIC)

Then I use a program that sequentially calls dlopen(); dlsym(test)(); dlclose(); dlopen(); dlsym(test)(); on this library (with RTLD_LAZY | RTLZ_LOCAL), where before the second dlopen(), I change the version number to a different value, expecting it print the updated one. But, it doesn't. I still see it give the old value, which means dlclose() does not really detach the lib.

If I set CMAKE_INTERPROCEDURAL_OPTIMIZATION to ON, or manually build it from command line (e.g., g++ -o libmain.so -fPIC -shared main.cpp header.cpp) the observation disapears. Can anyone tell the difference here?

UPD: Between the dlclose() and second dlopen(), we insert something to block the program like scanf() or getchar(), and continue the program only after we rebuilt the lib. This will ensure the reproduction to the three cases (CMAKE, CMAKE+lto, cmd).

Yuxai
  • 65
  • 1
  • 5
  • How do you change the version number? And how do you know that version number change and recompilation of `libmain` take place exactly between `dlclose` and `dlopen`? – pptaszni Oct 06 '22 at 10:33
  • 1
    @pptaszni Use something like scanf() to block the prog, I push a UPD to the description. – Yuxai Oct 06 '22 at 12:38
  • You don't need to specify `-fPIC` manually. For `SHARED` (or `MODULE`) library targets the option should be added automatically and if want to enable the option, you should set the [`POSITION_INDEPENDENT_CODE` target property](https://cmake.org/cmake/help/latest/prop_tgt/POSITION_INDEPENDENT_CODE.html) to `True` and let cmake choose the options. For targets that are supposed to be loaded using `dlopen` exclusively, a `MODULE` library is more suitable than a `SHARED` one btw. – fabian Oct 06 '22 at 18:18
  • @fabian thx to explain. I did set the POSITION_INDEPENDENT_CODE to ON in cmake instead of add -fPIC manually since I also required other imported targets are compiled with the flag. – Yuxai Oct 07 '22 at 02:14

1 Answers1

1

You can not rely on dlclose() to unload any library.

Per POSIX (bolding mine):

The dlclose() function shall inform the system that the symbol table handle specified by handle is no longer needed by the application.

An application writer may use dlclose() to make a statement of intent on the part of the process, but this statement does not create any requirement upon the implementation. ...

Assuming you're running on Linux:

A successful return from dlclose() does not guarantee that the symbols associated with handle are removed from the caller's address space.

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
  • We know it's not guaranteed indeed, but for the three cases I mentioned, the most interests is on how they differ and then how to avoid the bad case. If we actually cannot, then at least we need to show that the even the last two cases (CMAKE+lto, cmd) can be broken. AFAK, the last two cases works even if I import heavy codes from a large non-head-only projects. – Yuxai Oct 07 '22 at 02:28