Добрый день.
Вчере игрался с делением на ноль, вернее, с его перехватом. Да, оказалось это можно сделать, запретив программе повисать при делении на ноль. Для чего это было нужно мне - у меня есть программа, которая делит на ноль умышленно. Это все для нее. Для антиотладки.
Для чего это нужно еще кому-то: как пример перехвата и обработки исключений: без ассемблерных инструкций, без каких либо вызовов ESP-IDF API.
Скетч ниже выводит число деленое на ноль. Ну, вернее, выводит-то он undefined data, но не повисает. Описание того, что происходит - в коде скетча, в комментариях.
#include <Arduino.h> // а то оффтпик же!
// Номер исключения, которое мы хотим поймать.
// Всего исключений аж 64 штуки, но мы будем играться с делением на ноль
//
#define DIV0_EXC_NUMBER 6
// Структура, указатель на которую передается функции-обработчику исключений.
// В структуру можно писать, таким образом меня состояние процессора
//
struct exc_frame {
uint32_t unknown0; // Не знаю
uint32_t pc; // Указатель на текущую инструкцию (Program Counter Register)
uint32_t known[20]; // Пучок регистров от а0 до а15, немного специальных регистров
};
// Таблица указателей на обработчики исключений, на каждое ядро - своя, 64 указателя на каждое ядро.
// Таблица устанавливается при старте системы
//
// Обработчики вызываются ядром при возникновении исключительных ситуаций.
// Почти все исключения обрабатываются одной и той же функцией, которая в конце-концов
// вызывает panic() и мы видим "Guru Meditation" и дамп регистров. Мы повисли.
//
// Некоторые исключения обрабатываются ядром молча - например, невыровненный доступ к
// памяти: исключение исправит доступ и продрлжит исполнение
//
typedef void (*funcptr_t)(struct exc_frame *);
extern funcptr_t _xt_exception_table[];
// Наш новый обработчик деления на ноль: просто пропускаем инструкцию.
// Инструкция деления на Xtensa - 3 байта
//
// Параметр функции - указатель на Exception stack frame - структуру в
// памяти, в которой содержится россыпь регистров на момент исключения и
// указатель на текущую инструкцию, вызвавшую исключение (->pc)
//
// Мы исправляем PC так, чтобы он указывал на следующую инструкцию и возвращаемся.
// Результат деления будет неопределен, но программа продолжит выполнение
//
// В этом обработчике нельзя дергать функции типа delay() и прочее, связанное с шедулером
//
// Печатать на экран можно, но осторожно - через extern ets_rom_printf(const char *, ...)
//
static void div0_handler(struct exc_frame *f) {
f->pc += 3;
}
// Делитель
volatile int j = 1;
void setup() {
Serial.begin(115200);
// Перехватываем обработку деления на ноль, записывая в таблицу обработчиков адрес нашего.
// Для обоих ядер!
// Закоментируете обе строчки и скетч будет повисать, как и должен.
//
_xt_exception_table[DIV0_EXC_NUMBER * portNUM_PROCESSORS + 0] = div0_handler;
#if portNUM_PROCESSORS > 1
_xt_exception_table[DIV0_EXC_NUMBER * portNUM_PROCESSORS + 1] = div0_handler;
#endif
// Закладываем бонбу
j = 0;
}
// Момент истины. В цикле.
//
void loop() {
delay(1000);
// Грохнемся или нет?
for (int i = 0x61; i < 0x6e; i++)
Serial.printf("%% %x\r\n", i / j);
}
Когда ваш софт падает с вот таким сообщением привычным:
Guru Meditation Error: Core 1 panic'ed (IntegerDivideByZero). Exception was unhandled.
Core 1 register dump:
PC : 0x42001d07 PS : 0x00060030 A0 : 0x82004a93 A1 : 0x3fcec0f0
A2 : 0x00000001 A3 : 0x0000002c A4 : 0x0000002b A5 : 0xffffffff
A6 : 0x00000000 A7 : 0x00000061 A8 : 0x3fc93c00 A9 : 0x3fcec0d0
A10 : 0x3fc976c0 A11 : 0x3c030120 A12 : 0x00000000 A13 : 0x0000002b
A14 : 0x420050bf A15 : 0x3fcec0ac SAR : 0x00000002 EXCCAUSE: 0x00000006
EXCVADDR: 0x00000000 LBEG : 0x400556d5 LEND : 0x400556e5 LCOUNT : 0xfffffff5
вы можете посмотреть на циферку в регистре EXCCAUSE - это и будет интересующий вас номер исключения. И таким образом поймать интересующее вас. Списка “название=номер“ для исключений я что-то не нашел. Может фигово искал.
ЗЫЖ Только сейчас заметил, что объявил exception table просто как extern , без extern “C“ а оно один фиг работает. Интересно.