c++pluginslinkerdlopen

dlopen wayfire plugin results in undefined symbol: _ZTIN2wf3txn20transaction_object_tE


To avoid an A/B question: ultimately what I want to do is to find out whether a plugin binary for wayfire that I have (firedecor in my case) has been compiled with a new enough version of wayfire such that the API/ABI version supported by the plugin (as can be retrieved with its exported getWayfireVersion) matches the version of today's wayfire or if the plugin has to be recompiled against the new wayfire version.

To find out the wayfire api/abi version against which my plugin shared library was compiled so that I can ultimately perform the desired comparison, I write a short C++ program. It dlopens the plugin shared library and attempts calling getWayfireVersion() and compares its output against the value of WAYFIRE_API_ABI_VERSION as retrieved by the current version of the included wayfire headers:

#include <wayfire/plugin.hpp>
#include <errno.h>
#include <dlfcn.h>
#include <stdio.h>
#include <inttypes.h>
int main(int argc, char* argv[]) {
    void *handle = dlopen(argv[1], RTLD_NOW | RTLD_GLOBAL);
    if (handle == NULL) {
        fprintf(stderr, "dlopen failed: %s\n", dlerror());
        return 1;
    }
    uint32_t (*version_func)() = (uint32_t (*)())dlsym(handle, "getWayfireVersion");
    if (version_func == NULL) {
        perror("dlsym failed");
        dlclose(handle);
        return 1;
    }
    int32_t plugin_abi_version = version_func();
    if (WAYFIRE_API_ABI_VERSION != plugin_abi_version) {
        fprintf(stderr, "API/ABI version mismatch:\n");
        fprintf(stderr, "wayfire is: %" PRIu32 "\n", WAYFIRE_API_ABI_VERSION);
        fprintf(stderr, "plugin is: %" PRIu32 "\n", plugin_abi_version);
        dlclose(handle);
        return 1;
    }
    fprintf(stderr, "wayfire is: %" PRIu32 "\n", WAYFIRE_API_ABI_VERSION);
    fprintf(stderr, "plugin is: %" PRIu32 "\n", plugin_abi_version);
    dlclose(handle);
    return 0;
}

I compile it:

$ g++ test.cc -o test -Wall -ldl $(pkg-config --cflags pixman-1) -DWLR_USE_UNSTABLE

But when I run it I get:

$ ./test /usr/lib/aarch64-linux-gnu/wayfire/libfiredecor.so
dlopen failed: /usr/lib/aarch64-linux-gnu/wayfire/libfiredecor.so: undefined symbol: _ZTIN2wf3txn20transaction_object_tE

Looking at libfiredecor.so with nm indeed reveals, that _ZTIN2wf3txn20transaction_object_tE is undefined:

0000000000045b70 V _ZTIN2wf28view_geometry_changed_signalE
00000000000457a8 V _ZTIN2wf36view_decoration_state_updated_signalE
                 U _ZTIN2wf3txn20transaction_object_tE
00000000000457b8 V _ZTIN2wf3txn22new_transaction_signalE
0000000000045988 V _ZTIN2wf5scene17render_instance_tE

But since it's a plugin, that's okay because it's supposed to obtain the symbol from the executable loading it. Now my question: how do I modify my test.cc or my g++ invocation such that the test binary includes the typeinfo for wf::txn::transaction_object_t?

Using dummy values should be enough because the getWayfireVersion in my plugin is defined like this:

uint32_t getWayfireVersion() { return WAYFIRE_API_ABI_VERSION; }

Changing RTLD_NOW to RTLD_LAZY in dlopen only has the effect that the error message does not show up when doing dlopen but only later when calling the function.

I contacted the wayfire developers for their input but they are none the wiser: https://github.com/WayfireWM/wayfire/issues/2499

An alternative solution to this problem would be to hard-code the wayfire api/abi version when building the plugin and store the results in a file and query that file later and compare it with the current value of WAYFIRE_API_ABI_VERSION. But since that solution is not quite optimal and since this has turned into a c++ question, I'm asking for help here. Maybe somebody knows how to make my test.cc contain the (dummy) symbols expected by my plugin, thank you!

EDIT 1:

You can obtain your own version of libfiredecor.so by downloading and unpacking the Debian package like this:

$ curl http://ftp.debian.org/debian/pool/main/r/reform-firedecor/reform-firedecor_2023-10-23-5_amd64.deb | dpkg-deb --fsys-tarfile - | tar --to-stdout -x ./usr/lib/x86_64-linux-gnu/wayfire/libfiredecor.so > libfiredecor.so

EDIT 2:

The test.cc program works successfully when compiling the plugin with gold's --weak-unresolved-symbols option. Is this the correct solution?


Solution

  • how do I modify my test.cc or my g++ invocation such that the test binary includes the typeinfo for wf::txn::transaction_object_t

    You would add a definition of such class to your test.cc. Something like:

    namespace wf::txn {
    class transaction_object_t {
      virtual ~transaction_object_t() = default;
    };
    
    // Create one instance to make sure the vtable is not optimized out.
    static transaction_object_t *ptr = new transaction_object_t();
    }
    

    In addition, you must link your test with -rdynamic flag.

    You would have to do that for all undefined symbols in the plugin. The set may change between versions, so this doesn't sound like a viable long-term approach.

    OTOH, if dlopen fails due to unresolved symbol, than that's an indication that you have to rebuild the plugin anyway, so maybe that's good enough.