Switching to GCC C runtime linkage for the baremetal driver

Hello! I would like to cross compile an ARM bare metal target, but with the GCC C runtime. I would like the driver to generate a command like this:

lld -o output_file crti.o crtbegin.o … -lgcc crtend.o crtn.o

See Initialization functions, from the GCC docs.

I don’t want to use the -nostdlib flag, because then I am forced to recreate this linker invocation by hand, what is tedious: one would have to create a command which, for each executable, inserts all the object files and libraries in place of the three dots ("…") in the command above.

Basically, I would like to force Clang to create the same linker command, as the ARM GNU GCC does. When invoking GCC with:

-mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16

GCC driver will invoke linker command with crtstuff linked properly. How can I reproduce that with Clang?

I use Clang 13.0.0.

The best option I can think of to do this automatically is to use gcc as the linker and libgcc as the runtime. For example:
-fuse-ld=/path/to/arm-none-eabi-gcc --rtlib=libgcc that will invoke gcc as the linker driver which will then invoke GNU ld (may be able to get a modern gcc to invoke LLD with a -Wl,-fuse-ld=lld option) with the C runtime associated with the GCC compiler and avoid a link error when GCC fails to find compiler-rt.

This isn’t ideal but may be better than what you have now.

Hope this helps

Peter

Thanks, @smithp35, for the answer.

Unfortunately, I got two issues here. Let’s firstly present the command I use:

clang++ \
    --target=armv7em-none-eabi -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 \
    -fdata-sections -ffunction-sections \
    -Wall -Wextra \
    --gcc-toolchain=/path/to/armgnutoolchain-src/arm-none-eabi \
    -stdlib=libstdc++ \
    -fexceptions \
    -O3 -DNDEBUG \
    --sysroot=/path/to/armgnutoolchain-src/arm-none-eabi \
    -specs=nosys.specs -Wl,--gc-sections -lc -lm -lnosys \
    -T/path/to/linker/script.ld \
    CMakeFiles/firmware.dir/main.cpp.obj CMakeFiles/firmware.dir/custom_terminate.cpp.obj \
    -o firmware \
    libdevice.a cube/libcube.a libfreertos.a \
    --verbose \
    --ld-path=/path/to/armgnutoolchain-src/bin/arm-none-eabi-g++ \
    --rtlib=libgcc

First issue is that -Wl is not properly handled, so I get:

arm-none-eabi-g++: error: unrecognized command-line option ‘–gc-sections’; did you mean ‘–data-sections’?

When I remove the -Wl,--gc-sections flag, the compiler proceeds. The --verbose flag prints out the linker invocation:

"/path/to/armgnutoolchain-src/bin/arm-none-eabi-g++" \
    -lc -lm -lnosys \
    CMakeFiles/firmware.dir/main.cpp.obj CMakeFiles/firmware.dir/custom_terminate.cpp.obj \
    libdevice.a cube/libcube.a libfreertos.a \
    -Bstatic \
    -T /path/to/linker/script.ld \
    -L/path/to/armgnutoolchain-src/arm-none-eabi/lib \
    -L/path/to/llvm-src/lib/clang/13.0.0/lib/baremetal \
    -lstdc++ -lsupc++ -lunwind -lc -lm -lgcc -o firmware

Clang appends -lsupc++, -lunwind on its own, even though the libgcc contains unwind symbols. I guess this will be “multiple definitions” error? Yet, it doesn’t matter, because I get obviously an error:

/path/to/armgnutoolchain-src/bin/…/lib/gcc/arm-none-eabi/10.3.1/…/…/…/…/arm-none-eabi/bin/ld: cannot find -lunwind
collect2: error: ld returned 1 exit status

Some thoughts, sorry don’t have a lot of experience with doing this in practice.
The -nodefaultlibs option should suppress all the -Lunwind options. This will make the --rtlib=libgcc redundant.

The problem with -Wl, is that we need to go through multiple layers as clang thinks GCC is a linker, but GCC needs to pass that on to whatever it is using as a linker.

In some cases you may be able to get -Xlinker to work instead. For example:
-Xlinker "-Xlinker" -Xlinker -gc-sections

Ok, so -nodefaultlibs pushes things a bit further; I have fixed the linker invocation:

clang++ \
    --target=armv7em-none-eabi -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 \
    -fdata-sections -ffunction-sections \
    -Wall -Wextra \
    -fexceptions \
    -O3 -DNDEBUG \
    --sysroot=/path/to/armgnutoolchain-src/arm-none-eabi \
    -Xlinker -specs=nosys.specs -Xlinker -Wl,--gc-sections -lc -lm -lnosys \
    -T/path/to/linker/script.ld \
    CMakeFiles/firmware.dir/main.cpp.obj CMakeFiles/firmware.dir/custom_terminate.cpp.obj \
    -o firmware \
    libdevice.a cube/libcube.a libfreertos.a \
    --verbose \
    --ld-path=/path/to/armgnutoolchain-src/bin/arm-none-eabi-g++ \
    -nodefaultlibs

Effectively, I have removed:

  • --rtlib flag
  • irrelevant -stdlib= and --gcc-tolchain flags, which were not used by clang++ (got “unused” warning)

Added:

  • -Xlinker -specs=nosys.specs
  • -Xlinker -Wl,--gc-sections
  • -nodefaultlibs

Again, the --verbose flag prints out the linker invocation:

"/path/to/armgnutoolchain-src/bin/arm-none-eabi-g++" \
    -specs=nosys.specs -lc -lm -lnosys \
    CMakeFiles/firmware.dir/main.cpp.obj CMakeFiles/firmware.dir/custom_terminate.cpp.obj \
    libdevice.a cube/libcube.a libfreertos.a \
    -Wl,--gc-sections -Bstatic \
    -T/path/to/linker/script.ld \
    -L/path/to/armgnutoolchain-src/arm-none-eabi/lib \
    -L/path/to/llvm-src/lib/clang/13.0.0/lib/baremetal -o firmware

I get a bunch of errors:

…/arm-none-eabi/bin/ld: error: CMakeFiles/firmware.dir/main.cpp.obj uses VFP register arguments, firmware does not
…/arm-none-eabi/bin/ld: warning: CMakeFiles/firmware.dir/main.cpp.obj uses 32-bit enums yet the output is to use variable-size enums; use of enum values across objects may fail
…/arm-none-eabi/bin/ld: failed to merge target specific data of file CMakeFiles/firmware.dir/main.cpp.obj
…/arm-none-eabi/bin/ld: error: CMakeFiles/firmware.dir/custom_terminate.cpp.obj uses VFP register arguments, firmware does not
…/arm-none-eabi/bin/ld: warning: CMakeFiles/firmware.dir/custom_terminate.cpp.obj uses 32-bit enums yet the output is to use variable-size enums; use of enum values across objects may fail
…/arm-none-eabi/bin/ld: failed to merge target specific data of file CMakeFiles/firmware.dir/custom_terminate.cpp.obj
…/arm-none-eabi/bin/ld: error: cube/libcube.a(main.c.obj) uses VFP register arguments, firmware does not
…/arm-none-eabi/bin/ld: warning: cube/libcube.a(main.c.obj) uses 32-bit enums yet the output is to use variable-size enums; use of enum values across objects may fail
…/arm-none-eabi/bin/ld: failed to merge target specific data of file cube/libcube.a(main.c.obj)
…/arm-none-eabi/bin/ld: error: cube/libcube.a(gpio.c.obj) uses VFP register arguments, firmware does not
…/arm-none-eabi/bin/ld: warning: cube/libcube.a(gpio.c.obj) uses 32-bit enums yet the output is to use variable-size enums; use of enum values across objects may fail
…/arm-none-eabi/bin/ld: failed to merge target specific data of file cube/libcube.a(gpio.c.obj)
…/arm-none-eabi/bin/ld: error: cube/libcube.a(freertos.c.obj) uses VFP register arguments, firmware does not
…/arm-none-eabi/bin/ld: warning: cube/libcube.a(freertos.c.obj) uses 32-bit enums yet the output is to use variable-size enums; use of enum values across objects may fail
…/arm-none-eabi/bin/ld: failed to merge target specific data of file cube/libcube.a(freertos.c.obj)
…/arm-none-eabi/bin/ld: error: cube/libcube.a(system_stm32l4xx.c.obj) uses VFP register arguments, firmware does not
…/arm-none-eabi/bin/ld: warning: cube/libcube.a(system_stm32l4xx.c.obj) uses 32-bit enums yet the output is to use variable-size enums; use of enum values across objects may fail
i

This sounds like some architecture incompatibility.

The 32-bit enums can be fixed by compiling with -fshort-enums. GCC will select the appropriate libraries based on command line (multilib) you may need to pass through the -mcpu options, potentially the -mfloat-abi as well to GCC.

Ok, it seems that the linking order, I asked in the first question, is not really necessary.
See the blog post: .init, .ctors, and .init_array | MaskRay, and the comments.