4

Setup

Suppose I wrote a program in C/C++ and wanted to allow plugin loading. The typical solution would be to write the plugin as:

plugin.c

int my_plugin_fn() {
    return 7;
}

And compile it using something like gcc -fpic -shared -o plugin.so plugin.c

Then, in the main program that loads this plugin, we might have:

loader.c

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

int main() {
    void *plugin_handle = dlopen("./plugin.so", RTLD_LAZY);
    if (!plugin_handle) {
        printf("Could not open shared object: %s\n", dlerror());
        return -1;
    }

    int (*fn)() = dlsym(plugin_handle, "my_plugin_fn");
    char *err = dlerror();
    if (err) {
        printf("Could not resolve symbol: %s\n", err);
        return -1;
    }

    printf("Plugin object returned: %d\n", fn());

    return 0;
}

I compiled loader.c with gcc -o loader loader.c -ldl, and after running it, the output was Plugin object returned: 7, as expected.

Question

Suppose we want to add functions in our main program (loader.c) that plugins can use. For example,

loader_v2.c

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

int times_2(int x) {
    return 2*x;
}

int main() {
    void *plugin_handle = dlopen("./plugin_v2.so", RTLD_LAZY);
    if (!plugin_handle) {
        printf("Could not open shared object: %s\n", dlerror());
        return -1;
    }

    int (*fn)() = dlsym(plugin_handle, "my_plugin_fn");
    char *err = dlerror();
    if (err) {
        printf("Could not resolve symbol: %s\n", err);
        return -1;
    }

    printf("Plugin object returned: %d\n", fn());

    return 0;
}

plugin_v2.c

extern int times_2(int);

int my_plugin_fn() {
    return times_2(7);
}

Compiling and running these files in the same way as before produces Could not open shared object: ./loader_v2: symbol lookup error: ./plugin_v2.so: undefined symbol: times_2.

Is there a way to have plugins loaded using dlopen() call functions from the program that loaded them?

Acorn
  • 24,970
  • 5
  • 40
  • 69
Marco Merlini
  • 875
  • 7
  • 29

2 Answers2

4

Is there a way to have plugins loaded using dlopen() call functions from the program that loaded them?

Yes, but the function that you want to call from the main executable must be exported from it, which does not happen by default. You can see which symbols are exported from your main binary with nm -D loader.

You can export all functions defined in the main executable by linking it with the -rdynamic flag.

Some linkers, most notably newer versions of GNU-ld, GOLD and LLD, support the --export-dynamic-symbol flag, which allows one to selectively export just the symbol(s) you need.

In your case, you would link your loader executable with -Wl,--export-dynamic-symbol=times_2.

Employed Russian
  • 199,314
  • 34
  • 295
  • 362
  • 1
    This is better than callbacks. See ["What exactly does `-rdynamic` do and when exactly is it needed?"](https://stackoverflow.com/a/36700270/9305398). – Acorn Aug 18 '20 at 04:24
  • Confirmed that this works! My version of ld (2.34.0) doesn't have the `--export-dynamic-symbol` option, but you can use `--dynamic-list` and write file that lists the symbols you want to export. For me, this file looked like `{times_2;};` – Marco Merlini Aug 18 '20 at 14:18
1

The best way to do something like this is with a function pointer. You would pass the function pointer into the library function, which would subsequently call it.

So the library function would look like this:

int my_plugin_fn(int (*cb)(int)) {
    return cb(7);
}

The call to dlsym would then look like this:

int (*fn)(int (*)(int)) = dlsym(plugin_handle, "my_plugin_fn");

And you would call the library function like this:

printf("Plugin object returned: %d\n", fn(times_2));
dbush
  • 205,898
  • 23
  • 218
  • 273
  • This does what I need. Out of curiosity, is this the only way? For example, is it possible to get the link editor to resolve the symbol when using dlopen on plugin_v2.so? – Marco Merlini Aug 05 '20 at 04:30
  • 1
    @MarcoMerlini Not when using `dlopen` as the main executable is not linked to the library. Had the library been listed as a dynamic library at link time and `my_plugin_fn` called directly by name then you could do it. – dbush Aug 05 '20 at 04:36
  • 1
    You could put the common function in a separate shared library that both the executable and the dlopen library link with. – Brecht Sanders Aug 05 '20 at 17:38
  • This answer is not entirely wrong, but isn't necessarily "the best way". – Employed Russian Aug 18 '20 at 04:17
  • I've accepted @EmployedRussian's answer, but I sill think this is a great answer because it's portable. This works on both Windows and Linux – Marco Merlini Aug 18 '20 at 14:20