ARM-software / LLVM-embedded-toolchain-for-Arm

A project dedicated to building LLVM toolchain for Arm and AArch64 embedded targets.
Apache License 2.0
419 stars 98 forks source link

C++ / libunwind pulls in stderr reference #525

Closed nolange closed 1 month ago

nolange commented 1 month ago

Using C++ will pull in the unwind library. Apparently this is a debug build, adding assertions and outputting to stderr.

the basic libraries should not have these dependencies, its possibly to get along without using stdio in small projects

ld.lld: error: undefined symbol: stderr
>>> referenced by UnwindLevel1-gcc-ext.c
>>>               UnwindLevel1-gcc-ext.c.obj:(_Unwind_Backtrace) in archive /opt/arm-clang/lib/clang-runtimes/arm-none-eabi/armv5te_exn_rtti/lib/libc++.a
>>> referenced by UnwindLevel1-gcc-ext.c
>>>               UnwindLevel1-gcc-ext.c.obj:(_Unwind_GetIPInfo) in archive /opt/arm-clang/lib/clang-runtimes/arm-none-eabi/armv5te_exn_rtti/lib/libc++.a
>>> referenced by libunwind.cpp
>>>               libunwind.cpp.obj:(__unw_init_local) in archive /opt/arm-clang/lib/clang-runtimes/arm-none-eabi/armv5te_exn_rtti/lib/libc++.a
smithp35 commented 1 month ago

Can you provide a few more details?

One part of libc++ that will do this is the __verbose_abort function defined in the header https://github.com/llvm/llvm-project/blob/main/libcxx/include/__verbose_abort

This can be overridden in your local code. For example:

[[__noreturn__]] void std::__libcpp_verbose_abort(char const *format, ...) { while(1); }

Could be worth trying this to see if this will suppress the inclusion of stdio.

There is a no exceptions no rtti variant of libc++ that makes fewer calls to __libcpp_verbose_abort that is selected when -fno-exceptions and -fno-rtti is used.

nolange commented 1 month ago

the issue is within libunwind, it defines the macro _LIBUNWIND_TRACE_API which calls _LIBUNWIND_LOG. see https://github.com/llvm/llvm-project/blob/d9e986e915b03e2f68d9ed2b664a2511edbb6513/libunwind/src/config.h#L167C9-L167C24

this code in UnwindLevel1-gcc-ext.c is referenced when using an "unwind backtrace" via _Unwind_Backtrace (could be a C crash handler). if you use exceptions it will likely always be used.

This code calls 2 function, each will cause the issue:

#include <unwind.h>

extern "C" {
/* not sure why I need this */
void _exit(int rc)
{}
}

struct CHelper{

};
static _Unwind_Reason_Code unwindCallback(_Unwind_Context *unwind, void *pData) noexcept {
    return _URC_NO_REASON;
}

void extrafunction() {
    CHelper helper;
    _Unwind_Backtrace(&unwindCallback, &helper);

}

void extrafunction2() {
    throw CHelper{};

}

int main() {
    extrafunction();
    extrafunction2();
}

code should be compiled with:

/opt/arm-clang/bin/clang++ -target arm-none-eabi example.cpp  -lcrt0 -T picolibcpp.ld
nolange commented 1 month ago

Compiling with -fno-exceptions -fno-rtti doesnt help either (same code with extrafunction2 removed).

smithp35 commented 1 month ago

Thanks for the example. I think this is coming from _LIBUNWIND_LOG_IF_FALSE(x) as the other log functions are guarded under _LIBUNWIND_IS_BAREMETAL which is set in the libunwind build.

https://github.com/llvm/llvm-project/blob/d9e986e915b03e2f68d9ed2b664a2511edbb6513/libunwind/src/config.h#L177

I would have expected NDEBUG to be set by the CMake Release build or MinSizeRel but it appears not be the case. We'll need to investigate further.

smithp35 commented 1 month ago

Looks like NDEBUG is being forced on due to libunwind defaulting to assertions enabled https://github.com/llvm/llvm-project/blob/main/libunwind/CMakeLists.txt#L36

Anyway looks like we've got the root cause now.

nolange commented 1 month ago

Great. Do you build releases manually? I don't see any github workflows.

IMHO the upstream default should be different if building for baremetal.

smithp35 commented 1 month ago

Yes the releases are built manually.

smithp35 commented 1 month ago

https://github.com/ARM-software/LLVM-embedded-toolchain-for-Arm/pull/534 should resolve this. I'll close the issue for now.