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

113

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

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 и сам найти ответ, поэтому я не публикую здесь код. Извиняюсь.

Сангит Сараванарадж
источник
Вероятно, ваша программа спрыгнула в сорняк - вы можете восстановить что-нибудь из указателя стека?
Карл Норум
1
Еще одна вещь, которую следует учитывать, - правильно ли установлен указатель кадра. Вы строите без оптимизации или передаете флаг вроде -fno-omit-frame-pointer? Кроме того, для повреждения памяти valgrindможет быть более подходящим инструментом, если он вам подходит.
FatalError

Ответы:

155

Эти фиктивные адреса (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

Крис Додд
источник
5
@George: gdb executable corefileоткроет gdb с исполняемым файлом и файлом ядра, после чего вы можете сделать bt(или приведенные выше команды, за которыми следует bt) ...
Крис Додд
2
@mk .. ARM не использует стек для адресов возврата - вместо этого он использует регистр ссылок. Так что обычно такой проблемы нет, а если и есть, то обычно это связано с другим повреждением стека.
Крис Додд
2
Я думаю, что даже в ARM все регистры общего назначения и LR хранятся в стеке до того, как вызываемая функция начнет выполняться. После завершения функции значение LR загружается в ПК и, следовательно, функция возвращается. Итак, если стек поврежден, мы можем увидеть неправильное значение, правильно ли ПК? В этом случае, возможно, корректировка указателя стека приведет к соответствующему стеку и поможет отладить проблему. Что вы думаете? пожалуйста, дайте мне знать ваши мысли. Спасибо.
mk ..
1
Что значит подделка?
Дэнни Ло
5
ARM - это не x86 - его указатель стека вызывается sp, а не espили rsp, а его инструкция вызова сохраняет адрес возврата в lrрегистре, а не в стеке. Итак, для ARM вам действительно нужно отменить вызов set $pc = $lr. Если $lrон недействителен, вам гораздо сложнее расслабиться.
Крис Додд
44

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

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

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

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

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

Удачи!

wallyk
источник
13
Если второй пары глаз нет, то в качестве альтернативы хорошо зарекомендовали себя резиновые уточки.
Мэтт
2
Списание конца буфера тоже может это сделать. Он может не вылететь там, где вы списываете конец буфера, но когда вы выходите из функции, он умирает.
phyatt
28

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

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

gdb $ x / 32ga $ rsp

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

В этом случае неплохой идеей может стать оснащение вашего кода некоторыми контрольными printf.

человек
источник
Невероятно полезно, спасибо - у меня был стек, который вернулся назад только на три кадра, а затем нажал «Отслеживание остановлено: предыдущий кадр идентичен этому кадру (поврежденный стек?)»; Я делал нечто подобное в коде обработчика исключений ЦП раньше, но не мог вспомнить, кроме info symbolтого, как это сделать в gdb.
leander
23
FWIW на 32-битных устройствах ARM: x/256wa $sp =)
leander
2
@leander Подскажите, что такое X / 256wa? Мне он нужен для 64-битной ARM. В общем, будет полезно, если вы объясните, что это такое.
мк ..
5
В ответе 'x' = проверить место в памяти; он распечатывает количество «w» = слов (в данном случае 256) и интерпретирует их как «a» = адреса. Дополнительную информацию можно найти в руководстве GDB по адресу sourceware.org/gdb/current/on Lineocs/gdb/Memory.html#Memory .
leander
7

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

Майкл Дорган
источник
3

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

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

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

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

Крэйг Рингер
источник