At long last, I have arrived at a solution for this issue which I first reported over a year ago. Here is the full write-up.
Summary
Certain C++ projects are susceptible to a condition where exceptions are not caught correctly when thrown. This condition occurs at compile time and when present, any thrown exception will cause __terminate() to be invoked, regardless of any try/catch statement. This condition is ultimately caused by a badly formatted linker script.
Test Setup
I have a custom Kinetis K24 board programmed and running a C++ application which I've been developing over the last couple of years. The C++ application is using Newlib, which was compiled with exception support in place (Newlib-nano as distributed by NXP does not support exceptions). I first observed this issue with MCUX 10.2.1; for my test, I used MCUX 10.3.0. I am confident this issue would manifest if I were using MCUX 11.1.0, as the relevant linker files are identical between MCUX revisions (see below).
Failure and Analysis
When this issue manifests, any thrown exception will cause __terminate() to be invoked, regardless of any try/catch statement that is placed around the exception condition. Here is code to test the abnormal condition:
static void VerifyExceptions(void) {
try {
throw std::runtime_error("Exceptions are being handled normally.");
} catch (std::runtime_error &e) {
std::cout << e.what() << "\n";
}
}
I make a point of calling this method very early at startup so that if there is any issue with exceptions, I find out the moment I first run the software.
What I found when developing this project is that for a given build of code for my Kinetis K24, there is a roughly 50/50 chance that the build will exhibit the issue. Rebuilding the same project with the same source files will always yield the same result; either exceptions work normally, or they don't work at all. Adding, removing, or altering code will "re-roll the dice", usually in an unpredictable manner.
Workaround
When I first encountered this issue, I struggled to find ways to add or change code so as to create a working build. In my testing, I managed to find a way to do it for my specific project. Note that this only works for my specific project; I also found that changing my optimization settings could invalidate this workaround.
protected:
PowerMgr()
: task_(),
mutex_(),
signal_queue_() {
DoNothing();
}
void DoNothing(void) {
wibble_ = 123;
}
If I build my code and find that exceptions are not being caught, I simply change whether line 7 above is commented or uncommented, recompile, and now exceptions are handled correctly. If later I make additional changes to my project and exceptions no longer work again, I change line 7 back to how it was, and the project works once again. In effect, for a given state of the project code, the call to DoNothing() either MUST be commented out, or MUST NOT be commented out. The code WILL work if line 7 is in one state, and WILL NOT work if it is in the other state. The only way to know what state is required is to build the code and test on the target; if it runs, leave it alone, but if it fails, change the commenting, recompile, and guaranteed it will now work.
Root Cause
Quite simply, I lacked the means or expertise to fully investigate this issue (apparently, so did NXP). As I had a workaround, I posted what I was observing to the NXP forums and to the ARM forums in hope that someone might encounter a solution. And thankfully, eventually, someone did.
The issue is with the .ARM.exidx section, and specifically the linker file template that generates the linker file for that section. I won't try to explain it fully here (see the accepted answer to my ARM forum post for a more detailed explanation), but basically, the linker file template for the .ARM.exidx (named "exdata.ldt") is badly formatted, and a small modification to its structure resolves the exceptions issue.
There are two locations for the offending linker script template ("exdata.ldt"). They are:
- [MCUX IDE location]\ide\configuration\org.eclipse.osgi\4\0\.cp\Data\linkscripts
- [MCUX IDE location]\ide\plugins\com.nxp.mcuxpresso.tools.wizards_11.1.0.201909161352\Wizards\linker
This is how the file reads as installed:
/*
* for exception handling/unwind - some Newlib functions (in common
* with C++ and STDC++) use this.
*/
.ARM.extab : ALIGN(${text_align})
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
<#if (PLAIN_LOAD_IMAGE) >
} > ${CODEX} AT> ${CODE}
<#else>
} > ${CODE}
</#if>
__exidx_start = .;
.ARM.exidx : ALIGN(${text_align})
{
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
<#if (PLAIN_LOAD_IMAGE) >
} > ${CODEX} AT> ${CODE}
<#else>
} > ${CODE}
</#if>
__exidx_end = .;
Solution
The solution is simply to move the __exidx_start and __exidx_end declarations inside of the .ARM.exidx declaration, as follows:
.ARM.exidx : ALIGN(${text_align})
{
__exidx_start = .;
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
__exidx_end = .;
<#if (PLAIN_LOAD_IMAGE) >
} > ${CODEX} AT> ${CODE}
<#else>
} > ${CODE}
</#if>
The first half of the file remains unchanged. I have attached both the original (broken) and the fixed versions of this linker script. If you are experiencing this issue and you copy the fixed script to the "org.eclipse.osgi" location, you should see your issue resolve. You can verify whether this fix is applied to your code by examining the linker file generated for your project build ("projectname_Debug.ld" in the Debug directory). Here is how it looked before (search for "exidx" in your linker script):
.ARM.extab : ALIGN(8)
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} > PROGRAM_FLASH
__exidx_start = .;
.ARM.exidx : ALIGN(8)
{
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} > PROGRAM_FLASH
__exidx_end = .;
And here's how it looked after the fix:
.ARM.extab : ALIGN(8)
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} > PROGRAM_FLASH
.ARM.exidx : ALIGN(8)
{
__exidx_start = .;
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
__exidx_end = .;
} > PROGRAM_FLASH
Requested Action
NXP, I would ask that you please review this report, review my ARM forums post and its replies, examine the modified linker script, verify that the modified linker script generates correct and functional code for each of your test cases, and then incorporate this corrected script in future MCUXpresso releases. Please let me know if you have any additional questions, thank you.
David R.