Forum Rule: Always post complete source code & details to reproduce any issue!
Page 1 of 2 1 2 LastLast
Results 1 to 25 of 27

Thread: Teensy 4.0 linker issues with STL libraries

  1. #1
    Junior Member
    Join Date
    Aug 2019
    Posts
    2

    Teensy 4.0 linker issues with STL libraries

    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

  2. #2
    Junior Member
    Join Date
    Aug 2019
    Posts
    4
    Did you ever find a solution to this? I'm also running into the same problem.

  3. #3
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    799
    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 by luni; 09-11-2019 at 05:51 AM.

  4. #4
    Junior Member
    Join Date
    Sep 2019
    Posts
    6
    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...

  5. #5
    Junior Member
    Join Date
    Aug 2019
    Posts
    4
    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

  6. #6
    Junior Member
    Join Date
    Aug 2019
    Posts
    4
    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

  7. #7
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    799
    Interesting. Seems to be some known problem. E.g. https://answers.launchpad.net/gcc-ar...uestion/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

  8. #8
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    6,335
    @luni - I am no expert in this, but google exidx-start finds: https://stackoverflow.com/questions/...hat-do-they-do
    Sounds like they are used for exception handling

    As you mentioned hopefully @MichaelMeissner can fill in more of the details.

    Not sure how the GCC here sets up to handle things like try/catch ...

  9. #9
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    799
    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.

  10. #10
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    4,807
    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/28181...ll=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

  11. #11
    Senior Member
    Join Date
    Dec 2016
    Location
    Montreal, Canada
    Posts
    3,267
    I would also declare them as weak in case the real ones get added later on (or duplicates)

  12. #12
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    799
    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 by luni; 09-12-2019 at 07:25 PM.

  13. #13
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    4,807
    @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.

  14. #14
    __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

  15. #15
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    169
    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 by shawn; 12-21-2019 at 01:50 AM.

  16. #16
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    799
    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...)

  17. #17
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    169
    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

  18. #18
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    21,473
    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.

  19. #19
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    169
    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?

  20. #20
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    169
    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

  21. #21
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    799
    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.

  22. #22
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    169
    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.

  23. #23
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    799
    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.

  24. #24
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    169
    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 by shawn; 01-04-2020 at 08:24 AM.

  25. #25
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    799
    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.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •