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=>