Reputation: 606
I have been trying lately to compile firmware with Clang and CMake, using Toolchain files, for C++. I can get it normally working without exceptions. A problem arises when I use exceptions.
LLVM version: 13.0.0
CMake version: 3.21.3
CPU: STM32L432KC, ARM Cortex M4
To successfully compile the firmware I use precompiled libc
, libm
, libgcc
and libstdc++
bundled with ARM GNU GCC Toolchain, version 10.3.2021-10.
I won't put the entire toolchain file here. Trust me that the paths to the CMAKE_C_COMPILER
, CMAKE_CXX_COMPILER
, CMAKE_ASM_COMPILER
and CMAKE_LINKER
are good.
CMAKE_CXX_FLAGS_INIT
, which define initial compile flags for C language, are defined like so:
-mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16
-nodefaultlibs
--sysroot=${ARM_GNU_TOOLCHAIN_PATH}/arm-none-eabi
-flto
-fdata-sections -ffunction-sections
# For <iostream>, <string>, ...
-isystem "${ARM_GNU_TOOLCHAIN_PATH}/arm-none-eabi/include/c++/${ARM_GNU_TOOLCHAIN_GCC_VERSION}/"
# For <bits/*>, ...
-isystem "${ARM_GNU_TOOLCHAIN_PATH}/arm-none-eabi/include/c++/${ARM_GNU_TOOLCHAIN_GCC_VERSION}/arm-none-eabi/thumb/v7e-m+fp/hard/"
-fexceptions
ARM_GNU_TOOLCHAIN_PATH
is the root path to the mentioned ARM GNU GCC Toolchain. ARM_GNU_TOOLCHAIN_GCC_VERSION
is equal to 10.3.1
.
The linker flags, defined with CMAKE_EXE_LINKER_FLAGS_INIT
:
-mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16
-nodefaultlibs
--sysroot=${ARM_GNU_TOOLCHAIN_PATH}/arm-none-eabi
-flto
-fdata-sections -ffunction-sections
-Wl,--gc-sections
-flto
-fexceptions
# Path to standard libraries: libc, libm, ...
-L"${ARM_GNU_TOOLCHAIN_PATH}/arm-none-eabi/lib/thumb/v7e-m+fp/hard/"
# Path to libgcc
-L"${ARM_GNU_TOOLCHAIN_PATH}/lib/gcc/arm-none-eabi/${ARM_GNU_TOOLCHAIN_GCC_VERSION}/thumb/v7e-m+fp/hard/"
-lc -lm -lnosys -lstdc++ -lgcc")
If in the binary there is no, try ... catch
block. Everything compiles just fine, but if there is at least one block:
try
{
throw std::runtime_error{"Some error!"};
} catch (const std::exception&e)
{
printf("Error: %s\r\n", e.what());
}
The linker inputs the .got
section, before .data
section without being instructed within the linker script. The RAM start address is 0x20000000. objdump
output:
...
Contents of section .got:
20000000 848f0108 ....
Contents of section .data:
20000004 00000000 00000000 08000020 08000020 ........... ...
20000014 10000020 10000020 18000020 18000020 ... ... ... ...
20000024 20000020 20000020 28000020 28000020 .. .. (.. (..
20000034 30000020 30000020 38000020 38000020 0.. 0.. 8.. 8..
...
My linker script, generated by CubeMX, has LMA .data
section, which shall be the first in RAM.:
.fini_array :
{
. = ALIGN(8);
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN (__fini_array_end = .);
. = ALIGN(8);
} >FLASH
/* used by the startup to initialize data */
_sidata = LOADADDR(.data);
/* Initialized data sections goes into RAM, load LMA copy after code */
.data :
{
. = ALIGN(8);
_sdata = .; /* create a global symbol at data start */
*(.data) /* .data sections */
*(.data*) /* .data* sections */
. = ALIGN(8);
_edata = .; /* define a global symbol at data end */
} >RAM AT> FLASH
As you can see in the comment, _sdata
will be used by the startup code to initialize data
in RAM. The problem is that _sdata
will be set to 0x20000000, not 0x20000008, where the first global variable lies. This means that all the global variables will have wrong values.
As a workaround, I have added a .got
section which uses all the got*
input sections:
...
.fini_array :
{
. = ALIGN(8);
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN (__fini_array_end = .);
. = ALIGN(8);
} >FLASH
.got :
{
. = ALIGN(8);
*(.got)
*(.got*)
. = ALIGN(8);
} >RAM AT> FLASH
/* used by the startup to initialize data */
_sidata = LOADADDR(.data);
/* Initialized data sections goes into RAM, load LMA copy after code */
.data :
{
...
Since .got
section is related to dynamic symbol resolution, I am not really fluent with it. I use only static libraries to compile the firmware, since I write bare-metal programs targeted one binary per project.
The main issue is that the exceptions don't work correctly. No exceptions are caught within the above try ... catch ...
block. The firmware ends within Default_Handler
.
I guess it's related to the .got
section generated by clang. Clang is not able to properly link the compiler builtins from libgcc
to handle the exceptions.
Could you help me in debugging and fixing that?
Upvotes: 0
Views: 715
Reputation: 606
Use -Wl,--target2=rel
flag when compiling.
I have created a post on the llvm-dev mailing list, which can be found here. Peter Smith has helped to resolve the problem and pointed to the solution.
Upvotes: 0