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