Getting more info from ld errors

My build fails with the “collect2: error: ld returned 1 exit status” error. There are a number of warnings but no errors other than this cryptic ld error. I have added “-Wl, -v” to my build flags but still do not see any additional error information. I did check the other collect2 erro threads but don’t see this issue. I run VSCode and Platformio on MacOS. Where can I find the errors ld is complaining about?

The build output was too big to fit in a message on this board. However, it is on this pastebin → Platformio build failure -

Compiling .pio/build/teensy41/src/RVC_100hz_mixed.ino.cpp.o

There seems to be an “ino” file in between.

Convert this into a valid cpp file. See Convert Arduino file to C++ manually — PlatformIO latest documentation

Thanks sivar2311. I collaborate on this project with some folks who still use the Arduino IDE hence the .ino file. “RVC_100hz_mixed.ino” is the main .ino file so Platformio creates a temporary .cpp file for compilation and then deletes it.

I am narrowing it down to a recently created library. If I don’t instantiate an object from the class in this new library, everything works fine. I did find on Mac I need to use -Wl, --verbose instead of -Wl, -v so LD will provide more info. However, the more verbose output still does not show any errors. Only warnings.

Very strange. The new library uses AsyncWebServer_Teensy41. If the include for AsyncWebServer_Teensy41 is in the .h file for the library I get the ld errors. If I move the include for AsyncWebServer_Teensy41 into the .cpp file for the new library, all is well. The .cpp file does have an include for the .h file so I think the reference should have been fine.

That is the nature of C++ :wink:
If you define an object in a header file you end with “Multiple definitions…”

The correct way to do this is to declare the Object as extern in the .h file


extern SomeClass myClass;

and define it in the .cpp file


#include "myLib.h"

SomeClass myClass;

I didn’t quite follow the recommendation.

Here is OTA-Update.h

#ifndef OTA_Update_h
#define OTA_Update_h
#include "Arduino.h"
//#include <AsyncWebServer_Teensy41.h>
#include "FXUtil.h"		// read_ascii_line(), hex file support
extern "C" {
  #include "FlashTxx.h"		// TLC/T3x/T4x/TMM flash primitives

class OTA_Update {
    void OTAapply();


Here is OTA_Update.cpp

#include "Arduino.h"
#include "AsyncWebServer_Teensy41.h"
#include "OTA_Update.h"

// hex_info_t	struct for hex record and hex file info
typedef struct {	// 
char *data;		// pointer to array allocated elsewhere
unsigned int addr;	// address in intel hex record
unsigned int code;	// intel hex record type (0=data, etc.)
unsigned int num;	// number of data bytes in intel hex record

uint32_t base;	// base address to be added to intel hex 16-bit addr
uint32_t min;		// min address in hex file
uint32_t max;		// max address in hex file

int eof;		// set true on intel hex EOF (code = 1)
int lines;		// number of hex records received  
} hex_info_t;

void read_ascii_line( Stream *serial, char *line, int maxbytes );
int  parse_hex_line( const char *theline, char *bytes, unsigned int *addr, unsigned int *num, unsigned int *code );
int  process_hex_record( hex_info_t *hex );
void update_firmware( Stream *in, Stream *out, uint32_t buffer_addr, uint32_t buffer_size );

static char line[96];					// buffer for hex lines
int line_index=0;
static char data[32] __attribute__ ((aligned (8)));	// buffer for hex data
hex_info_t hex = {					// intel hex info struct
  data, 0, 0, 0,					//   data,addr,num,code
  0, 0xFFFFFFFF, 0, 					//   base,min,max,
  0, 0						//   eof,lines
bool ota_status=0; // 1=running
bool ota_final=0;
bool ota_apply=0;
uint32_t buffer_addr, buffer_size;

AsyncWebServer server(80);

void handleNotFound(AsyncWebServerRequest *request)
  request->send(404, "text/plain", "Not found");

void OTAend(AsyncWebServerRequest *request){
  AsyncWebParameter* p = request->getParam(0);
  Serial.printf("FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size());
  if (ota_final){
    Serial.printf( "\nhex file: %1d lines %1lu bytes (%08lX - %08lX)\n",
		hex.lines, hex.max-hex.min, hex.min, hex.max );

    // check FSEC value in new code -- abort if incorrect
    #if defined(KINETISK) || defined(KINETISL)
    uint32_t value = *(uint32_t *)(0x40C + buffer_addr);
    if (value != 0xfffff9de) {
      Serial.printf( "new code contains correct FSEC value %08lX\n", value );
    else {
      Serial.printf( "abort - FSEC value %08lX should be FFFFF9DE\n", value );

  if (ota_final){
    // check FLASH_ID in new code - abort if not found
    if (check_flash_id( buffer_addr, hex.max - hex.min )) {
      Serial.printf( "new code contains correct target ID %s\n", FLASH_ID );
    else {
      Serial.printf( "abort - new code missing string %s\n", FLASH_ID );

  AsyncWebServerResponse *response = request->beginResponse((!ota_final)?500:200, "text/plain", (!ota_final)?"OTA Failed... Going for reboot":"OTA Success! Going for reboot");
  response->addHeader("Connection", "close");
  response->addHeader("Access-Control-Allow-Origin", "*");


void OTA(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
  if (!ota_status){
    Serial.println("Starting OTA...");
    if (firmware_buffer_init( &buffer_addr, &buffer_size ) == 0) {
        Serial.println( "unable to create buffer" );
    } else {
      Serial.printf( "created buffer = %1luK %s (%08lX - %08lX)\n",
      buffer_size/1024, IN_FLASH(buffer_addr) ? "FLASH" : "RAM",
      buffer_addr, buffer_addr + buffer_size );
  if (ota_status) {
      size_t i=0;
      while (i<len){
        if (data[i]==0x0A || (line_index==sizeof(line)-1)){ // '\n'
          line[line_index] = 0;	// null-terminate
          //Serial.printf( "%s\n", line );
          if (parse_hex_line( (const char*)line,, &hex.addr, &hex.num, &hex.code ) == 0) {
            Serial.printf( "abort - bad hex line %s\n", line );
            return request->send(400, "text/plain", "abort - bad hex line");
          else if (process_hex_record( &hex ) != 0) { // error on bad hex code
            Serial.printf( "abort - invalid hex code %d\n", hex.code );
            return request->send(400, "text/plain", "invalid hex code");
          else if (hex.code == 0) { // if data record
            uint32_t addr = buffer_addr + hex.base + hex.addr - FLASH_BASE_ADDR;
            if (hex.max (FLASH_BASE_ADDR + buffer_size)) {
              Serial.print("addr: "); Serial.println(addr);
              Serial.print("buffer_addr: "); Serial.println(buffer_addr);
              Serial.print("buffer_size: "); Serial.println(buffer_size);
              Serial.print("hex.base: "); Serial.println(hex.base);
              Serial.print("hex.addr: "); Serial.println(hex.addr);
              Serial.print("hex.max: "); Serial.println(hex.max);
              Serial.print("FLASH_BASE_ADDR: "); Serial.println(FLASH_BASE_ADDR);
              Serial.printf( "abort - max address %08lX too large\n", hex.max );
              return request->send(400, "text/plain", "abort - max address too large");
            else if (!IN_FLASH(buffer_addr)) {
              memcpy( (void*)addr, (void*), hex.num );
            else if (IN_FLASH(buffer_addr)) {
              int error = flash_write_block( addr,, hex.num );
              if (error) {
                Serial.printf( "abort - error %02X in flash_write_block()\n", error );
                return request->send(400, "text/plain", "abort - error in flash_write_block()");
        } else if (data[i]!=0x0D){ // '\r'
    if (final) { // if the final flag is set then this is the last frame of data
        Serial.println("Transfer finished");
  } else {
    return request->send(400, "text/plain", "OTA could not begin");

//extern "C" char* sbrk(int incr);
int freeRam() {
  char top;
  return &top - reinterpret_cast<char*>(sbrk(0));

    Serial.print("\nOTA through Ethernet code for ");

    server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    String html = "<body><h1>OTA through Ethernet demo code for Teensy41</h1><br \><h2>select and send your binary file:</h2><br \><div><form method='POST' enctype='multipart/form-data' action='/'><input type='file' name='file'><button type='submit'>Send</button></form></div></body>";
    request->send(200, "text/html", html);

    server.on("/", HTTP_POST, OTAend, OTA);


void OTA_Update::OTAapply(){
  if (ota_final){ 
    Serial.printf( "calling flash_move() to load new firmware...\n" );
    flash_move( FLASH_BASE_ADDR, buffer_addr, hex.max-hex.min );
    for (;;) {}
  } else {
    Serial.printf( "erase FLASH buffer / free RAM buffer...\n" );
    firmware_buffer_free( buffer_addr, buffer_size );
    for (;;) {}

I’m no C++ guru, but this code looks dangerous to me. It could end in a static initialization order fiasco.

If you instantiate an object from the class OTA_Update (in a different .cpp file) it could happen that the server object from the OTA_Update.cpp is not initialized yet and the program crashes. The chances of this happening are 50%.

I assume there will only be one central object of the class OTA_Update in your project?

You should therefore declare this object in the header file and already instantiate the object in the cpp file.

Add this line to the header file (before the #endif):

extern OTA_Update otaUpdate;

Add this line to the CPP file:

OTA_Update otaUpdate;

The object 'otaUpdateis then available in all program parts that include theOTA_Update.h`. The static order fiasco is thus avoided.

Unfortunately, I don’t understand the purpose of the OTA_Update class. All important functions are outside the class. The constructor and the member functions OTAapply could also be simple functions. But that is a different topic.

Thanks again sivar2311. The code performs OTA updates of Teensy board firmware. The proposed commit from one of the collaborators was a bit of a head scratcher for me as well. It did compile but did not actually work. The web server never started. I sent the submitter back to the drawing board.