Получение обратного адреса исключения на 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. Я не могу понять, как получить правильный адрес.
1 ответ
Вы не можете полагаться на указатель стека внутри обработчика C по двум причинам:
- Регистры всегда помещаются в активный стек для выгруженного кода. Обработчики всегда используют основной стек (
MSP
). Если прерывание прерывает код режима потока, который выполняется из стека процессов (PSP
) затем регистры будут переданы вPSP
и вы никогда не найдете их в стеке обработчиков; - Процедура 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
;- В заключение,
LDR
/BX
переходит к реальной функции (R0
это первый аргумент).
Вариант M0/1 аналогичен, но использует ветви, поскольку ядро не поддерживает блоки IT.
Это решает MSP
/PSP
и, поскольку он запускается перед любой стековой операцией, генерируемой компилятором, он обеспечивает надежный указатель. Я использовал простую (не связанную) ветвь для функции, потому что мне не нужно ничего делать после нее и LR
уже хорошо идти. Это экономит несколько циклов и LR
тяни / поп. Также все используемые регистры находятся в R0-R3
диапазон царапин, поэтому нет необходимости сохранять их.