Use of C++ dynamic global object initialization with thread guards

Kim Barrett kim.barrett at oracle.com
Fri Dec 22 21:32:53 UTC 2023


> On Dec 19, 2023, at 12:23 PM, Kim Barrett <kim.barrett at oracle.com> wrote:
> 
>> 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.

Empirically, a recursive initialization attempt doesn't make any attempt to
throw. Rather, it blocks forever waiting for a futex signal from a thread that
succeeds in the initialization. Which of course will never come.

And that makes sense, now that I've looked at the code.

In __cxa_guard_acquire, with _GLIBCXX_USE_FUTEX, if the guard indicates
initialization hasn't yet been completed, then it goes into a while loop.
This while loop tries to claim initialization.  Failing that, it checks
whether initialization is complete.  Failing that, it does a SYS_futex
syscall, waiting for some other thread to perform the initialization.  There's
nothing there to check for recursion.

throw_recursive_init_exception is only called if single-threaded (either by
configuration or at runtime).

-------------- 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/20231222/9e68626b/signature-0001.asc>


More information about the hotspot-dev mailing list