cshared-librariesdlopendlsym

Is there a compiler flag which allows a shared library to call a function coming from the shared library which opened it?


Below is a minimal reproducible example, where the goal is to call b_util() from a(). My question is whether it's possible to do this without explicitly passing a b_util() function pointer to a.c.

I am looking for a flag like -rdynamic, as discussed in this Stack Overflow post, which allows a shared library to call functions from the main executable. Compiling a.c or b.c with this flag prints the same runtime error, but maybe there is a way the flag could be utilized?

You can compile and run the below code with gcc c.c -o c.out && gcc b.c -o b.so -shared -fPIC && gcc a.c -o a.so -shared && ./c.out, but the problem is that it prints dlopen: ./a.so: undefined symbol: b_util:

// a.c

void b_util(void);

void a(void) {
    b_util();
}
// b.c

#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>

void assert_dl(void *p, char *fn_name) {
    if (!p) {
        fprintf(stderr, "%s: %s\n", fn_name, dlerror());
        exit(EXIT_FAILURE);
    }
}

void b(void) {
    void *dll = dlopen("./a.so", RTLD_NOW);
    assert_dl(dll, "dlopen");

    void (*a)(void) = dlsym(dll, "a");
    assert_dl(a, "dlsym");

    a();
}

void b_util(void) {
    printf("b_util\n");
}
// c.c

#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>

void assert_dl(void *p, char *fn_name) {
    if (!p) {
        fprintf(stderr, "%s: %s\n", fn_name, dlerror());
        exit(EXIT_FAILURE);
    }
}

int main(void) {
    void *dll = dlopen("./b.so", RTLD_NOW);
    assert_dl(dll, "dlopen");

    void (*b)(void) = dlsym(dll, "b");
    assert_dl(b, "dlsym");

    b();
}

Solution

  • Some programmer dude in the comments made me aware of the RTLD_GLOBAL flag, and I indeed only needed to change dlopen("./b.so", RTLD_NOW) to dlopen("./b.so", RTLD_NOW | RTLD_GLOBAL).

    A little later I figured out that there is no need for b.so to open a.so. c.c can just open b.so with RTLD_GLOBAL, making b_util visible as if it had been linked directly into the program. After this, dlopen("./a.so", RTLD_NOW) succeeds:

    // a.c
    
    void b_util(void);
    
    void a(void) {
        b_util();
    }
    
    // b.c
    
    #include <stdio.h>
    
    void b_util(void) {
        printf("b_util\n");
    }
    
    // c.c
    
    #include <dlfcn.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    void assert_dl(void *p, char *fn_name) {
        if (!p) {
            fprintf(stderr, "%s: %s\n", fn_name, dlerror());
            exit(EXIT_FAILURE);
        }
    }
    
    int main(void) {
        // Comment dlopen("./b.so") out to cause `undefined symbol: b_util`
        void *dll_b = dlopen("./b.so", RTLD_NOW | RTLD_GLOBAL);
        assert_dl(dll_b, "dlopen");
    
        void *dll_a = dlopen("./a.so", RTLD_NOW);
        assert_dl(dll_a, "dlopen");
    
        void (*a)(void) = dlsym(dll_a, "a");
        assert_dl(a, "dlsym");
    
        a();
    }