2

Code:

//a.c   I don't use header files as this is just for demo purpose.
extern void function_b(int num);
void function_a(int num) {
   function_b(num)
}
//b.c
void function_b(int num) {
   ...
}
//dll.c 
#include <dlfcn.h>
int main() {
   void *handle_a;
   void *handle_b;
   void (*pfunc_a)(int);
   ...
   handle_a = dlopen("./a.so", RTLD_LAZY);
   ...
   pfunc_a = dlsym(handle_a, "function_a");
   ...
   handle_b = dlopen("./b.so", RTLD_GLOBAL);
   ...
   pfunc_a(2020);
   ...
   return 0;
}

We can see that dll.c tries to load shared library in run time and module a has a reference on function_b and module b has the definition of function_b. Let's say we have already created shared libraries a.so, and b.so so those shared libraries exist on disk before the program runs, but when I run the program, it throws an symbol lookup error:

./a.so:undefined symbol: function_b

but for this line of code handle_a = dlopen("./a.so", RTLD_LAZY); since I use RTLD_LAZY here, the runtime linker doesn't try to resolve the symbol function_b, and there's an opportunity for me to call dlopen("b.so", RTLD_GLOBAL) before calling function_a. This way the dynamic linker will modify the reference in a.so with the definition of function_b in b.so.

My questions are:

  • Is my understanding correct that the dynamic linker is supposed to modify the .got or .got.plt section of a.so so that it can be linked/relocated to the instruction address of function_b in the .text section of b.so.

  • If my understanding is correct, then why couldn't the dynamic linker still resolve function_b in this case?

1

1 Answer 1

3

The problem isn’t that the dynamic linker can’t resolve function_b, it’s that your second call to dlopen is incorrect: you need to include either RTLD_LAZY or RTLD_NOW, the other flags are complementary to those two.

One of the following two values must be included in flags:

Changing your b.so load to

handle_b = dlopen("./b.so", RTLD_NOW | RTLD_GLOBAL);

produces a working program.

Every call to dlopen must choose between RTLD_LAZY and RTLD_NOW; since b.so is the last library loaded, I specified NOW above (we don’t gain anything by lazy-loading), but LAZY works just as well in this instance. On top of that, other flags can be added; here we need RTLD_GLOBAL because we need b.so’s symbols to be made available globally, so that function_a can find function_b when it runs.

See the examples in dlopen(3) for details of the error-handling you need to do with dlopen etc., which reveals the problem.

3
  • Thank you for your answer. could you explain a little bit more , why RTLD_NOW | RTLD_GLOBAL is needed?
    – slowjams
    Sep 3, 2020 at 14:30
  • @StephenKitt so the definition of RTLD_NOW: all undefined symbols in the shared object are resolved before dlopen() returns. But my b.so doesn't have any undefined symbols, why it still need to be loaded it with RTLD_NOW?
    – amjad
    Sep 4, 2020 at 0:42
  • 1
    Because dlopen requires you to specify LAZY or NOW. If you don’t, it fails with an “invalid flags” error. Try adding error-handling to your example and you’ll see. Sep 4, 2020 at 4:35

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .