Search code examples
linuxdynamic-linkingld-preloaddlsym

When is late enough to use dlsym to find functions in another shared library?


Say I have an application foo that is linked against bar.so. I have another library, buzz.so that the application does not directly link, but that I use when I run the application with LD_PRELOAD like so: LD_PRELOAD=buzz.so ./foo. Now I want buzz.so to be able to use dlsym to find a symbol inside bar.so. My question is: what is the earliest point in time the dlsym call can succeed and how do run a function at that time?

GCC/Clang support annotating functions with the __constructor__((priority)) attribute to make them run before main, but AFAIK there isn't any priority number that corresponds to being sure that all shared libraries are loaded.


Solution

  • what is the earliest point in time the dlsym call can succeed and how do run a function at that time?

    When buzz.so initializes, nothing stops it from using dlopen() to load bar.so, and once that dlopen() finishes, you can use dlsym().

    The fact that bar.so is also a dependency of main (and therefore bar.so would be loaded later as part of loading main) is irrelevant.

    Alternatively, you can link buzz.so against bar.so (use -Wl,--no-as-needed to force the linker to record this dependency).

    Example with dlopen:

    // main.c
    extern int bar();
    int main() { return bar(); }
    
    // bar.c
    int bar() { return 42; }
    
    // buzz.c
    #include <dlfcn.h>
    #include <stdio.h>
    __attribute__((constructor)) void init_buzz()
    {
      printf("loading bar.so\n");
      void *handle = dlopen("./bar.so", RTLD_LAZY|RTLD_GLOBAL);
      if (handle == NULL) {
        fprintf(stderr, "dlopen: %s\n", dlerror());
        return;
      }
      int (*pbar)() = (int (*)()) dlsym(handle, "bar");
      if (pbar == NULL) {
        fprintf(stderr, "dlsym: %s\n", dlerror());
        return;
      }
      printf("bar.so bar() = %d\n", (*pbar)());
    }
    

    Putting it all together:

    gcc -fPIC -shared -o bar.so bar.c &&
    gcc main.c ./bar.so &&
    gcc -fPIC -shared -o buzz.so buzz.c -ldl
    
    ./a.out ; echo $?
    42
    
    LD_PRELOAD=./buzz.so ./a.out
    loading bar.so
    bar.so bar() = 42
    

    Example with direct dependency:

    // buzz-direct.c
    #include <dlfcn.h>
    #include <stdio.h>
    __attribute__((constructor)) void init_buzz()
    {
      int (*pbar)() = (int (*)()) dlsym(RTLD_DEFAULT, "bar");
      if (pbar == NULL) {
        fprintf(stderr, "dlsym: %s\n", dlerror());
        return;
      }
      printf("bar() = %d\n", (*pbar)());
    }
    
    gcc -fPIC -shared -o buzz-direct.so buzz-direct.c -Wl,--no-as-needed ./bar.so -ldl
    LD_PRELOAD=./buzz-direct.so ./a.out
    bar() = 42
    

    P.S. Using dlsym() in the "direct dependency" case is a bit silly: you could just call it directly:

    #include <stdio.h>
    extern int bar();
    __attribute__((constructor)) void init_buzz()
    {
      printf("direct call bar() = %d\n", bar());
    }
    
    gcc -fPIC -shared -o buzz-direct2.so buzz-direct2.c ./bar.so
    LD_PRELOAD=./buzz-direct2.so ./a.out
    direct call bar() = 42