Сбой защиты от разрушения стека ESP32 с Modbus RTU

Я делаю проект, используя ESP32 TTGO с LoRa, Max485 и WELLPRO ADAM 8082 для связи Modbus. Я пытаюсь использовать ESP32 в качестве ведущего, а ADAM в качестве ведомого. Я также использую Node-RED для отправки входных сигналов (чтение цифровых входов на ADAM или отправка сигналов на цифровые выходы). Всякий раз, когда я отправляю сигнал, проблем нет, ADAM правильно его принимает и отправляет ответ. Но когда мой ESP32 получает ответ, я получаю следующую ошибку:

Stack smashing protect failure!

abort() was called at PC 0x400dc860 on core 1

Я попытался отладить свою программу, и ошибка возникает, когда моя программа возвращается из функции, которая имеет мой цикл чтения Modbus. Я новичок в Arduino, поэтому не совсем понимаю, что может быть причиной этого переполнения стека.

Вот мой код.

Мои глобальные переменные:

unsigned char ucSt[N_MAX];
int delayTime;

HardwareSerial modbusData(2);

WiFiClientSecure espClient;
PubSubClient client(espClient);
boolean bWifiConnected = false;

const byte button = PRG_BUTTON;

Чтение Modbus с циклом:

void vReadingModBus()
{
  int i, nCmpt = 0;
  unsigned int uiCRC;

  while (modbusData.available() > 0)
  {
    Serial.print("nCmpt: ");
    Serial.println(nCmpt);
    ucSt[nCmpt] = (unsigned char)modbusData.read();
    nCmpt++;
    delay(delayTime - 2);
  }
  if (nCmpt)
  {
    Serial.print("He llegit aquests ");
    Serial.print(nCmpt);
    Serial.println(" bytes: ");
    char szResposta[N_MAX] = "", szByte[3];
    for (i = 0; i < nCmpt; i++)
    {
      sprintf(szByte, "%02X", ucSt[i]);
      strcat(szResposta, szByte);
      Serial.print(ucSt[i], HEX);
      Serial.print(" ");
    }
    Serial.println();
    client.publish(TEMA_RESPOSTA_MODBUS, szResposta);
    uiCRC = uiModRTU_CRC(ucSt, nCmpt - 2);
    if ((byte)(uiCRC >> 8) == ucSt[nCmpt - 1] && (byte)(uiCRC & 0xFF) == ucSt[nCmpt - 2])
    {
      Serial.println("Trama amb CRC correcte");
    }
    else
    {
      Serial.println("Trama amb CRC incorrecte");
    }
  }
  nCmpt = 0;
}

Передача Modbus:

void vModbusTx(unsigned char *uc, int nLen)
{
  int i;

  Serial.print("Enviat: ");
  for (i = 0; i < nLen; i++)
  {
    Serial.print(uc[i], HEX);
    Serial.print(" ");
  }
  Serial.println();

  vModeTxRxRS485(RS485Transmit);
  delay(1);
  for (i = 0; i < nLen; i++)
  {
    modbusData.write(uc[i]);
  }
  delay(delayTime);

  vModeTxRxRS485(RS485Receive);
}

Моя установка:

void setup() {
  Serial.begin(115200);    // U0_RXD:GPIO3, U0_TXD:GPIO1 (UART0)
  modbusData.begin(9600); // U2_RXD:GPIO16, U2_TXD:GPIO17 (UART2)

  pinMode(PRG_BUTTON, INPUT_PULLUP);
  pinMode(GPIO_R2, INPUT); // 39
  pinMode(SSerialTxControl, OUTPUT); // жестко подключен к GPIO_R2
  pinMode(LED, OUTPUT);

  vModeTxRxRS485(RS485Receive); // RS485Receive равен 0, а vModeTxRxRS485 управляет битами DE/RE для Max485

  delayTime = 0;

  // Wire.begin (I2C_SCL_OLED);

  delay(1000); // дайте мне время запустить серийный монитор
}

Мой цикл:

void loop() {
  boolean bButtonState = !digitalRead(button);
  static boolean bLastButtonState = bButtonState;

  // ЦИКЛ ЧТЕНИЯ MODBUS
  vReadingModBus();

  if (!client.connected())
  {
    reconnect();
  }

  // ПРОВЕРКА СОСТОЯНИЯ КНОПКИ
  if (bButtonState != bLastButtonState)
  {
    bLastButtonState = bButtonState;
    vDelayESP(50);
    if (!bButtonState)
      client.publish(TEMA_PUBLICA_ESTAT_BOTO, "UNPRESSED");
    else
      client.publish(TEMA_PUBLICA_ESTAT_BOTO, "PRESSED");
  }

  client.loop();
}

И, наконец, вот пример моей программы, работающей и выдающей упомянутую ошибку:

Connecting to WiFi network
Waiting for WIFI connection...
..............................................
Attempting MQTTS connection...connected
Message arrived [/clot/dam/biel/esp32/escriuModbus] 010F000000080111
Petició a ModBus:
010F000000080111
Mida: 8
ucSt[0] = 1
ucSt[1] = F
ucSt[2] = 0
ucSt[3] = 0
ucSt[4] = 0
ucSt[5] = 8
ucSt[6] = 1
ucSt[7] = 11
Enviat: 1 F 0 0 0 8 1 11 3E 99 
He llegit aquests 8 bytes: 
1 F 0 0 0 8 54 D 
Trama amb CRC correcte

Stack smashing protect failure!

abort() was called at PC 0x400dc7d8 on core 1

Backtrace: 0x4008c800:0x3ffb1ef0 0x4008ca31:0x3ffb1f10 0x400dc7d8:0x3ffb1f30 0x400d1eed:0x3ffb1f50 0x400d23c0:0x3ffb1f90 0x400d513d:0x3ffb1fb0 0x4008879d:0x3ffb1fd0

Rebooting...
ets Jun  8 2016 00:22:57

rst:0xc (SW_CPU_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0018,len:4
load:0x3fff001c,len:928
ho 0 tail 12 room 4
load:0x40078000,len:8424
ho 0 tail 12 room 4
load:0x40080400,len:5868
entry 0x4008069c

, 👍0


1 ответ


Лучший ответ:

2

tl;dr: сообщение, вероятно, означает, что ваша функция перезаписывает свой массив:

Переполнение стека вызвано тем, что стек расширяется вниз в выделенное пространство кучи, что приводит либо к повреждению данных в куче, либо к повреждению содержимого стека кодом, использующим это пространство кучи.

Но "разрушение стека" относится к повреждению, вызванному записью кода функции за пределы автоматической локальной переменной и повреждением содержимого стека, обычно включая адрес возврата функции.

[Вы можете пропустить довольно длинное объяснение:]

Кадр стека функции начинается с адреса возврата — адреса, следующего за инструкцией, вызывающей функцию, т. е. с адреса следующей инструкции, которая должна быть выполнена после возврата из функции. Затем, строя вниз, параметры вызова помещаются в стек и становятся локальными переменными. Затем выделяются любые переменные, выделенные во время выполнения функции, продолжая строиться вниз.

Предположим, что одна из этих выделенных переменных является массивом. Его нулевой элемент находится по младшему адресу выделенного пространства, а последующие элементы — по последовательно более высоким адресам. Если я начну запись в массив и продолжу его длину, я в конечном итоге перезапишу другие автоматические локальные переменные, мои параметры и мой обратный адрес. Затем, когда функция пытается вернуться, вместо перехода к коду, вызвавшему функцию, MCU использует поврежденный адрес возврата и переходит к... ????

Чтобы отловить эту ошибку, компилятор может вставить код, который 1) выделяет и записывает "канарейку" — известное значение — сразу после адреса возврата и 2) проверяет канарейку на наличие повреждений непосредственно перед возвратом функции. Если канарейка была изменена, вы получите сообщение, которое вы видели, потому что вполне вероятно, что обратный адрес также был изменен. Но в любом случае канарейка не является частью вашего кода и недоступна для него. Таким образом, он обнаруживает ошибки записи за пределы границ.

[Конец многословного объяснения]

Проверьте код в вашей функции 'void vReadingModBus()' - мои деньги находятся в цикле, который записывает в szResposta[]:

  char szResposta[N_MAX] = "", szByte[3];
    for (i = 0; i < nCmpt; i++)
    {
      sprintf(szByte, "%02X", ucSt[i]);
      strcat(szResposta, szByte);
      Serial.print(ucSt[i], HEX);
      Serial.print(" ");
    }

Обновление:

Если я правильно понял, это означает, что szResposta перезаписывает обратный адрес функции? Как этого избежать?

Короткий ответ: сделайте массив достаточно большим для того, что вы собираетесь в него поместить. Но это, вероятно, неудовлетворительный ответ, поэтому давайте посмотрим поближе. Я не совсем уверен, каково ваше намерение в этом цикле, но поскольку вы печатаете результат strcat() на каждой итерации и, похоже, вы не используете szResposta после что, возможно, вы хотели вызвать strcpy() (заменить содержимое массива) вместо strcat() (расширить содержимое массива). Если это так, strcat(), скорее всего, выполнит запись за пределами массива.

,

Огромное спасибо за это фантастическое объяснение! Это определенно была петля. Строка, выдающая ошибку: strcat(szResposta, szByte). Если я правильно понял, это означает, что szResposta должен перезаписывать обратный адрес функции? Как мне этого избежать? Спасибо еще раз!, @Biel