Я думаю, что лучшее, что вы можете сделать, это найти адрес инструкции, к которой вернулся бы обработчик, если бы ему было позволено вернуться. Обычно это будет инструкция, следующая за той, которая вызвала ошибку, хотя это не гарантируется (например, если ошибка была вызвана командой перехода).
При входе в обработчик регистр ссылок содержит код, который, среди прочего, сообщает вам, какой стек использовался, когда произошло исключение. См., например, здесь для 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