esp32 Stack canary watchpoint срабатывает

Я пишу программу для esp32 на Arduino IDE. В какой-то момент выполнения кода для чтения файла я сталкиваюсь с этим исключением,

Guru Meditation Error: Core  1 panic'ed (Unhandled debug exception)
Debug exception reason: Stack canary watchpoint triggered (loopTask) 

Однако, прочитав несколько форумов, я нашел ответ об увеличении глубины стека для вашей задачи FreeRTOS. Мой вопрос: есть ли другой способ справиться с этим исключением, кроме увеличения размера стека и предотвращения сбоя программы?

, 👍2


1 ответ


6

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

Три наиболее распространенных способа использования слишком большого пространства стека:

  1. большие локальные переменные - например, объявление большого массива в качестве локальной переменной внутри функции, например:
#define VERY_LARGE_STRING_LENGTH 8000

void loop() {
  char my_very_large_string[VERY_LARGE_STRING_LENGTH];

  for(i = 0; i < VERY_LARGE_STRING_LENGTH; i++)
    my_very_large_string[i] = ' ';
}

Локальные переменные хранятся в стеке, поэтому их большой массив будет занимать много места в стеке.

  1. рекурсивные функции - каждый раз, когда функция рекурсирует, она использует пространство стека. Если он рекурсирует достаточно глубоко, то он растопчет защиту стека и вызовет это исключение. Например:
int count(i) {
  i--;

  if(i > 0) {
    Serial.println(count(i));
  }

  return i;
}

void loop() {
  count(8000);
}

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

  1. ошибки в вашем коде, такие как дикие указатели и переполнение буфера
void loop() {
  char my_bad_string[10];
  char *wild_pointer;

  // переполнение буфера
  for(i = 0; i < 8000; i++)
    my_bad_string[i] = ' ';

  // кто знает, где это приведет к указанию
  wild_pointer += rand();
  wild_pointer = ' ';
}

когда вы переполняете массив в стеке, вы, вероятно, перезаписываете другие переменные и искажаете их значения. Сделайте это достаточно, и вы попадете в охранника стека. Помните, что строки C (массивы символов и указатели) нуждаются в n + 1 символах для хранения строки длиной n, потому что они заканчиваются дополнительным нулевым символом '\ 0'.

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

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

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

int *do_not_do_this() {
  int this_will_not_work_the_way_you_hope = 0;

  return &this_will_not_work_the_way_you_hope;
}

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

,