C++ Exceptions

pionex

Member
I am working on porting a project that uses C++ exceptions to Teensy 4.1. The project is large, but at the moment I am not able to get exceptions to work properly at all even on the simplest of projects.

I am running on Platformio with this configuration:
Code:
[env:teensy41-gcc11]
platform = teensy
platform_packages = 
    toolchain-gccarmnoneeabi@symlink:///home/perception/.platformio/packages/toolchain-gccarmnoneeabi@1.11.3.1.202208/
    ;platformio/toolchain-gccarmnoneeabi@1.90301.200702
    tool-teensy
lib_deps = 
	discord-intech/FreeRTOS-Teensy4@^10.0.5
board = teensy41
framework = arduino
build_unflags = -std=gnu++11 -std=gnu++14 -fno-rtti
build_flags = -std=c++17 
              -Wl,-Map,output.map
              -D_GLIBCXX_HAS_GTHREADS=1 
              -Isrc/mutex_freertos
              -D_GLIBCXX_HAVE_DIRENT_H 
              -fexceptions


This is my simple code:

Code:
#include <iostream>
#include <chrono>
#include <filesystem>
#include <mutex>

#include <Arduino.h>
#include <string>
#include <exception>

int led = 13;

void setup() 
{
  pinMode(led, OUTPUT);
}

void loop() {

  try
  {          
        throw std::runtime_error("exception");       // wait for a second
  }
  catch(...)
  {
    while(true)
    {
        digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
        delay(100);               // wait for a second
        digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
        delay(100);        
    }
  }
}


With the default Teensy beta linker script just published for GCC11, the program won't link as soon as I enable exceptions and gives these errors:
Code:
.pio/build/teensy41-gcc11/libFrameworkArduino.a(startup.c.o):(.ARM.exidx.startup+0x0): relocation truncated to fit: R_ARM_PREL31 against `.startup'
.pio/build/teensy41-gcc11/libFrameworkArduino.a(startup.c.o):(.ARM.exidx.startup+0x8): relocation truncated to fit: R_ARM_PREL31 against `.startup'
.pio/build/teensy41-gcc11/libFrameworkArduino.a(startup.c.o):(.ARM.exidx.startup+0x10): relocation truncated to fit: R_ARM_PREL31 against `.startup'
.pio/build/teensy41-gcc11/libFrameworkArduino.a(startup.c.o):(.ARM.exidx.flashmem+0x0): relocation truncated to fit: R_ARM_PREL31 against `.flashmem'
.pio/build/teensy41-gcc11/libFrameworkArduino.a(startup.c.o):(.ARM.exidx.flashmem+0x8): relocation truncated to fit: R_ARM_PREL31 against `.flashmem'
.pio/build/teensy41-gcc11/libFrameworkArduino.a(startup.c.o):(.ARM.exidx.flashmem+0x10): relocation truncated to fit: R_ARM_PREL31 against `.flashmem'
.pio/build/teensy41-gcc11/libFrameworkArduino.a(startup.c.o):(.ARM.exidx.flashmem+0x18): relocation truncated to fit: R_ARM_PREL31 against `.flashmem'
.pio/build/teensy41-gcc11/libFrameworkArduino.a(startup.c.o):(.ARM.exidx.flashmem+0x20): relocation truncated to fit: R_ARM_PREL31 against `.flashmem'
.pio/build/teensy41-gcc11/libFrameworkArduino.a(startup.c.o):(.ARM.exidx.flashmem+0x28): relocation truncated to fit: R_ARM_PREL31 against `.flashmem'
.pio/build/teensy41-gcc11/libFrameworkArduino.a(tempmon.c.o):(.ARM.exidx.flashmem+0x0): relocation truncated to fit: R_ARM_PREL31 against `.flashmem'
.pio/build/teensy41-gcc11/libFrameworkArduino.a(usb.c.o):(.ARM.exidx.flashmem+0x0): additional relocation overflows omitted from the output


I can fix these errors by modifying the linker script such that the general text AND the .ARM.exidx sections get put into flash:

Code:
	.text.progmem : {
		*(.progmem*)
		*(.text*)
		. = ALIGN(4);
	} > FLASH

	.text.itcm : {
		. = . + 32; /* MPU to trap NULL pointer deref */
		*(.fastrun)
		. = ALIGN(16);
	} > ITCM  AT> FLASH

	.ARM.exidx : {
		__exidx_start = .;
		*(.ARM.exidx* .ARM.extab.text* .gnu.linkonce.armexidx.*)
		__exidx_end = .;
	} AT> FLASH


This will compile, link, and execute but exceptions do not get caught. I don't have a debugger, but considering that the uC seems to reboot, I'm guessing its going to std::terminate(). I have read about issues with previous version of the ARM toolchain, but my understanding is that this was fixed. I have also looked at the map file to ensure that it's not linking the nano versions of newlib.

I don't know if the .ARM.exidx section needs to be in some kind of RAM to work properly, but if so, I don't know how to also put the startup in RAM (or at least ITCM) as well. I believe that the startup has to run from flash, but if so, the .ARM.exidx would have to be in Flash to to prevent the R_ARM_PREL31 error.
 
I believe this is due to a bug in the linker script.

The 1.58 beta linker script has:

Code:
	.ARM.exidx : {
		__exidx_start = .;
		*(.ARM.exidx* .gnu.linkonce.armexidx.*)
		*(.ARM.exidx* .ARM.extab.text* .gnu.linkonce.armexidx.*)
		__exidx_end = .;
	} > ITCM  AT> FLASH

I found another script online that does this instead and it seems to fix the exception handling:

Code:
         .ARM.extab : ALIGN(8) 
         {
             *(.ARM.extab* .gnu.linkonce.armextab.*)
         } > FLASH

	.ARM.exidx : ALIGN(8) {
    __exidx_start = .;
        *(.ARM.exidx* .gnu.linkonce.armexidx.*)
    __exidx_end = .;
	} > FLASH
 
Is this with the Beta 3 version of the 1.58 release with the updated tool chain?

With p#2 edit are the exceptions now properly working as expected?
 
At the moment, I’m not explicitly using the beta, but I was using the latest linker script from the github repository with the 11.3.1 toolchain (as described above). After I moved the extab section above the rest of the exidx, the exceptions started working properly.
 
It might be best to confirm with the 1.58 Beta install - currently #3?

The tool chain is getting updated and if the linker script change is safe and good - and it works with the pending update to the toolchain it might make sense to have it considered.
 
It might be best to confirm with the 1.58 Beta install - currently #3?

The tool chain is getting updated and if the linker script change is safe and good - and it works with the pending update to the toolchain it might make sense to have it considered.

I’ll give it a go tomorrow. In the meantime, here is a related issue that gave me the information to solve the problem. The apparent bug is slightly different here, but I think adding the extab where it currently is breaks whatever data structure the exception handling is using.

https://community.nxp.com/t5/MCUXpr...tions-not-being-caught-w-solution/m-p/1015296
 
I’ll give it a go tomorrow. In the meantime, here is a related issue that gave me the information to solve the problem. The apparent bug is slightly different here, but I think adding the extab where it currently is breaks whatever data structure the exception handling is using.

https://community.nxp.com/t5/MCUXpr...tions-not-being-caught-w-solution/m-p/1015296

Great if you can see it work on the Beta of 1.58 so it might get changed and work going forward with the new toolchain.

Interesting - seems a simple alignment/location issue of a couple of bytes the linker can get right.

Not sure there has been much interest in, or use of, exceptions but, having them as a working option might be beneficial.

One test case in p#1, if others are handy to post they might make for good test and usage examples.
 
Note: I believe there have been some other threads in the past about this.
Like: https://forum.pjrc.com/threads/64573-Exception-handling-on-Teensy

There are times it would be great, but not sure as a default as might add lots of overhead to handle the undwinding.

I agree with this and given the number of modifications that have to be done, I think this is likely to be limited to manual / platformio compilation for specific projects (although we aren't exactly using a resource limited processor with a Teensy 4.1). There are a lot of c++ libraries that use exceptions however, and I assume this could be a major use case for enabling them. I have looked at the ARM documentation https://github.com/ARM-software/abi-aa/releases/download/2022Q1/ehabi32.pdf and I do think there is a bug in the linker script that can be fixed without causing any other side effects.

Namely:
5.4.1 Sections
An object producer must generate:
• One fragment of index table for each code section.
• One exception-handling table entry corresponding to each function that may need to be unwound.
Each fragment of index table (read-only data) must be generated in its own ELF section. It must contain an index entry
for each non-leaf function in the associated code section, in the same order as that of the functions in the code
section. The index table section name must be .ARM.exidx optionally followed by further characters. The section
type must be SHT_ARM_EXIDX (see [AAELF32]). It must have the SHF_LINK_ORDER flag set in the sh_flags field of
its section header and be linked to its associated code section via the sh_link field of its section header.
An object producer may generate exception-handling table entries (read-only data) in one ELF section, or one section
per function. The name of a section containing an exception table fragment must be .ARM.extab optionally followed
by further characters. The section type must be SHT_PROGBITS.

To me, that reads that they should be in different sections and that seems to actually make things work empirically.

To actually get exceptions working requires more than fixing that issue. For one thing, the compiler flags have to be changed from -fno-exceptions to -fexceptions. The teensy linker script runs all of the code from ITCM but puts the .ARM.exidx section in flash. This will not link because of the 31-bit address issue. I currently run all of the code I'm porting from Flash (also because it's too big for ITCM). That causes an issue when I try to write to the flash using LittleFS and all flash code must be executed from RAM. As a result I have put eeprom.c into ITCM as well as it's exidx data (otherwise it won't link). So far that seems to be running fine.

.text.itcm : {
. = . + 32; /* MPU to trap NULL pointer deref */
*(.fastrun)
*libFrameworkArduino.a:eeprom.c.o(.text* .ARM.exidx.text* )
. = ALIGN(16);
} > ITCM AT> FLASH

So for the purposes of the beta, I think the only thing that can likely be done is just to continue to verify that moving .ARM.extab to it's on section is the correct thing to do, and provide some external documentation for anyone that wants to enable exceptions.
 
Back
Top