Statically linking libc++/libc++abi (ELF)

I want to statically link libc++ and libc++abi into my ELF DSO (shared
library). (Statically linking avoids incompatibilities with other
libc++/libstdc++/etc. loaded at runtime by other DSO-s.) (My DSO exports
no C++ APIs, imports no C++ APIs, does not throw or catch exceptions
across DSO boundaries, does not require callers to delete returned
pointers, etc.)

To achieve this, I am using -fvisibility=hidden,
-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS, and
-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS (and some other tweaks) when
compiling libc++, libc++abi, and my own code. I build libc++ and libc++abi
as static libraries and link them into my DSO.

This seems to work well. However, Clang forces some libc++abi symbols to
default visibility. This puts these symbols in my DSO's dynamic export
table. Thus:

* Linking against my DSO may inadvertently use my DSO's symbols instead of
the symbols you expect (e.g. the system libstdc++'s symbols).
* It also means my DSO's references to its libc++abi symbols may be
replaced at runtime with another DSO's symbols (e.g. the system
libstdc++'s symbols).

From what I have found so far, default visibility for libc++abi symbols is
forced in two places in Clang:

* lib/Sema/SemaExprCXX.cpp Sema::DeclareGlobalAllocationFunction
* lib/CodeGen/ItaniumCXXABI.cpp ItaniumRTTIBuilder::BuildTypeInfo

Is there a reason these symbols *must* have default visibility? I
understand that, in general, typeinfo should have default visibility for
exception handling to function properly. Does an opt-in option to change
the visibility of these symbols (for use cases like mine) sound reasonable?

Thanks,

Matthew Glazar

Typically the way people do this is by passing a --version-script to the linker, which lets you include/exclude symbols as you wish regardless of the visibility attributes.

Even more so when building against libstdc++ headers, which forces nearly everything to have default visibility, no matter what.

Is there a reason these symbols must have default visibility?

Yes. These symbols all must have exactly one definition in a C++ program in order to function correctly.

lib/Sema/SemaExprCXX.cpp Sema::DeclareGlobalAllocationFunction

The global new/delete are universally replaceable. If you have a hidden definition it can’t be replaced.

lib/CodeGen/ItaniumCXXABI.cpp ItaniumRTTIBuilder::BuildTypeInfo

This creates typeinfo definitions for the builtin types, such as typeinfo(int). They are automatically emitted by Clang
when it is building libc++abi (https://godbolt.org/g/OtdstM). Typeinfo equality is fundamentally
based on the uniqueness of symbols. If there are two definitions of a type’s typeinfo in a program things like
dynamic_cast and exception catch blocks may stop working.

At least that’s my understanding of why those symbols are forced to have default visibility.

/Eric

The global new/delete are *universally* replaceable. If you have a
hidden definition it can't be replaced.

If the user specifies -fvisibility=hidden, aren't they saying they
explicitly don't want a replaceable version?

Typeinfo equality is fundamentally based on the uniqueness of symbols.
If there are two definitions of a type's typeinfo in a program things
like dynamic_cast and exception catch blocks may stop working.

I suspected that, but I recall seeing a fallback path in case the
uniqueness guarantee isn't met. Maybe I misread.

But if I specify __attribute__((visibility("hidden"))), another DSO
shouldn't reference the type (with dynamic_cast or catch()), so shouldn't
hidden visibility be allowed?