1

For learning purpose, I am trying to get full C++ support on an ARM MCU (STM32F407ZE). I am struggling at getting exceptions working, consequently carrying this question:

How to get C++ exceptions on a bare-metal ARM bootloader?

To extend a bit the question:
I understand that an exception, like exiting a function require unwinding the stack. The fact that exiting a function works out of the box, but the exception handling does not, make me to think that the compiler is adding the unwinding of functions-exit but can not do it for exceptions.

So the sub-question 1 is: Is this premise correct? Do I really need to implement/integrate an unwinding library for exception handling?

In my superficial understanding of unwinding, there is a frame in the stack and the unwinding "just" need to call the destructor on each object of it, and finally jump to the given catch.

Sub-question 2 is: How does the unwinding library perform this task? What is the strategy used? (to the extends appropriate for a SO answer)

In my searches, I found many explanations of WHAT is the unwinding, but very few of how to get it working. The closest is:

GCC arm-none-eabi (Codesourcery) and C++ Exceptions


The project

1) The first step and yet with some difficulties, was to get the MCU powered and communicating through JTAG.

This is just contextual information, please do not tag the question off-topic just because of this picture. Jump to step 2 instead.

I know there are testing boards available, but this is a learning project to get a better understanding on all the "magic" behind the scene. So I got a chip socket, a bread-board and setup the minimal power-up circuitry:

enter image description here

Note: JTAG is performed through the GPIO of a raspberry-pi.
Note2: I am using OpenOCD to communicate with the chip.

2) Second step, was to make a minimal software to blink the yellow led.
Using arm-none-eabi-g++ as a compiler and linker, the c++ code was straightforward, but my understanding of the linker script is still somewhat blurry.

3) Enable exceptions handling (not yet working).

For this goal, following informations where useful:

However, it seems quite too much complexity for a simple exception handling, and before to start implementing/integrating an unwinding library, I would like to be sure I am going in the correct direction.
I would like to avoid earing in 2 weeks: "Ohh, by the way, you just need to add this "-xx" option to the compiler and it works"

main.cpp

auto reset_handler() noexcept ->void;
auto main() -> int;

int global_variable_test=50;

extern "C"
{

        #include "stm32f4xx.h"
        #include "stm32f4xx_rcc.h"
        #include "stm32f4xx_gpio.h"



        void assert_failed(uint8_t* file, uint32_t line){}
        void hardFaultHandler( unsigned int * hardFaultArgs);

        // vector table
        #define SRAM_SIZE 128*1024
        #define SRAM_END        (SRAM_BASE + SRAM_SIZE)
        unsigned long *vector_table[] __attribute__((section(".vector_table"))) =
        {
                (unsigned long *)SRAM_END,   // initial stack pointer 
                (unsigned long *)reset_handler,        // main as Reset_Handler
        };
}


auto reset_handler() noexcept -> void
{
        // Setup execution

        // Call the main function
        int ret = main();

        // never finish
        while(true);
}


class A
{
public:
        int b;

        auto cppFunc()-> void
        {
                throw (int)4;
        }
};

auto main() -> int
{
        // Initializing led GPIO
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);
        GPIO_InitTypeDef GPIO_InitDef;

        GPIO_InitDef.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14;
        GPIO_InitDef.GPIO_OType = GPIO_OType_PP;
        GPIO_InitDef.GPIO_Mode = GPIO_Mode_OUT;
        GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;
        GPIO_InitDef.GPIO_Speed = GPIO_Speed_100MHz;
        GPIO_Init(GPIOG, &GPIO_InitDef);

        // Testing normal blinking
        int loopNum = 500000;
        for (int i=0; i<5; ++i)
        {
                loopNum = 100000;
                GPIO_SetBits(GPIOG, GPIO_Pin_13 | GPIO_Pin_14);
                for (int i = 0; i < loopNum; i++) continue; //active waiting!

                loopNum = 800000;
                GPIO_ResetBits(GPIOG, GPIO_Pin_13 | GPIO_Pin_14);
                for (int i=0; i<loopNum; i++) continue; //active waiting!
        }

        // Try exceptions handling
        try
        {
                A a;
                a.cppFunc();
        }
        catch(...){}


        return 0;
}

Makefile

CPP_C = arm-none-eabi-g++
C_C = arm-none-eabi-g++
LD = arm-none-eabi-g++
COPY = arm-none-eabi-objcopy

LKR_SCRIPT = -Tstm32_minimal.ld

INCLUDE = -I. -I./stm32f4xx/CMSIS/Device/ST/STM32F4xx/Include -I./stm32f4xx/CMSIS/Include -I./stm32f4xx/STM32F4xx_StdPeriph_Driver/inc -I./stm32f4xx/Utilities/STM32_EVAL/STM3240_41_G_EVAL -I./stm32f4xx/Utilities/STM32_EVAL/Common
C_FLAGS  = -c -fexceptions -fno-common -O0 -g -mcpu=cortex-m4 -mthumb -DSTM32F40XX -DUSE_FULL_ASSERT -DUSE_STDPERIPH_DRIVER $(INCLUDE)
CPP_FLAGS  = -std=c++11 -c $(C_FLAGS)
LFLAGS  = -specs=nosys.specs -nostartfiles -nostdlib $(LKR_SCRIPT)
CPFLAGS = -Obinary

all: main.bin

main.o: main.cpp
        $(CPP_C) $(CPP_FLAGS) -o main.o main.cpp

stm32f4xx_gpio.o: stm32f4xx_gpio.c
        $(C_C) $(C_FLAGS) -o stm32f4xx_gpio.o stm32f4xx_gpio.c

stm32f4xx_rcc.o: stm32f4xx_rcc.c
        $(C_C) $(C_FLAGS) -o stm32f4xx_rcc.o stm32f4xx_rcc.c

main.elf: main.o stm32f4xx_gpio.o stm32f4xx_rcc.o
        $(LD) $(LFLAGS) -o main.elf main.o stm32f4xx_gpio.o stm32f4xx_rcc.o

main.bin: main.elf
        $(COPY) $(CPFLAGS) main.elf main.bin

clean:
        rm -rf *.o *.elf *.bin

write:
        ./write_bin.sh main.elf

Linker script: stm32_minimal.ld

/* memory layout for an STM32F407 */
MEMORY
{
    FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 512K
    SRAM (rwx)  : ORIGIN = 0x20000000, LENGTH = 128K
}

/* output sections */
SECTIONS
{
    /* program code into FLASH */
    .text :
    {
        *(.vector_table)    /* Vector table */
        *(.text)            /* Program code */
        *(.data)
        /**(.eh_frame)*/
    } >FLASH

    .ARM.exidx :            /* Required for unwinding the stack? */
    {
        __exidx_start = .;
        * (.ARM.exidx* .gnu.linkonce.armexidx.*)
        __exidx_end   = .;
    } > FLASH

    PROVIDE ( end = . );

}
Adrian Maire
  • 14,354
  • 9
  • 45
  • 85
  • 1
    `there is a frame in the stack and the unwinding "just" need to call the destructor on each object of it, and finally jump to the given catch.` - no, there's (potentially) a hierarchy of active exception frames (try blocks) on stack, arranged as a linked list. The actual exception may well occur further down the hierarchy, outside an explicit frame. The unwinder must inspect each active frame, which may contain a finally block, a catch block, or both. Finally block must be executed, catch blocks must too, if they match the exception. This is just scratching the surface. Use a library. – 500 - Internal Server Error Dec 12 '18 at 21:20
  • 1
    Most high-quality exception-handling implementations rely on tables that indicate which destructors and `catch` blocks apply to any given program counter value, so as to avoid burdening the non-exceptional path with recording them on every execution. – Davis Herring Dec 13 '18 at 05:45
  • Thank you for you comments. Is there any further explanation on this? (Before to jump to those 100+ pages spec.). – Adrian Maire Dec 13 '18 at 06:51

0 Answers0