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

Florian Weimer fweimer at redhat.com
Wed Dec 6 10:51:36 UTC 2023


* 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:

00000000002997df <__cxa_guard_acquire.cold>:
  2997df:       bf 08 00 00 00          mov    $0x8,%edi
  2997e4:       e8 77 c0 e9 00          callq  1135860 <__cxa_allocate_exception
>
  2997e9:       48 89 c7                mov    %rax,%rdi
  2997ec:       48 89 c5                mov    %rax,%rbp
  2997ef:       e8 7c b6 e9 00          callq  1134e70 <_ZN9__gnu_cxx20recursive
_init_errorC1Ev>
  2997f4:       48 8d 15 35 b6 e9 00    lea    0xe9b635(%rip),%rdx        # 1134
e30 <_ZN9__gnu_cxx20recursive_init_errorD1Ev>
  2997fb:       48 8d 35 be e5 24 01    lea    0x124e5be(%rip),%rsi        # 14e
7dc0 <_ZTIN9__gnu_cxx20recursive_init_errorE>
  299802:       48 89 ef                mov    %rbp,%rdi
  299805:       e8 46 b4 e9 00          callq  1134c50 <__cxa_throw>

Arguably this is a gap in what GCC provides.  There are three ways to
address it: ship a no-exceptions variant of libstdc++.a, introduce a
non-throwing symbol that replaces __cxa_guard_acquire, or stop throwing
altogether on reentrant initialization (which probably isn't required by
the C++ standard).  The libstdc++.a variant does not address the
dynamically linked version of libstdc++, which conceptually has the same
problem.  But I don't expect libstdc++ to duplicate all potentially
throwing functions to accommodate the non-exception case.  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.

(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.)

Thanks,
Florian



More information about the hotspot-dev mailing list