Use of C++ dynamic global object initialization with thread guards
Kim Barrett
kim.barrett at oracle.com
Tue Dec 19 17:23:38 UTC 2023
> On Dec 6, 2023, at 5:51 AM, Florian Weimer <fweimer at redhat.com> wrote:
>
> * Kim Barrett:
>
>>> The implementation of __cxa_guard_acquire is not entirely trivial
>>> because it detects recursive initialization and throws
>>> __gnu_cxx::recursive_init_error, which means that it pulls in the C++
>>> unwinder (at least with a traditional GNU/Linux build of libstdc++.a).
>>
>> Does it? Seems like it shouldn’t. We build with -fno-exceptions, and
>> the definition of throw_recursive_init_exception is conditionalized on
>> __cpp_exceptions, only throwing when that macro is defined. It calls
>> __builtin_trap() if that macro isn’t defined.
>
> With upstream GCC (and presumably most distributions), there's one
> libstdc++.a with one implementation of __cxa_guard_acquire, and it's
> built with exception support.
>
> It's supposed to be possible to build libstdc++ without exception
> support, but upstream GCC doesn't do this automatically for you if the
> target supports exception handling. In principle, the GCC specs
> mechanism allows you to treat -fno-exceptions as a linker flag and link
> against a custom no-exceptions build of libstdc++.a.
>
> Maybe this is what your toolchain is doing if you don't see the unwinder
> symbols in your builds? It should be easy enough to check if you have a
> build with a symbol table: look for a call in __cxa_throw in the
> disassembly of __cxa_guard_acquire.cold or __cxa_guard_acquire. One of
> our builds looks like this:
I've verified that the same is happening in Oracle builds. We don't build an
exception-disabled libstdc++ as part of our devkit either.
So my next question is, exactly what is the harm, and how serious is it? So
far, I don't know of anyone noticing a problem arising from this.
Obviously, if someone writes an initializer that can lead to recursive entry,
that would lead to an attempt to throw an exception. That's likely to have
pretty bad consequences. OTOH, this doesn't seem like a problem we have in
practice. I'm not sure I've ever seen such a problem arise (not just in
HotSpot); after all, it's UB to do so.
The relevant throwing code seems to be tagged as cold, so maybe it doesn't
even get mapped into memory unless it gets invoked.
And while it's true that we try to minimize reliance on the C++ runtime
library, it's not a strict rule. It might not even be feasible to completely
avoid.
And we do periodically have discussions about permitting the use of additional
C++ Standard Library features. Dependency on the runtime library is one of the
things that comes up in those discussions.
> With
> increasing use of libstdc++ facilities in Hotspot, the libstdc++.a
> variant may be the only feasible long-term approach that is both
> maintainable on the GCC side and truly avoids an unwinder dependency.
So long as we stay away from APIs that can throw (or uses that can throw), I'm
not sure there's a problem? For example, if we were to permit the use of
std::vector, we'd likely forbid the use of std::vector::at.
We already avoid throwing std::bad_alloc from operator new implementations,
instead either terminating or returning nullptr.
> (I don't want to turn this into a Restaurant Sketch scenario—there is
> non-trivial libstdc++ usage beyond __cxa_guard_acquire in Hotspot. I
> just wanted to start with a fairly simple example.)
It would be interesting to know what else is there.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: Message signed with OpenPGP
URL: <https://mail.openjdk.org/pipermail/hotspot-dev/attachments/20231219/5a22a3d9/signature.asc>
More information about the hotspot-dev
mailing list