Linker does not link libc.a

I am migrating a project from STM32CubeIDE to CMake. It is a cross-compiled ARM project using the standard libraries (not nanolib). With CMake, it fails to link libc.a and links libg.a instead, with an error in CMakeError.log:

Compiling the C compiler identification source file "CMakeCCompilerId.c" failed.
Compiler: /usr/bin/arm-none-eabi-gcc
Build flags:
Id flags: 

The output was:
1
/usr/lib/gcc/arm-none-eabi/10.3.1/../../../arm-none-eabi/bin/ld: /usr/lib/gcc/arm-none-eabi/10.3.1/../../../arm-none-eabi/lib/libc.a(lib_a-exit.o): in function `exit':
/build/newlib-pB30de/newlib-3.3.0/build/arm-none-eabi/newlib/libc/stdlib/../../../../../newlib/libc/stdlib/exit.c:64: undefined reference to `_exit'
collect2: error: ld returned 1 exit status

I checked my options, I do have:
set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY")

My linker flags include:
--specs=nosys.specs -flto

Now, following a forum suggestion, if I add
-ffreestanding -nostdlib
it now tries to link libc.a, but I get a linker error:

/usr/lib/gcc/arm-none-eabi/10.3.1/../../../arm-none-eabi/bin/ld: /usr/lib/gcc/arm-none-eabi/10.3.1/../../../arm-none-eabi/lib/thumb/v7e-m+fp/hard/libc.a(lib_a-init.o): in function `__libc_init_array':
/build/newlib-pB30de/newlib-3.3.0/build/arm-none-eabi/thumb/v7e-m+fp/hard/newlib/libc/misc/../../../../../../../../newlib/libc/misc/init.c:40: undefined reference to `_init'

I fixed the issue by adding a weak and empty definition for _init (and _fini, while at it), but it still puzzles me that the STM IDE manages to link libc.a without any workaround, and I cannot, even if I am using the exact same toolchain.
Has anyone a guess on what could be happening?

Some similar topics I read:

Is it possible to get the command line that STM IDE is using?

For linking, it’s using:
arm-none-eabi-gcc -o "lunii_firmware.elf" @"objects.list" -mcpu=cortex-m7 -T"STM32F730V8TX_FLASH.ld" --specs=nosys.specs -Wl,-Map="firmware.map" -Wl,--gc-sections -static -Wl,--print-memory-usage -flto -mfpu=fpv5-sp-d16 -mfloat-abi=hard -mthumb -Wl,--start-group -lc -lm -Wl,--end-group

Here is what the STMCube-generated makefiles invoke:

Startup/%.o: ../Startup/%.s Startup/subdir.mk
    arm-none-eabi-gcc -mcpu=cortex-m7 -g3 -c -x assembler-with-cpp -MMD -MP -MF"$(@:%.o=%.d)" -MT"$@"  -mfpu=fpv5-sp-d16 -mfloat-abi=hard -mthumb -o "$@" "$<"

Src/%.o Src/%.su: ../Src/%.c Src/subdir.mk
        arm-none-eabi-gcc -fstack-usage "$<" -mcpu=cortex-m7 -std=gnu11 -g3 -DUSE_HAL_DRIVER -D[CUSTOM_FLAGS] -DSTM32F730xx -DDEBUG -c -I../Middlewares/Third_Party/FreeRTOS/Source/include -I../Drivers/CMSIS/Include -I../Drivers/CMSIS/Device/ST/STM32F7xx/Include -I../Drivers/STM32F7xx_HAL_Driver/Inc -I../Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM7/r0p1 -I../Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Inc -I../Middlewares/ST/STM32_USB_Device_Library/Core/Inc -I../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS -I../Drivers/STM32F7xx_HAL_Driver/Inc/Legacy -I../Middlewares/Third_Party/FatFs/src -I../Middlewares/Third_Party/Helix -I../Middlewares/Third_Party/Helix/pub -I../Middlewares/Third_Party/Helix/real -I../Middlewares/Third_Party/FreeRTOS/coreJSON/include -I../Middlewares/Third_Party/microtar/src -I[my_source_paths] -Og -ffunction-sections -fdata-sections -mslow-flash-data -Wall -fstack-usage -MMD -MP -MF"$(@:%.o=%.d)" -MT"$@"  -mfpu=fpv5-sp-d16 -mfloat-abi=hard -mthumb -o "$@"

firmware.elf firmware.map: $(OBJS) $(USER_OBJS) STM32F730V8TX_FLASH.ld makefile objects.list $(OPTIONAL_TOOL_DEPS)
        arm-none-eabi-gcc -o "firmware.elf" @"objects.list" $(USER_OBJS) $(LIBS) -mcpu=cortex-m7 -T"STM32F730V8TX_FLASH.ld" --specs=nosys.specs -Wl,-Map="firmware.map" -Wl,--gc-sections -static -Wl,--print-memory-usage -flto -mfpu=fpv5-sp-d16 -mfloat-abi=hard -mthumb -Wl,--start-group -lc -lm -Wl,--end-group

I assume it provides an object with those symbols in the objects.list file

Hmm. I see that CMake’s command line is missing (it’s just error messages). Can you get that from the internal project that it uses to detect this stuff?

I don’t get what info I should be looking for, should I check the options it used to compile “CMakeCCompilerId.c”? Or the generated makefile linker calls? I am not very fluent in CMake :slight_smile:

The generated linker call for sure, but the command line could be helpful too while you’re in there (to make sure any other intended flags are getting through).

CMakeOutput.log tells me:

COLLECT_GCC=/opt/st/stm32cubeide_1.11.2/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.10.3-2021.10.linux64_1.0.100.202210260954/tools/bin/arm-none-eabi-gcc
COLLECT_GCC_OPTIONS='-v' '-o' 'CMakeFiles/cmTC_44ac2.dir/CMakeCCompilerABI.c.obj' '-c' '-mcpu=arm7tdmi' '-mfloat-abi=soft' '-marm' '-mlibarch=armv4t' '-march=armv4t'

If I try to retrieve the compilation calls from CMakeOutput.log:

/opt/st/stm32cubeide_1.11.2/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.10.3-2021.10.linux64_1.0.100.202210260954/tools/bin/arm-none-eabi-gcc   -v -o CMakeFiles/cmTC_44ac2.dir/CMakeCCompilerABI.c.obj -c /usr/share/cmake-3.22/Modules/CMakeCCompilerABI.c
/opt/st/stm32cubeide_1.11.2/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.10.3-2021.10.linux64_1.0.100.202210260954/tools/bin/../lib/gcc/arm-none-eabi/10.3.1/cc1 -quiet -v -iprefix /opt/st/stm32cubeide_1.11.2/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.10.3-2021.10.linux64_1.0.100.202210260954/tools/bin/../lib/gcc/arm-none-eabi/10.3.1/ -isysroot /opt/st/stm32cubeide_1.11.2/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.10.3-2021.10.linux64_1.0.100.202210260954/tools/bin/../arm-none-eabi -D__USES_INITFINI__ /usr/share/cmake-3.22/Modules/CMakeCCompilerABI.c -quiet -dumpbase CMakeCCompilerABI.c -mcpu=arm7tdmi -mfloat-abi=soft -marm -mlibarch=armv4t -march=armv4t -auxbase-strip CMakeFiles/cmTC_44ac2.dir/CMakeCCompilerABI.c.obj -version -o /tmp/cco6dRVU.s
/opt/st/stm32cubeide_1.11.2/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.10.3-2021.10.linux64_1.0.100.202210260954/tools/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/bin/as -v -march=armv4t -mfloat-abi=soft -meabi=5 -o CMakeFiles/cmTC_44ac2.dir/CMakeCCompilerABI.c.obj /tmp/cco6dRVU.s
/opt/st/stm32cubeide_1.11.2/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.10.3-2021.10.linux64_1.0.100.202210260954/tools/bin/arm-none-eabi-ar qc libcmTC_44ac2.a CMakeFiles/cmTC_44ac2.dir/CMakeCCompilerABI.c.obj
/opt/st/stm32cubeide_1.11.2/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.10.3-2021.10.linux64_1.0.100.202210260954/tools/bin/arm-none-eabi-ranlib libcmTC_44ac2.a

The CMake link.txt files says my final linker command is:
/opt/st/stm32cubeide_1.11.2/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.10.3-2021.10.linux64_1.0.100.202210260954/tools/bin/arm-none-eabi-gcc -mthumb -mcpu=cortex-m7 -mfpu=fpv5-sp-d16 -mfloat-abi=hard -Wall -fs tack-usage -ffunction-sections -fdata-sections -mslow-flash-data -std=gnu11 -Og -g3 -g2 -Wl,--gc-sections -static --specs=nosys.specs -mthumb -T./STM32F730V8TX_FLASH.ld -Wl,-Map=firmware_mvp.map -mcpu=cortex-m7 -mfpu=fpv5-sp-d16 -mfloat-abi=hard -Wl,--start-group -lc -lm -Wl,--end-group -Wl,--print-memory-usage -flto OBJECTS.c.obj -o firmware.elf

I checked that the object files linked in the STM32Cube IDE “objects.list” and in the CMake “link.txt” command are exactly the same.

I scraped the contents of the “objects.list” files with arm-none-eabi-nm, but _init and _fini are nowhere to be found.

_exit is defined in a syscalls.c source file, but it is not in any include path, maybe I should add a matching header file?

A header file problem would be detected at compile time. Something needs to provide the implementation of the symbols from syscalls.c in one of the object files or something you link to (probably libc.a, but why that’s not being found…).

I ran out of time to spend on this topic, so I will keep my additional _init and _fini definitions for now:

/* Empty init definition to avoid errors linking libc.a */
__attribute__((weak)) void _init(void){}
__attribute__((weak)) void _fini(void){}