Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 14 Apr 2022 16:36:24 +0000
From:      jbo@insane.engineer
To:        "freebsd-hackers@freebsd.org" <freebsd-hackers@freebsd.org>
Subject:   llvm & RTTI over shared libraries
Message-ID:  <sJFawz_zShI2kxleBBVrBWLp5qW5ampgaEfL6ko6PzYYzCcrCb7ztk9Lsw8cOgrLpzEQ0sh3xIMysGXvnUDOAVEbRNbth6JMYYnfboDry-s=@insane.engineer>

next in thread | raw e-mail | index | archive | help
Hello folks!

I'm in the middle of moving to FreeBSD as my primary development platform (=
desktop wise).
As such, I am currently building various software tools I've written over t=
he years on FreeBSD for the first time. Most of those were developed on eit=
her Linux+GCC or on Windows+Mingw (MinGW -> GCC).

Today I found myself debugging a piece of software which runs fine on FreeB=
SD when compiled with gcc11 but not so much when compiling with clang14.
I managed to track down the problem but I lack the deeper understanding to =
resolve this properly - so here we are.

The software in question is written in C++20 and consisting of:
  - An interface library (just a bunch of header files).
  - A main executable.
  - A bunch of plugins which the executable loads via dlopen().

The interface headers provide several types. Lets call them A, B, C and D. =
where B, C and D inherit from A.
The plugins use std::dynamic_pointer_cast() to cast an std::shared_ptr<A> (=
received via the plugin interface) to the derived classes such as std::shar=
ed_ptr<B>.
This is where the trouble begins.

If everything (the main executable and the plugins) are compiled using gcc1=
1, everything works "as I expect it".
However, when compiling everything with clang14, the main executable is abl=
e to load the plugins successfully but those std::dynamic_pointer_cast() ca=
lls within the plugins always return nullptr.

After some research I seem to understand that the way that RTTI is handled =
over shared library boundaries is different between GCC and LLVM.
This is where my understanding starts to get less solid.

I read the manual page of dlopen(3). It would seem like the flag RTLD_GLOBA=
L would be potentially interesting to me: "Symbols from this shared object =
[...] of needed objects will be available for re-solving undefined referenc=
es from all other shared objects."
The software (which "works as intended" when compiled with GCC) was so far =
only calling dlopen(..., RTLD_LAZY).
I'm not even sure whether this applies to my situation. My gut feeling tell=
s me that I'm heading down the wrong direction here. After all, the main ex=
ecutable is able to load the plugins and to call the plugin's function whic=
h receives an std::shared_ptr<A> as parameter just fine, also when compiled=
 with LLVM.
Is the problem I'm experiencing related to the way that the plugin (shared =
library) is loaded or the way that the symbols are being exported?
In the current state, the plugins do not explicitly export any symbols.

Here's a heavily simplified version of my scenario:


=3D=3D=3D interface.hpp =3D=3D=3D

struct A {};
struct B : A {};
struct C : A {};
struct D : A {};

struct plugin
{
    virtual void do_stuff(std::shared_ptr<A> in);
};


=3D=3D=3D plugin1 =3D=3D=3D

struct plugin1 :
    plugin
{
    void do_stuff(std::shared_ptr<A> a) override
    {
        auto b =3D std::dynamic_pointer_cast<B>(a);
        if (!b)
            return;

        // GCC  ->   success
        // LLVM ->   b always nullptr
    }
};


Could you guys help me out here?


Best regards,
~ Joel



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?sJFawz_zShI2kxleBBVrBWLp5qW5ampgaEfL6ko6PzYYzCcrCb7ztk9Lsw8cOgrLpzEQ0sh3xIMysGXvnUDOAVEbRNbth6JMYYnfboDry-s=>