Получение адреса возврата исключения на ARM Cortex M0

Я пытаюсь получить в своем коде адрес возврата обработчика IRQ. Моя цель - сохранить значение ПК непосредственно перед истечением сторожевого таймера и перед сбросом для целей отладки с помощью WDT_IRQHandler (). Я также тестирую этот подход с другими IRQ, чтобы проверить, уловил ли я идею. Но, похоже, нет.

Я прочитал документацию доступно. Я понял, что при возникновении исключения в стек помещается 8 регистров: R0, R1, R2, R3, R12, LR, PC и XPSR.

Я также читал, что стек автоматически выравнивается по двойному слову. На мой взгляд, получить обратный адрес так же просто, как:

  • получить адрес sp с помощью __builtin_frame_address (0);
  • добавьте к нему смещение сложенного ПК (0x18) и прочтите значение, которое предположительно является значением, которое будет восстановлено на ПК, когда обработчик вернется.

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

Код работает нормально, поэтому я думаю, что у меня проблемы с пониманием того, как он работает.

Если я проверю разборку, в некоторых IRQ к sp перед POPping (?) Добавляется константа.

00001924: 0x000009b0 ...TE_IRQHandler+280   add     sp, #36 ; 0x24
00001926: 0x0000f0bd ...TE_IRQHandler+282   pop     {r4, r5, r6, r7, pc}

В других IRQ этого не происходит.

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

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


person Vitomakes    schedule 27.07.2016    source источник


Ответы (1)


Вы не можете полагаться на указатель стека внутри обработчика C по двум причинам:

  1. Регистры всегда помещаются в активный стек для вытесненного кода. Обработчики всегда используют основной стек (MSP). Если прерывание вытесняет код режима потока, который выполняется из стека процессов (PSP), тогда регистры будут помещены в PSP, и вы никогда не найдете их в стеке обработчика;
  2. Подпрограмма C, вероятно, зарезервирует некоторое пространство стека для локальных переменных, и вы не знаете, сколько это, поэтому вы не сможете найти регистры.

Вот как я обычно это делаю:

void WDT_IRQHandler_real(uint32_t *sp)
{
    /* PC is sp[6] (sp + 0x18) */
    /* ... your code ... */
}

/* Cortex M3/4 */
__attribute__((naked)) void WDT_IRQHandler()
{
    asm volatile (
        "TST   LR, #4\n\t"
        "ITE   EQ\n\t"
        "MRSEQ R0, MSP\n\t"
        "MRSNE R0, PSP\n\t"
        "LDR   R1, =WDT_IRQHandler_real\n\t"
        "BX    R1"
    );
}

/* Cortex M0/1 */
__attribute__((naked)) void WDT_IRQHandler()
{
    asm volatile (
        "MRS R0, MSP\n\t"
        "MOV R1, LR\n\t"
        "MOV R2, #4\n\t"
        "TST R1, R2\n\t"
        "BEQ WDT_IRQHandler_call_real\n\t"
        "MRS R0, PSP\n"
    "WDT_IRQHandler_call_real:\n\t"
        "LDR R1, =WDT_IRQHandler_real\n\t"
        "BX  R1"
    );
}

Хитрость здесь в том, что обработчик представляет собой небольшой фрагмент сборки (я использовал голую функцию с GCC asm, вы также можете использовать отдельный файл asm), который передает указатель стека реальному обработчику. Вот как это работает (для M3 / 4):

  • Начальное значение LR в обработчике исключений известно как EXC_RETURN (подробнее здесь). Его биты имеют различное значение, нас интересует тот факт, что EXC_RETURN[2] равно 0, если активным стеком был MSP, и 1, если активным стеком был PSP;
  • TST LR, #4 проверяет EXC_RETURN[2] и устанавливает флаги условий;
  • MRSEQ R0, MSP перемещает MSP в R0, если EXC_RETURN[2] == 0;
  • MRSNE R0, PSP перемещает PSP в R0, если EXC_RETURN[2] == 1;
  • Наконец, _22 _ / _ 23_ переходит к реальной функции (R0 - первый аргумент).

Вариант M0 / 1 похож, но использует ветви, так как ядро ​​не поддерживает ИТ-блоки.

Это решает проблему _25 _ / _ 26_, и, поскольку он выполняется перед любой операцией стека, созданной компилятором, он предоставит надежный указатель. Я использовал простую (несвязанную) ветвь функции, потому что мне не нужно ничего делать после нее, и LR уже готов. Это экономит несколько циклов и LR нажатие / нажатие. Также все используемые регистры находятся в R0-R3 рабочем диапазоне, поэтому нет необходимости их сохранять.

person Andrea Biondo    schedule 27.07.2016
comment
Спасибо. для пункта 1) Я предполагал, что могу проверить как MSP, так и PSP с C, используя __get_MSP () и __get_PSP (). для 2) Я понимаю, что после нажатия код может добавлять локальные переменные в стек, и они известны во время компиляции, поэтому я не могу полагаться на фиксированное смещение. Я попробую ваш подход. - person Vitomakes; 28.07.2016
comment
@Vitomakes Проверка MSP и PSP из C не говорит вам, какой из них правильный. Конечно, если вы используете только MSP (очень распространенный), вы можете пропустить проверку. - person Andrea Biondo; 28.07.2016
comment
ITE и MRSEQ / MRSNE не поддерживаются cortex M0, и компилятор жалуется на то, что находится в режиме большого пальца и не может использовать TST. Но у меня есть идея, я разберусь - person Vitomakes; 28.07.2016
comment
@Vitomakes Исправил. - person Andrea Biondo; 28.07.2016