Как я могу сохранить стек вызовов из обработчика исключений в Cortex M4?

Вот чего я хочу добиться: всякий раз, когда я получаю серьезную ошибку или прерывание сторожевого таймера, я сохраняю адрес предыдущей инструкции в какой-либо ячейке ОЗУ, которая переживет сброс.

Сторожевой таймер Kinetis M64 дает мне 256 циклов процессора, прежде чем он выполнит сброс, что должно быть достаточно времени, чтобы сохранить несколько вещей. Вопрос, где найти эти адреса? Когда происходит IRQ, LR содержит значение исключения вместо фактического адреса возврата.

Я хочу сделать это без подключенного зонда SWD, чтобы устройство могло самостоятельно сообщать, когда что-то пойдет не так.


person Atilla Filiz    schedule 31.07.2019    source источник


Ответы (2)


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

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

Непосредственно перед переходом к обработчику исключений ЦП поместит r0-r3, r12, LR (r14), PC (r15) и xPSR в активный стек. Если на вашем устройстве есть FPU и он включен, контекст с плавающей запятой также может быть передан или для него может быть оставлено место. Стек ARM полностью нисходящий, и регистры хранятся в порядке возрастания номера регистра в возрастающих адресах памяти, поэтому при входе в обработчик исключений, какой бы указатель стека ни использовался, он будет указывать на стековое значение r0; поэтому само собой разумеется, что 6 слов (24 байта) выше этого будут составным значением PC, которое является адресом возврата для обработчика исключений.

Таким образом, процесс поиска инструкции после той, которая вызвала ошибку, если предположить, что она не была вызвана ветвью, таков:

  • Изучите LR, чтобы узнать, какой стек использовался
  • Загрузите соответствующий указатель стека в свободный регистр (все r0-r3 доступны, потому что они помещаются при входе в обработчик)
  • Прочитайте слово на 24 байта выше этого указателя стека, чтобы найти адрес возврата для обработчика.

Находится ли инструкция, вызвавшая ошибку, за 2 или 4 байта до этого адреса возврата, конечно, зависит от инструкции, набор инструкций Thumb-2 смешанный 16- и 32-битный. И, конечно же, он может быть расположен совсем в другом месте!

Имейте в виду, что если MSP использовался до ошибки, то обработчик будет использовать тот же стек, и все это будет работать только в том случае, если в прологе функции-обработчика ничего не было помещено в стек. Проще всего написать обработчик на ассемблере. Он всегда может вызвать функцию C после того, как закончит возиться со стеками, чтобы завершить любой процесс завершения, который вы имеете в виду.

И последнее, вероятно, стоит также сохранить сложенное значение LR. Если составное значение PC не говорит вам ничего полезного (например, потому что оно равно нулю, код пытался выполнить переход по недопустимому адресу), то составное значение LR, по крайней мере, скажет вам, где была обнаружена последняя инструкция BL, и если вам повезет, это будет ветвь, вызвавшая ошибку. Даже если вам не так повезло, это может помочь вам сузить область поиска.

Код

Вот некоторый (непроверенный) код, который может делать то, что вы хотите. Он написан на синтаксисе ARMASM, поэтому вам нужно будет изменить странную вещь, если вы используете другую цепочку инструментов:

    IMPORT cHandler

    TST   lr, 0x4       ; Is bit 2 of LR clear?
    ITE   eq
    MRSEQ r3, MSP       ; If so, MSP was in use
    MRSNE r3, PSP       ; Otherwise, PSP was in use
    LDR   r0, [r3, #24] ; Load the stacked PC into r0
    LDR   r1, [r3, #20] ; Load the stacked LR into r1
    B     cHandler      ; Tail-call a C function to finish the job

Если функция C cHandler имеет прототип

void cHandler(void * PC, void * LR);

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

person cooperised    schedule 31.07.2019
comment
Спасибо, это очень информативно. Я соглашусь, когда у меня будет возможность протестировать его. - person Atilla Filiz; 01.08.2019
comment
Как вы попали на? - person cooperised; 09.08.2019
comment
отвлекся на смену приоритетов, вернусь к этому, когда смогу. - person Atilla Filiz; 21.08.2019
comment
В итоге я сделал что-то немного другое, но я принимаю этот ответ, поскольку он указал мне правильное направление. - person Atilla Filiz; 11.12.2019

Я использовал следующий код с GCC.

void McuHardFault_HandlerC(uint32_t *hardfault_args)
{
  static volatile unsigned long stacked_r0 __attribute__((unused));
  static volatile unsigned long stacked_r1 __attribute__((unused));
  static volatile unsigned long stacked_r2 __attribute__((unused));
  static volatile unsigned long stacked_r3 __attribute__((unused));
  static volatile unsigned long stacked_r12 __attribute__((unused));
  static volatile unsigned long stacked_lr __attribute__((unused));
  static volatile unsigned long stacked_pc __attribute__((unused));
  static volatile unsigned long stacked_psr __attribute__((unused));
  static volatile unsigned long _CFSR __attribute__((unused));
  static volatile unsigned long _HFSR __attribute__((unused));
  static volatile unsigned long _DFSR __attribute__((unused));
  static volatile unsigned long _AFSR __attribute__((unused));
  static volatile unsigned long _BFAR __attribute__((unused));
  static volatile unsigned long _MMAR __attribute__((unused));
  stacked_r0 = ((unsigned long)hardfault_args[0]);
  stacked_r1 = ((unsigned long)hardfault_args[1]);
  stacked_r2 = ((unsigned long)hardfault_args[2]);
  stacked_r3 = ((unsigned long)hardfault_args[3]);
  stacked_r12 = ((unsigned long)hardfault_args[4]);
  stacked_lr = ((unsigned long)hardfault_args[5]);
  stacked_pc = ((unsigned long)hardfault_args[6]);
  stacked_psr = ((unsigned long)hardfault_args[7]);

  /* Configurable Fault Status Register */
  /* Consists of MMSR, BFSR and UFSR */
  _CFSR = (*((volatile unsigned long *)(0xE000ED28)));

  /* Hard Fault Status Register */
  _HFSR = (*((volatile unsigned long *)(0xE000ED2C)));

  /* Debug Fault Status Register */
  _DFSR = (*((volatile unsigned long *)(0xE000ED30)));

  /* Auxiliary Fault Status Register */
  _AFSR = (*((volatile unsigned long *)(0xE000ED3C)));


  /* Read the Fault Address Registers. */
  /* These may not contain valid values. */
  /* Check BFARVALID/MMARVALID to see */
  /* if they are valid values */
  /* MemManage Fault Address Register */
  _MMAR = (*((volatile unsigned long *)(0xE000ED34)));
  /* Bus Fault Address Register */
  _BFAR = (*((volatile unsigned long *)(0xE000ED38)));

  __asm("BKPT #0\n") ; /* cause the debugger to stop */
}

void HardFault_Handler(void) __attribute__((naked));
void HardFault_Handler(void)
{
  __asm volatile (
    ".syntax unified              \n"  /* needed for the 'adds r1,#2' below */
    " movs r0,#4                  \n"  /* load bit mask into R0 */
    " mov r1, lr                  \n"  /* load link register into R1 */
    " tst r0, r1                  \n"  /* compare with bitmask */
    " beq _MSP                    \n"  /* if bitmask is set: stack pointer is in PSP. Otherwise in MSP */
    " mrs r0, psp                 \n"  /* otherwise: stack pointer is in PSP */
    " b _GetPC                    \n"  /* go to part which loads the PC */
  "_MSP:                          \n"  /* stack pointer is in MSP register */
    " mrs r0, msp                 \n"  /* load stack pointer into R0 */
  "_GetPC:                        \n"  /* find out where the hard fault happened */
    " ldr r1,[r0,#24]             \n"  /* load program counter into R1. R1 contains address of the next instruction where the hard fault happened */
    " b McuHardFault_HandlerC   \n"  /* decode more information. R0 contains pointer to stack frame */
  );
}
person Atilla Filiz    schedule 11.12.2019