Как отследить исключение SIGFPE / Arithmetic - PullRequest
9 голосов
/ 19 июля 2011

У меня есть приложение C ++, скомпилированное для Linux, работающее на процессоре ARM CortexA9, которое аварийно завершает работу с исключением SIGFPE / Arithmetic. Сначала я думал, что это из-за некоторых оптимизаций, введенных флагом gcc -O3 , но затем я построил его в режиме отладки, и он все еще вылетает.

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

    3 raise()  0x402720ac   
    2 __aeabi_uldivmod()  0x400bb0b8    
    1 __divsi3()  0x400b9880

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

Существуют ли какие-либо методы для отслеживания причин таких исключений, когда отладчик не может помочь?

ОБНОВЛЕНИЕ: После обработки шестнадцатеричных чисел, сброса памяти и анализа стеков (спасибо Crashworks) я наткнулся на этот камень в документации по компилятору ARM (хотя я не использую компилятор ARM Ltd. ):

Целочисленные ошибки деления на ноль могут быть перехвачены и идентифицированы повторная реализация соответствующих вспомогательных функций библиотеки C. Поведение по умолчанию при делении на ноль происходит, когда сигнал функция используется, или __rt_raise () или __aeabi_idiv0 () повторно реализованы, __aeabi_idiv0 () называется. В противном случае функция деления возвращает ноль. __aeabi_idiv0 () вызывает SIGFPE с дополнительным аргументом, DIVBYZERO.

Поэтому я установил точку останова на __aeabi_idiv0 (_aeabi_ldiv0) и вуаля !, у меня была полная трассировка стека до того, как я был полностью уничтожен. Спасибо всем за их очень информативные ответы!

Отказ от ответственности: «победивший» ответ был выбран исключительно и субъективно с учетом веса его предложений в моих усилиях по отладке, потому что более одного было информативным и действительно полезным .

Ответы [ 5 ]

9 голосов
/ 19 июля 2011

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

В комментариях я привел несколько слайдов презентации, иллюстрирующих эту технику на PowerPC & mdash; посмотрите на # 73-86 для изучения конкретного случая схожего сбоя в стеке с ошибками. Очевидно, что стековые фреймы вашего ARM будут расположены по-разному, но общий принцип верен.

3 голосов
/ 19 июля 2011

(Используя основную идею Федора Скрынникова, но вместо этого с помощью компилятора)

Скомпилируйте ваш код с помощью -pg. Это вставит вызовы mcount и mcountleave() в каждую функцию. Сделайте не ссылку на библиотеку профилирования GCC, но предоставьте свою собственную. Единственное, что вы хотите сделать в своих mcount и mcountleave(), - это сохранить копию текущего стека, поэтому просто скопируйте около 128 байтов стека в фиксированный буфер. И стек, и буфер будут постоянно в кеше, так что это довольно дешево.

2 голосов
/ 19 июля 2011

Поскольку для повышения исключения он использует метод lift (), я ожидаю, что signal () сможет его перехватить. Разве это не так?

В качестве альтернативы, вы можете установить условную точку останова на __aeabi_uldivmod для прерывания, когда делитель (r1) равен 0.

2 голосов
/ 19 июля 2011

Включить генерацию файлов ядра и открыть файл ядра с помощью отладчика

2 голосов
/ 19 июля 2011

Вы можете использовать специальные средства защиты в функциях, которые могут вызвать исключение.Guard - это простой класс, в конструкторе этого класса вы вводите имя файла и строку ( _ FILE _ , _ LINE _ ) в файл / массив / что угодно.Главное условие - это хранилище должно быть одинаковым для всех экземпляров этого класса (вид стека).В деструкторе вы удалите эту строку.Чтобы это работало, вам нужно поместить создание этой защиты в первую строку каждой функции и создавать ее только в стеке.Когда вы выйдете из текущего блока, будет вызван деконструктор.Таким образом, в момент вашего исключения из этого импровизированного стека вызовов вы узнаете, какая функция вызывает проблему.Конечно, вы можете поставить создание этого класса в условие отладки

...