4

I am currently developing a program module called test.so. Inside this test.so, there is code that loads and uses an external library called wow.so.

To test this module, I created a new test project. The execution flow is as follows: Test program (main) → test.so → internally loads wow.so

Because test.so needs to automatically execute some code upon being loaded, I used a constructor function like this: static void __attribute__((constructor)) my_init() { ... }

However, this constructor function is not working properly.

After some research, I found that attempting to load another .so file using dlopen() within a constructor function can fail. This appears to be due to loader lock or the initialization order of dynamic libraries on Linux.

Furthermore, I do not have the source code or header files for wow.so; I only have the compiled .so file. Also, I must solve this issue by modifying only test.so — I cannot modify any other part.

asked May 16, 2025 at 10:31
5
  • If executing from within the constructor is the problem, would it work to outsource the loading to another thread which loads the other library? Commented May 16, 2025 at 11:46
  • 2
    If dlopen fails, what does dlerror tell you? Commented May 16, 2025 at 12:20
  • You have to make sure your constructor function is part of the .init section. Can you check with readelf if it is? Commented May 16, 2025 at 15:26
  • 1
    maybe you can simply create test.so with -lwow instead of loading libwow.so by dlopen(). Then you could even use the symbols of libwow.so directly.. But of course that could be in contradiction to what you intend to test with test.so Commented May 16, 2025 at 16:27
  • You may want to set LD_DEBUG so you can have the ELF interpreter tell you what it's doing Commented May 16, 2025 at 20:23

1 Answer 1

3

This behavior is related to glibc's dynamic loader. When libraries are loaded recursively, if a constructor function attempts to call dlopen() to load another shared library, it can lead to deadlocks or undefined behavior because dlopen() tries to acquire the loader lock which is already held during constructor execution.

Several bug reports have been filed but it is not considered a bug in glibc.
Red Hat Bugzilla Bug #661676
Debian glibc Bug #786397
Sourceware Bugzilla Bug #15686

The same behaviour also occurs in windows, due to the similarity of Windows loader lock and glibc's loader lock. MSDN - DLL Best Practices

Constructor functions (Glibc) or DllMain function (Windows) are executed before the main() function. It is recommended that constructors perform simple initializations. You shouldn’t perform operations that might block or wait on external resources inside constructor functions. Calling functions like dlopen() (in glibc) or LoadLibrary() (in windows) in constructors can lead to deadlocks or undefined behavior.

In glibc, dlopen() is marked as AS-Unsafe (asynchronous-signal-unsafe) and AC-Unsafe (asynchronous-cancel-unsafe) because it requires holding internal loader locks. Therefore, dlopen() should only be called in normal, sequential program code.
Glibc - Unsafe Features

On Windows, the situation is similar. Microsoft's documentation warns about the limitations of what can safely be done inside DllMain() (same as glibc’s constructor functions).
MSDN - DllMain

There are two valid options to safely load additional libraries and perform other similar operations:

1. Define an Explicit Initialization Function
Just like many other libraries (e.g. libcurl, libusb etc.) that require you to call an init() function explicitly in your main program, you can do the same for initialization.

static void *wow_handle = NULL;
int test_init(void) {
 if (!wow_handle) {
 wow_handle = dlopen("./libwow.so", RTLD_NOW);
 if (!wow_handle) {
 fprintf(stderr, "[libtest.so] dlopen failed: %s\n", dlerror());
 return -1;
 }
 return 0;
 }
 return 0;
}

2. Lazy Load or Lazy Perform on first use
You can delay loading the library or delay operations until the first time a dependent function is called. This avoids early initialization but requires checking on each use.

static void *wow_handle = NULL;
typedef void (*wow_func)(void);
static wow_func wow_func = NULL;
void test_function_requires_wow(void) {
 if (!wow_handle) {
 wow_handle = dlopen("./libwow.so", RTLD_NOW);
 if (!wow_handle) {
 fprintf(stderr, "[libtest.so] dlopen failed: %s\n", dlerror());
 return;
 }
 wow_func = (wow_func)dlsym(wow_handle, "wow_func");
 if (!wow_func) {
 fprintf(stderr, "[libtest.so] dlsym failed: %s\n", dlerror());
 return;
 }
 }
 wow_func();
}
answered May 16, 2025 at 21:55
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.