It’s happening with other symbols. __clang_call_terminate was just an example.
I might have narrowed it down fruther, the invalid Thunks seem to be related to compiling with -fstack-protector
and related code-gen. The simplified outline is below.
__stack_chk_fail
is defined using ASM and performs some implementation specific logic to recover the stack canary. It then calls stack_protector_fail
to perform the debug printout and terminate.
setup_stack_protector
is a constructor function that assigns a value to the __stack_chk_guard
reference val.
set_protector
/get_protector
are functions to control the currently installed stack protector failure function.
StackProtector.S (GAS Syntax):
.text
.global __stack_chk_fail
.extern stack_protector_fail
__stack_chk_fail:
#ifndef NDEBUG
add x8, x8, x9
#endif
mov x0, x8
mov x1, x9
b stack_protector_fail
StackProtector.h/cpp (exposition only):
extern "C" {
std::uintptr_t __stack_chk_guard;
[[noreturn]] void stack_protector_fail(std::uintptr_t lhs, std::uintptr_t rhs) noexcept;
}
[[clang::no_stack_protector, gnu::constructor]] void setup_stack_protector() noexcept;
StackProtector get_protector() noexcept;
StackProtector set_protector(StackProtector protector) noexcept;
The current code relies on [[gnu::constructor]]
to setup the canary value. When I compile the code-base as is:
- The
setup_stack_protector
constructor is removed from the program entirely.
-
stack_protector_fail
is emitted as a null thunk.
If a program manually references any functions within the StackProtector.cpp
compilation unit (e.g. calling set_protector
):
- The
setup_stack_protector
constructor is no longer removed.
- The linker step fails with
ld.lld: error: undefined symbol: stack_protector_fail
.
Finally, marking stack_protector_fail
as [[gnu::used]]
, the link error goes away.
In my mind, the two pieces of buggy behaviour are:
-
[[gnu::constructor]]
doesn’t seem to be respected by LTO if a compilation unit has no external references. Specifying [[gnu::constructor, gnu::used]]
produces the same wrong behaviour.
-
.extern
directives don’t seem to be respected correctly by LTO, at least in this instance.
I’m not 100% sure what the C++ spec and compiler extensions specify in terms of behaviour, but based off the documentation provided I would argue this is incorrect behaviour. To me, [[gnu::constructor]]
would imply [[gnu::used]]
in the sense that the compiler/linker wouldn’t try to optimise it away. Obviously the actual implementation would be subject to optimisations.
I would need to dig in and see why .extern stack_protector_fail
is not behaving as expected.