Teensy 4.0 linker issues with STL libraries

Status
Not open for further replies.

exeless

New member
Hello,
I received my teensy 4.0 and I am excited to test it. I am quite new to the Teensy platform and Teensyduino, but I had experience with ESP32 and ESP-IDF.

I have code that makes extensive use of std::string, pair, vector and others and it worked fine on ESP32. However with TeensyDuino even using a vector reserve generates this error during compilation:


Code:
Linking everything together...
/home/exeless/arduino-1.8.9/hardware/teensy/../tools/arm/bin/arm-none-eabi-gcc -O1 -Wl,--gc-sections,--relax -T/home/exeless/arduino-1.8.9/hardware/teensy/avr/cores/teensy4/imxrt1062.ld -mthumb -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 -o /tmp/arduino_build_438136/Arduino_src.ino.elf /tmp/arduino_build_438136/sketch/Arduino_src.ino.cpp.o /tmp/arduino_build_438136/libraries/EchoBay/Reservoir.cpp.o /tmp/arduino_build_438136/../arduino_cache_326848/core/core_teensy_avr_teensy40_usb_serial,opt_o1std,keys_it-it_c98261a91e586b5428d648c0cf6e354f.a -L/tmp/arduino_build_438136 -larm_cortexM7lfsp_math -lm -lc -lstdc++ -lsupc++
/home/exeless/arduino-1.8.9/hardware/tools/arm/bin/../lib/gcc/arm-none-eabi/5.4.1/armv7e-m/fpu/fpv5-d16/libgcc.a(unwind-arm.o): In function `get_eit_entry':
unwind-arm.c:(.text+0x134): undefined reference to `__exidx_end'
unwind-arm.c:(.text+0x138): undefined reference to `__exidx_start'
collect2: error: ld returned 1 exit status

Searching around in the forum it seems to be related to some hindrances that the Arduino platform have against the STL library. Do you have any guidance for me? Using makefiles instead of Arduino IDE won't be an issue for me.

Thank you
 
Just tried std::string and std::vector. The following compiles and runs without problems. Tested on a T4.0

Code:
#include "Arduino.h"
#include <vector>
#include <string>

void setup()
{
   while(!Serial);
   
   std::string s = "Hello STL";
   Serial.println(s.c_str());

   std::vector<int> v;

   v.reserve(100);
   v.push_back(1);
   v.push_back(2);

   for (auto it = v.begin(); it != v.end(); ++it)
   {
      Serial.println(*it);
   }
}

void loop()
{   
}

Care to show a minimal example of what doesn't work?
 
Last edited:
I haven't used the STL, but have been fiddling with linker related questions recently.

In that light, I suspect you could add a the symbols "__exidx_end" and "__exidx_start" to Teensy 4's linker script in file:
"core/core/imxrt1062.ld"

Try looking into "unwind-arm.c" and figuring out what values the symbols should have. Judging by their names, they are memory locations.

You might thoroughly need to learn the GNU Linker script file syntax and purpose as well, if not already familiar with it... And possibly about different IMXRT1062 memory types/locations...
 
I've been putting together a minimal example and it is compiling just fine. In the test example I've included all of the calls that I'm using in my main project:

Code:
#include <vector>
#include <Servo.h>
#include <Wire.h>
#include "VL53L1X.h"
#include "CrossPlatformI2C_Core.h"
#include "TeensyThreads.h"
#include <EEPROM.h>
#include <PID_v1.h>
#include <algorithm>
#include "Adafruit_VL53L0X.h"
#include "EM7180_Master.h"
#include "MegunoLink.h"


std::vector<int> buffer;

void setup()
{
    Serial.begin(115200);

    buffer.insert(buffer.end(), 1);
    buffer.insert(buffer.begin(), 2);
    int value = buffer[0];

    for (std::vector<int>::iterator it = buffer.begin(); it != buffer.end(); ++it)
    {
        Serial.print(*it);
    }
}

void loop()
{
    // put your main code here, to run repeatedly:

}

All of that compiles and links without any errors. I've compiled both projects side-by-side and the linker commands are nearly identical, the only differences being the other classes that make up my project. The linker command is the long line that starts with:
Code:
"C:\\Program Files (x86)\\Arduino\\hardware\\teensy/../tools/arm/bin/arm-none-eabi-gcc"
-O2
-Wl,--gc-sections,--relax
"-TC:\\Program Files (x86)\\Arduino\\hardware\\teensy\\avr\\cores\\teensy4/imxrt1062.ld"
-mthumb
-mcpu=cortex-m7
-mfloat-abi=hard
-mfpu=fpv5-d16
-o
...

I've also tried clearing out the cached files from previous builds, maybe there are other files that I'm missing.

Anyhow I'll keep at it, I'm obviously still missing something!

--
Evan
 
Ok I figured it out, the linker fails if the copy constructor or the assignment operator is used, here is the example:

Code:
#include <vector>
#include <algorithm>

std::vector<int> buffer;

void setup()
{
    Serial.begin(115200);

    buffer.insert(buffer.end(), 1);
    buffer.insert(buffer.begin(), 2);
    int value = buffer[0];

    buffer.size();

    // Works
    std::vector<int> sorted_buffer;
    for (int i=0; i<buffer.size(); i++) {
        sorted_buffer.push_back(buffer[i]);
    }

    // Assignment operator causes linker failure
    // std::vector<int> sorted_buffer;
    // sorted_buffer = buffer;

    // Copy constructor causes linker failure
    // std::vector<int> sorted_buffer(buffer);

    std::sort(sorted_buffer.begin(), sorted_buffer.end());

    for (std::vector<int>::iterator it = buffer.begin(); it != buffer.end(); ++it)
    {
        Serial.print(*it);
    }

}

void loop()
{
    // put your main code here, to run repeatedly:

}

At least I have a work around, not sure if this is the right place to post a bug though.

--
Evan
 
Interesting. Seems to be some known problem. E.g. https://answers.launchpad.net/gcc-arm-embedded/+question/203480. Simply adding definitions for the two symbols makes the linker happy and everything works.

But honestly this is over my head.

Maybe @MichaelMeissner can give an explanation for the problem and if the fix does not generate any unwanted side effects.

Working code:
Code:
#include <algorithm>
#include <vector>

unsigned __exidx_start;
unsigned __exidx_end;

std::vector<int> buffer;

void setup()
{
   while(!Serial);

   buffer.insert(buffer.end(), 1);
   buffer.insert(buffer.begin(), 2);
   //int value = buffer[0];

   buffer.size();

   //Works
   std::vector<int> sorted_buffer;
   for (unsigned i = 0; i < buffer.size(); i++)
   {
      sorted_buffer.push_back(buffer[i]);
   }

   // Assignment operator OK
   std::vector<int> sorted_buffer2;
   sorted_buffer2 = buffer;

   //Copy constructor OK
   std::vector<int> sorted_buffer3(buffer);

   std::sort(sorted_buffer.begin(), sorted_buffer.end());

   Serial.print("buffer: ");
   for (std::vector<int>::iterator it = buffer.begin(); it != buffer.end(); ++it)
   {      
      Serial.printf("%d ",*it);
   }

   Serial.print("\nsorted_buffer: ");
   for (std::vector<int>::iterator it = sorted_buffer.begin(); it != sorted_buffer.end(); ++it)
   {
      Serial.printf("%d ", *it);
   }

   Serial.print("\nsorted_buffer2: ");
   for (std::vector<int>::iterator it = sorted_buffer2.begin(); it != sorted_buffer2.end(); ++it)
   {
      Serial.printf("%d ", *it);
   }
}

void loop()
{
   // put your main code here, to run repeatedly:
}


Output:
Code:
buffer: 2 1 
sorted_buffer: 1 2 
sorted_buffer2: 2 1
 
Yes, but exception handling should be deactivated by -fno-exceptions (which teensyduino sets). Maybe one of the libs which are linked in binary were originally compiled without -fno-exceptions? But as I mentioned, this is something I don't really understand.

I did a few tests with the the code above, seems to work nicely. However, I would thoroughly test it with some larger project using the STL before I'd use the workaround for production code.
 
Interesting. This is very similar to the problem that I was having a couple of years ago with having to define missing symbols for abort, kill, write and getpid. This was resolved by doing what was recommended in this post: https://forum.pjrc.com/threads/2818...-3-1-Help-needed?p=86394&viewfull=1#post86394.

Maybe doing something similar would be better:

Code:
extern "C"{
  int __exidx_start(){ return -1;}
  int __exidx_end(){ return -1; }
}
I put it after the includes in the example and it works as well:
Code:
buffer: 2 1 
sorted_buffer: 1 2 
sorted_buffer2: 2 1
 
I just remembered that I had another issue with the STL some time ago when it complained about a missing std::__throw_bad_alloc();
IIRC, this function is required if you have -fno-exceptions. It is called instead of throwing an exception in case of an error at memory allocation. Gave it a try and voila everything compiles and links flawlessly without the strange __exidx stuff.

Code:
#include <algorithm>
#include <vector>

void std::__throw_bad_alloc()
{
  while(1);  // do whatever you want to do instead of a c++ exception
}


std::vector<int> buffer;

void setup()
{
   while(!Serial);

   buffer.insert(buffer.end(), 1);
   buffer.insert(buffer.begin(), 2);
   //int value = buffer[0];

   buffer.size();

   //Works
   std::vector<int> sorted_buffer;
   for (unsigned i = 0; i < buffer.size(); i++)
   {
      sorted_buffer.push_back(buffer[i]);
   }

   // Assignment operator OK
   std::vector<int> sorted_buffer2;
   sorted_buffer2 = buffer;

   //Copy constructor OK
   std::vector<int> sorted_buffer3(buffer);

   std::sort(sorted_buffer.begin(), sorted_buffer.end());

   Serial.print("buffer: ");
   for (std::vector<int>::iterator it = buffer.begin(); it != buffer.end(); ++it)
   {      
      Serial.printf("%d ",*it);
   }

   Serial.print("\nsorted_buffer: ");
   for (std::vector<int>::iterator it = sorted_buffer.begin(); it != sorted_buffer.end(); ++it)
   {
      Serial.printf("%d ", *it);
   }

   Serial.print("\nsorted_buffer2: ");
   for (std::vector<int>::iterator it = sorted_buffer2.begin(); it != sorted_buffer2.end(); ++it)
   {
      Serial.printf("%d ", *it);
   }
}

void loop()
{
   // put your main code here, to run repeatedly:
}

EDIT:
BTW: The problem seems to come from linking in libstdc++ . If you remove it from the list of linked libs the core compiles like it did before. But now the linker complains about the missing __throw_bad_alloc() which makes much more sense since we don't have exceptions enabled. Can it be that there is something wrong with libstdc++?.
I wonder if we really need it? At least the complete core and the test sketch from above compile and link perfectly without.
 
Last edited:
@luni

You are making me remember as well. Think I had the same problem when I was working on UAVCAN. I actually had to add this as well:
Code:
namespace std {
  void __throw_bad_alloc()
  {
    Serial.println("Unable to allocate memory");
  }

  void __throw_length_error( char const*e )
  {
    Serial.print("Length Error :");
    Serial.println(e);
  }

  void __throw_bad_function_call()
  {
    Serial.println("Bad function call!");
  }
}
for the same reason with -fno_exceptions.
 
__exidx is defined in the linker script and is probably missing. It's used for stack unwinding. It can be turned off with following option -fno-unwind-tables.
A good alternative to STL on embedded systems are https://www.etlcpp.com/


.ARM :
{
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} > m_text
 
Another, std::function-based, example program that shows this error.

Code:
#include <functional>

//namespace std {
//  void __throw_bad_function_call() {
//    // Serial.println("Bad function call!");
//    while (true) {}
//  }
//}

std::function<void()> f;

void func() {
  digitalWriteFast(LED_BUILTIN, HIGH);
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  f = &func;
  f();  // <--Here
}

void loop() {
}

Uncommenting the `__throw_bad_function_call` section makes it link successfully. Is @luni right and something statically-linked is included that wasn't originally built with `-fno-exceptions`? Removing the line that says "Here" will also get it to build properly, but then we couldn't use a `std::function` to call things.

Update

If I change `setup()` to the following, then the build is successful.
Code:
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  f = &func;
  if (f != nullptr) {  // <-- This fixes it
    f();  // <--Here
  }
}
 
Last edited:
I'm using std::function quite often it works without problems so far. You have to define the __throw_bad_function_call() to use it without exceptions enabled. This function will be called instead of throwing an exception when the function pointer is null. You can easily check that by changing

Code:
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  
  //f = &func;
  f = nullptr;   <--------
  f();  
}
This will call your definition of __throw_bad_function_call() as it is supposed to.

Regarding your second code block:
I assume that, due to your null pointer check, the compiler knows that f can never be null and optimizes the internal null pointer check including the call to __thow_bad_function_call away. So the linker doesn't need that function and doesn't complain. (As usual, I'm impressed how smart modern compilers are...)
 
I haven't distilled a simple example yet, but I've found a case where Teensy LC is the only one in the family that complains with the following, even with a `nullptr` check:
Code:
/Applications/Arduino-1.8.10.app/Contents/Java/hardware/tools/arm/arm-none-eabi/include/c++/5.4.1/functional:2266: undefined reference to `std::__throw_bad_function_call()'

Compiling for all the other boards works. Basically, I have an array of `std::function<void()>` and do a check like this:
Code:
if (funcs[0] != nullptr) {
  funcs[0]();
}

This fails compilation just for Teensy LC. I know it's this line because commenting out the function call allows the program to compile. My simple test cases all work fine, but the program that's failing compilation is still too large to post here; still working on simplifying an example.

Changing to the following also does not work:
Code:
std::function<void()> f = funcs[0];
if (f != nullptr) {
  f();
}

So for now, I've added this to the top of my source file:
Code:
#if defined(KINETISL)
namespace std {
  void __throw_bad_function_call() {
    Serial.println("EXCEPTION: Bad function call!");
    while (true) {
      // Don't return
    }
  }
}
#endif  // KINETISL
 
Try changing the setting in Tools > Optimize.

On Teensy 3.2, 3.5, 3.6 and 4.0, the default is "Faster", which compiles your code with -O2 and links with the regular C library.

But on Teensy LC the default is "Smallest Code", which complies with -Os and uses "--specs=nano.specs" which configures the linker for a minimal C library. The result is much less memory usage, and some seldom used features not working. Usually the main one people notice is lack of floating point support in printf(). Maybe that minimal C lib is also causing the trouble you're seeing?

Also, please keep in mind we always use compiler flags -fno-exceptions and -fno-rtti, so those C++ language features aren't ever available.
 
Yep, that did it. I changed the optimization option to "faster" and now it compiles. Thanks!

Side question: Those `__throw_XXX` functions are called even when -fno-exceptions is enabled, is that true?
 
For posterity, here's what I settled on:
Code:
// This is defined so that calling a std::function<void()> can compile when
// size optimization is enabled. Teensy LC has "Smallest Code" set by default,
// for example.
namespace std {
#if !defined(__throw_bad_function_call)
  void __throw_bad_function_call() {
    Serial.println("EXCEPTION: Bad function call!");
    while (true) {
      // Don't return
    }
  }
#endif
}  // namespace std
 
Those `__throw_XXX` functions are called even when -fno-exceptions is enabled, is that true?

They are not called even when -fno-exceptions is enabled, they are called instead of throwing an exception. I.e, they are called because of -fno-exeptions is enabled. Defining them like you did above is nothing wrong or bad at all, it is the supposed way to tell the compiler/STL what to do instead of throwing an exception if something is badly wrong with your std::function.

IMHO, not defining __throw_bad_function_call and rely on the optimizer and a manual nullptr check instead is not a good idea. I always define this (and if required, other exception replacement functions) and rely on the STL to do the nullptr checks.
 
Thanks for the explanation.

This leads me to the next question: if I’m defining it in a library, is it best to mark it with "attribute weak" to avoid potential conflicts with user code? On one hand, I don’t want to cause confusion if it’s needed, but on the other, I figure some advanced users might want or need to define it themselves.
 
That's an interesting point. So far I always ignored this problem but felt that something like weak might be better. But, the pain was never high enough to dig into it ;-). So, if you find out how to define this as weak I'd be very interested.
 
I believe it's `__attribute__((weak))`, like so:
Code:
// This is defined so that calling a std::function<void()> can compile when
// size optimization is enabled. Teensy LC has "Smallest Code" set by default,
// for example.
namespace std {
#if !defined(__throw_bad_function_call)
  __attribute__((weak))
  void __throw_bad_function_call() {
    Serial.println("EXCEPTION: Bad function call!");
    while (true) {
      // Don't return
    }
  }
#endif  // !__throw_bad_function_call
}  // namespace std

This is my new declaration inside the library. Maybe with `weak`, I don't need the #ifndef...?
 
Last edited:
Oh, thats really simple. Gave it a try with this sketch

Code:
#include "Arduino.h"
#include <functional>

std::function<void(void)> f;

void myFunc()
{
  Serial.println("myFuncCalled");
}

void setup()
{
  while(!Serial);
  Serial.println("Start");

  // f = myFunc;  // uncomment to work normally
}

void loop()
{
  f();

  digitalWriteFast(LED_BUILTIN,!digitalReadFast(LED_BUILTIN));
  delay(1000);
}

// uncomment the following to override the weak library function from test.cpp

// namespace std
// {
//   void __throw_bad_function_call()
//   {
//     Serial.println("User bad function call");
//     while (true) yield();
//   }
// }

and this file (test.cpp) to simulate a library
Code:
#include "Arduino.h"

namespace std
{
   __attribute__((weak))
   void __throw_bad_function_call()
   {
     Serial.println("Library Exception");
     while (true) yield();
   }
}

Works perfectly. Thanks for the idea.
 
Status
Not open for further replies.
Back
Top