MCUX 10.2.1 - C++ exceptions not being caught

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

MCUX 10.2.1 - C++ exceptions not being caught

Jump to solution
4,217 Views
dmarks_ls
Senior Contributor I

I've recently encountered an issue with my Kinetis K24 C++ project where it appears that unknown conditions cause my project to build in such a way that it's impossible to catch exceptions, and I don't know what's causing this "fragility".

Here's my test code:

  static void VerifyExceptions(void) {
    try {
      throw std::runtime_error("Exceptions are being handled normally.");
    } catch (std::runtime_error &e) {
      std::cout << e.what() << "\n";
    }
    /* If exceptions are not working correctly, then the above statement will
     * cause __cxa_throw() to call terminate() immediately.  Testing this at
     * startup assures that any issues with exceptions will be immediately
     * diagnosed during development. */
  }

I have a call to VerifyException() near the top of my main() function, after I've initialized the console.  Normally it prints out the test message and continues.  I had been developing quite happily the last week or so, then I committed some updates to one of my program modules yesterday and tested out the code.  Suddenly, I'm getting this on my console at startup:

terminate called after throwing an instance of 'std::runtime_error'
terminate called recursively

This is what my call stack looks like:

exception_terminate.png

I had first encountered this issue maybe a week or two ago.  I have a test module where I can press a couple of keys and send a JSON string to a parser to be processed.  The parser is written in C++ and uses exceptions, which my application catches.  Somehow, adding one additional test case to my app caused ALL C++ exceptions to fail in this manner, by removing that one test case from my module, the fault went away.  I had hoped that was the end of it, but apparently not.  I put in this self-check of exception handling as a mineshaft canary for just this reason.

I'm building my app with Newlib, not Newlib-Nano, since Nano was built with exceptions disabled (for significant code size savings).  This is my second project using C++ and JSON parsing on the K24/64; my first was done two years ago on Kinetis Design Studio, and I never had any issues with catching exceptions.  What concerns me is how random this issue appears to be.  With the code and project in a given state, the executable will build the same way each time, even on another developer's machine (using the same version of MCUXpresso).  So it's not tied to my particular workstation; it's tied purely to the code as it exists at that moment.

Can anyone (NXP or otherwise) explain to me how exception handling could be sabotaged globally simply by the particular object modules that are being built?  (If NXP wants to do a ticket-based investigation, I can send our code privately.) Thanks.

(Win 10 Pro 1803, MCUX 10.2.1, C++11 and C11, linking with Newlib, -fexceptions specified in compiler flags)

0 Kudos
1 Solution
3,560 Views
dmarks_ls
Senior Contributor I

All,

I have finally located a solution, which I describe in detail here.  If you want the quick solution, follow these steps:

  • Search for the file "exdata.ldt" in your MCUXpresso installation.  You should find it in these two locations:
    • [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
    • There is a third location that is a direct link to the "wizards" file location and not a separate copy.
  • Archive your existing copy of "exdata.ldt" if you wish.
  • Replace it with the copy attached to this post.

That's it.  In my limited testing, I have found that I no longer need to employ my 50/50 workaround, and everything works normally.

David R.

View solution in original post

0 Kudos
10 Replies
3,561 Views
dmarks_ls
Senior Contributor I

All,

I have finally located a solution, which I describe in detail here.  If you want the quick solution, follow these steps:

  • Search for the file "exdata.ldt" in your MCUXpresso installation.  You should find it in these two locations:
    • [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
    • There is a third location that is a direct link to the "wizards" file location and not a separate copy.
  • Archive your existing copy of "exdata.ldt" if you wish.
  • Replace it with the copy attached to this post.

That's it.  In my limited testing, I have found that I no longer need to employ my 50/50 workaround, and everything works normally.

David R.

0 Kudos
3,560 Views
dmarks_ls
Senior Contributor I

FYI, I have made a parallel post over in the Arm forums:

GCC 7.2.1 on Cortex-M4 - C++ exceptions not being caught - Software Tools forum - Software Tools - A... 

I will update this post if I get additional information over there, and vice versa.

0 Kudos
3,560 Views
dmarks_ls
Senior Contributor I

OK, further testing... as I develop my project, I'm obviously adding and removing code as I go.  Maybe every 3-4 builds or so, I'll download and find that exceptions are no longer working.  To fix this, I simply comment out the DoNothing() line as described above, or if it's already commented out, I re-enable the line.  So far, this wacky workaround has been successful 100% of the time.

This smells like a compiler/linker alignment issue.  I'm grateful that I had the serendipity to find a workaround for this, but it's just that, a workaround.  Does anyone at NXP have any interest in sniffing out this problem?  Because it's definitely a problem.

0 Kudos
3,560 Views
converse
Senior Contributor V

I don't know whether NXP would be interested - they do not write the compiler. The compiler is GCC and NXP just take a build of it from ARM. You may be able to report the problem here:

GNU Arm Embedded Toolchain – Arm Developer 

One other thought though - is a different exception being thrown - one that you are not catching? Have you tried to catch 'any' exception, using "catch (...)".


Unfortunately, (unless you are using C++ 11) you cannot determines what the exception actually is - only that one occurred .

0 Kudos
3,560 Views
dmarks_ls
Senior Contributor I

NXP should take an interest in this.  Their IDE is wholly reliant on GCC as the compiler, so if there's a quality issue with the GNU Arm tools, NXP should be concerned.  It would be lazy for them to say "well, that's a GNU Arm issue, we're not going to do anything about this."  Thanks for the link to report the issue, though, I might do that.

And yes, I have tried the catch-all "catch (...)", and when this problem is manifesting, catch-all will not catch exceptions, either.  (I am using C++11, FWIW.)  That's why I wrote the test function above the way I did; I throw a std::runtime_error, and I catch an exception of that exact type.  If the issue isn't present in that particular build, then that test function works great.  If the issue is manifesting, then that function will fail, and ANY attempt to catch an exception will fail, even with a catch-all statement.  I have that test function called near the start of my program; if it fails, I notice immediately.  Then I just toggle the commenting on that call to DoNothing(), recompile, and voila, it works.  If this were stack abuse, I would expect a much less predictable workaround.

0 Kudos
3,560 Views
converse
Senior Contributor V

Could it be stack (corruption) related? Catching an exceptions involves unwinding the stack to the catching code. If the stack is corrupted, I can imaging this causing a problem.

0 Kudos
3,560 Views
dmarks_ls
Senior Contributor I

It could be, but in this instance, I'm nearly certain it's not.  My stack (as allocated in the linker) is 16KB in size.  I run my VerifyExceptions() routine during system startup, before I've even allocated any of my threads or other resources, let alone launched the FreeRTOS scheduler.  And I've had my full system (with 20+ threads) up and running and handling exceptions just fine.  But somehow, by merely adding code to the project (not even code that gets called), it somehow causes the project to build in such a way that ALL exceptions are uncatchable... I mean, my test routine above couldn't be any simpler; there's virtually no stack to unwind.

In my current application, I've been developing a "Power Manager" module in the last week.  Just for giggles, I #if-zeroed the one line of task/module initialization for the Power Manager in main(), and told MCUX to exclude power_manager.cc from Debug and Release builds.  I then built my application, downloaded it, and ran it, and exceptions work just fine; the test routine prints "Exceptions are being handled normally." at startup, and the application handles JSON parsing exceptions correctly.  I then tell MCUX to re-include power_manager.cc, but I don't re-enable the initialization of that module; all the code in that module is dormant.  I rebuild the project, download, and debug... and exceptions are malfunctioning.

So I now have my project in a state where if I merely compile and link in a single code module, without ever calling its code, that can cause exceptions to become uncatchable.  This smells like a compiler/linker issue to me, and I hate those.

0 Kudos
3,560 Views
converse
Senior Contributor V

Depending on how your class is structured, you class *may* be being called (initialised) even if you do not call it explicitly - for example any contructors or any object initialisations will cause some of your code to be invoked in the startup code (before main() - __libc_init_array). My suspicion is something stack-related, probably caused by something in class initialisation. Try setting some breakpoints in your code (initialisers or constructors etc) to confirm.

0 Kudos
3,560 Views
dmarks_ls
Senior Contributor I

Further testing... I made some additional changes and enhancements to my Power Manager object.  I compile and debug... and exceptions are broken.  So I comment out the DoNothing() call in the constructor, recompiled, and debug.  Exceptions work fine.  It's like some damn toggle switch in my project.  I was wondering if it's some memory alignment thing that's not being handled correctly, but that's pure speculation... I don't know what's causing this.  Everything else is copacetic when the project runs fine... heaps (both system and FreeRTOS) are fine, task stacks are well within margin.  System stack is 16KB, which should be more than enough for anything that's going on.  And this issue can be tested and triggered long before I call the initialization of any of my objects.

Does anyone else (including NXP) have an opinion on this?

0 Kudos
3,560 Views
dmarks_ls
Senior Contributor I

I'm doing next to nothing in my Power Manager object constructor.  I instantiate a task object (constructor sets an int to 0), a mutex object (constructor calls xSemaphoreCreateMutex()), and a queue object (constructor sets an int to 0).  All the other data members are either POD or a std::list or std::vector, which are initialized empty.  The body of the constructor is empty.  The object is implemented as a singleton, meaning that the only instance of my object is declared static inside a GetInstance() static method.  So the Power Manager object should only be constructed on first invocation of the GetInstance() method, which in my test case never happens.

So, just for giggles, I went to test your theory about the constructor possibly being called in the prologue, prior to main().  In order to have something to breakpoint on, I created a uint32_t member named wibble_, then created a method called DoNothing(), which simply sets wibble_ to 123, something that can't be optimized out.  I set a breakpoint on the single line of the body of DoNothing().  I then added a call to DoNothing() in the body of the constructor, rebuilt my project and debugged it... and wouldn't you know, my program launches just fine.  Exceptions work fine at startup, and if I feed the program a bad JSON message, it catches that exception.  Also, the breakpoint inside DoNothing() never tripped, because I did not re-enable the initialization of the Power Manager object in main().  If I do re-enable initialization in main(), the breakpoint in DoNothing() does trip; it's called when GetInstance() is called in main(), prior to invoking Init() in the object.  It is not called in the system prologue.

If I comment out the call to DoNothing() in the body of my constructor, then exceptions return to their broken state.

This makes no goddamn sense.

0 Kudos