Как получить стек вызовов из исключения прерывания SoftWare
Я пытаюсь отладить программу, работающую на платформе ARM с голым железом, используя gdb
, В какой-то момент SWI
(Программное прерывание) генерируется исключение. Однако обратная трассировка не показывает, что породило исключение, как вы можете видеть:
(GDB) C
Continuing. ^C Program received signal SIGTRAP, Trace/breakpoint trap. 0xffff0008 in ?? () (gdb) bt #0 0xffff0008 in ?? ()
Программа, запущенная на ARM, была скомпилирована с arm-none-linux-gnueabi-gcc -O2 -c -ggdb
Я также пытался использовать -O0
с тем же результатом.
Как я могу получить значимый стек вызовов? Есть ли другой способ найти то, что генерирует это исключение?
1 ответ
Чтобы понять проблему gdb
с производством трассировки стека, вы должны сначала понять, как gdb
производит трассировку стека. Компилятор использует стандартный пролог и эпилог, когда создает C
функция, это код ассемблера при входе и выходе из функции. Часть этого, чтобы сохранить lr
в стеке зарезервируйте место для локальных переменных и свяжите указатель предыдущего кадра или fp
, Эти стековые фреймы предоставляют тип связанного списка, который коренится с fp
и обычно заканчивается нулем. Это зависит от ABI (см. -Mabi). Специфика немного отличается для каждого типа ARM ABI, но концепции похожи.
Итак, когда SWI
(или происходит любое исключение), оно полностью прерывает поток C
Скомпилированный код и список указателей фреймов могут быть трудны для декодирования, особенно в случае повреждения стека. Также, gdb
не декодирует контекст программы. Некоторые системы могут изменить указатель кадра и немедленно переключиться с exception mode
в system/supervisor mode
, sp
исключения могут даже использоваться в качестве чистых регистров. Когда SWI
часть работы обработчика будет заключаться в сохранении user
регистры (r0-r12
). В случае многозадачной операционной системы это может привести к полному изменению user
стек, от одной задачи к другой.
Вы всегда можете определить ошибочную / вызывающую инструкцию, изучив lr
в режиме исключения. Это указано в ARM Architechure, и то же самое для любого процессора ARM. 0xfff0008
по умолчанию SWI
адрес обработчика (при использовании таблицы векторов с высокой памятью). Исключения из SWI
в ARM ARM (справочное руководство по архитектуре) следует,
A2.6.4 Исключение программного прерывания
Команда программного прерывания (SWI) переходит в режим супервизора для запроса определенной функции супервизора (операционной системы). Когда выполняется SWI, выполняются следующие действия:
R14_svc = address of next instruction after the SWI instruction
SPSR_svc = CPSR
CPSR[4:0] = 0b10011 /* Enter Supervisor mode */
CPSR[5] = 0 /* Execute in ARM state */
/* CPSR[6] is unchanged */
CPSR[7] = 1 /* Disable normal interrupts */
/* CPSR[8] is unchanged */
CPSR[9] = CP15_reg1_EEbit /* Endianness on exception entry */
if high vectors configured then
PC = 0xFFFF0008
else
PC = 0x00000008
Чтобы вернуться после выполнения операции SWI, используйте следующую инструкцию для восстановления ПК (из R14_svc) и CPSR (из SPSR_svc) и возврата к инструкции, следующей за SWI:
MOVS PC,R14
Как вы видете, R14_svc
какой lr
в режиме супервизора устанавливается на SWI
Инструкция по эксплуатации +4. Это так, чтобы нормальный возврат перезапустил следующую инструкцию. Изучая, где lr
вы можете определить, где SWI
произошло. Если пользовательский стек не поврежден, вы можете связать supervisor
укладывать в user
стек с использованием ABI, который компилятор использует через fp
, Если вы сделаете это, то gdb
может дать трассировку стека. Однако в обсуждаемой системе нет SWI
код поддержки.
Вы могли бы также написать gdb
макросы для трассировки стека в этой ситуации. Тем не менее, я надеюсь, что ясно, что gdb
будет трудная задача сделать это в общем.