GDB поврежденный кадр стека - Как отлаживать?

У меня есть следующая трассировка стека. Можно ли из этого разобрать что-нибудь полезное для отладки?

Program received signal SIGSEGV, Segmentation fault.
0x00000002 in ?? ()
(gdb) bt
#0  0x00000002 in ?? ()
#1  0x00000001 in ?? ()
#2  0xbffff284 in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
(gdb) 

С чего начать смотреть на код, когда мы получим Segmentation fault, а трассировка стека не очень полезна?

ПРИМЕЧАНИЕ: если я опубликую код, то эксперты SO дадут мне ответ. Я хочу воспользоваться руководством SO и найти ответ сам, поэтому я не публикую здесь код. Извиняюсь.

6 ответов

Решение

Эти фиктивные адреса (0x00000002 и т. П.) На самом деле являются значениями ПК, а не значениями SP. Теперь, когда вы получаете этот вид SEGV с поддельным (очень маленьким) адресом ПК, в 99% случаев это происходит из-за ложного указателя на функцию. Обратите внимание, что виртуальные вызовы в C++ реализуются с помощью указателей на функции, поэтому любая проблема с виртуальным вызовом может проявляться аналогичным образом.

Инструкция непрямого вызова просто помещает ПК после вызова в стек, а затем устанавливает для ПК целевое значение (в данном случае фальшивое), поэтому, если это то, что произошло, вы можете легко отменить его, вручную выталкивая ПК из стека., В 32-битном коде x86 вы просто делаете:

(gdb) set $pc = *(void **)$esp
(gdb) set $esp = $esp + 4

С 64-битным кодом x86 вам нужно

(gdb) set $pc = *(void **)$rsp
(gdb) set $rsp = $rsp + 8

Затем вы должны быть в состоянии сделать bt и выяснить, где на самом деле код.

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

Если ситуация довольно проста, ответ Криса Додда - лучший. Похоже, что он перепрыгнул через указатель NULL.

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

Более эффективным решением будет запуск программы под отладчиком и переключение функций до сбоя программы. Как только обнаружена функция сбоя, запустите ее снова, войдите в эту функцию и определите, какая функция вызывает, вызывая сбой. Повторяйте, пока не найдете единственную оскорбительную строку кода. В 75% случаев исправление будет очевидным.

В остальных 25% случаев так называемая оскорбительная строка кода представляет собой красную сельдь. Он будет реагировать на (недопустимые) условия, заданные за много строк до, может быть, за тысячи строк раньше. В этом случае лучший выбранный курс зависит от многих факторов: в основном от вашего понимания кода и опыта работы с ним:

  • Возможно, установка точки наблюдения отладчика или вставка диагностики printfНаходящиеся на критических переменных приведут к необходимым А га!
  • Возможно, изменение условий теста с разными входами даст больше информации, чем отладка.
  • Возможно, вторая пара глаз заставит вас проверить свои предположения или собрать пропущенные доказательства.
  • Иногда все, что нужно, это пойти на ужин и подумать о собранных доказательствах.

Удачи!

Предполагая, что указатель стека действителен...

Может быть невозможно точно знать, где происходит SEGV из обратной трассировки - я думаю, что первые два стековых кадра полностью перезаписаны. 0xbffff284 выглядит как действительный адрес, но следующие два - нет. Для более детального изучения стека вы можете попробовать следующее:

gdb $ x / 32ga $ rsp

или вариант (замените 32 на другой номер). Это выведет некоторое количество слов (32), начиная с указателя стека гигантского (g) размера, отформатированного как адреса (a). Введите "help x" для получения дополнительной информации о формате.

Обработка вашего кода с помощью некоторых дозорных 'printf' не может быть плохой идеей, в этом случае.

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

Если это перезапись стека, значения могут хорошо соответствовать чему-то, что можно узнать из программы.

Например, я просто смотрю на стек

(gdb) bt
#0  0x0000000000000000 in ?? ()
#1  0x000000000000342d in ?? ()
#2  0x0000000000000000 in ?? ()

а также 0x342d 13357, который оказался идентификатором узла, когда я нашел в нем журналы приложений. Это сразу помогло сузить сайты-кандидаты, где могла произойти перезапись стека.

Забавно ... у нас было точно то же самое происходит с водителем в старом приложении C здесь. два верхних указателя значений трассировки стека в шестнадцатеричном формате были байтами данных, считываемых из порта. Я случайно заметил одну, потому что она была мне знакома.

Другие вопросы по тегам