Пошкоджена рамка стека 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 та знайти відповідь сам, тому я не публікую тут код. Вибачення.


Можливо, ваша програма стрибнула в бур’яни - чи можете ви відновити що-небудь із вказівника стека?
Карл Норум

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 вводиться в ПК і, отже, функція повертається. Отже, якщо стек пошкоджений, ми можемо побачити неправильне значення, чи не так? У цьому випадку можливо коригування покажчика стека призведе до відповідного стеку та допоможе налагодити проблему. Що ти думаєш? pls, дайте мені знати ваші думки. Дякую.
mk ..

1
Що означає фальшивість?
Денні Ло

5
ARM не є x86 - його вказівник стека викликається sp, немає espабо rsp, і його інструкція виклику зберігає зворотну адресу в lrрегістрі, а не в стеці. Отже, для ARM все, що вам дійсно потрібно, щоб скасувати дзвінок set $pc = $lr. Якщо $lrвін недійсний, у вас набагато складніше розмотатися.
Кріс Додд

44

Якщо ситуація досить проста, відповідь Кріса Додда найкраща. Це схоже на те, що він проскочив через покажчик NULL.

Однак, можливо, програма вистрілила в стопу, коліно, шию та око перед тим, як зазнати аварії - перезаписала стек, зіпсувала вказівник кадру та інші лихи. Якщо так, то розгадування хешу, швидше за все, не покаже вам картоплю та м'ясо.

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

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

  • Можливо, встановлення точки відліку налагоджувача або вставлення діагностичних printfна критичні змінні призведе до необхідного Ага!
  • Можливо, зміна умов тесту з різними входами дасть більше розуміння, ніж налагодження.
  • Можливо, друга пара очей змусить вас перевірити свої припущення або зібрати проігноровані докази.
  • Іноді все, що потрібно, - це обідати і подумати про зібрані докази.

Удачі!


13
Якщо друга пара очей недоступна, гумові качки добре зарекомендували себе як альтернативи.
Метт

2
Списання кінця буфера також може це зробити. Він може не вийти з ладу, коли ви списуєте кінець буфера, але коли ви виходите з функції, він гине.
фіат

Можливо, стане в нагоді: GDB: Автоматичне "Далі"
користувач202729

28

Якщо припустити, що покажчик стека дійсний ...

Можливо, неможливо точно знати, звідки відбувається SEGV з backtrace - я думаю, перші два кадри стека повністю перезаписані. 0xbffff284 здається дійсною адресою, але наступні два - ні. Щоб детальніше розглянути стек, ви можете спробувати наступне:

gdb $ x / 32ga $ rsp

або варіант (замініть 32 на інше число). Це дозволить роздрукувати деяку кількість слів (32), починаючи з покажчика стека гігантського (g) розміру, відформатованого у вигляді адрес (a). Введіть "help x" для отримання додаткової інформації про формат.

Інструювання коду з деякими дозорними 'printf' може бути не поганою ідеєю в цьому випадку.


Неймовірно корисно, дякую - у мене був стек, який повернувся лише на три кадри, а потім натиснув "Backtrace stop: попередній кадр ідентичний цьому фрейму (пошкоджений стек?)"; Я раніше щось подібне робив у коді в обробці винятків процесора, але не міг згадати, info symbolяк це зробити в gdb.
лизинг

22
FWIW на 32-бітних пристроях ARM: x/256wa $sp =)
орендуємо

2
@leander Не могли б ви сказати мені, що таке X / 256wa? Мені це потрібно для 64-розрядної зброї. Загалом, це буде корисно, якщо ви зможете пояснити, що це таке.
mk ..

5
На відповідь, 'x' = вивчити місце пам'яті; він виводить ряд слів 'w' = (у даному випадку 256) та інтерпретує їх як 'a' = адреси. Додаткову інформацію можна знайти в посібнику з GDB за адресою sourceware.org/gdb/current/onlinedocs/gdb/Memory.html#Memory .
орендар

7

Подивіться на деякі ваші інші регістри, щоб побачити, чи є в одному з них кешований вказівник стека. Звідти ви зможете отримати стек. Крім того, якщо це вбудовано, досить часто стек визначається за дуже конкретною адресою. Використовуючи це, ви також можете іноді отримати гідний стек. Це все передбачає, що коли ви перескочили на гіперпрограму, ваша програма не пробуджувала всю пам'ять по дорозі ...


3

Якщо це перезапис стека, значення цілком можуть відповідати чомусь розпізнаваному з програми.

Наприклад, я просто знайшов себе, дивлячись на стек

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

і 0x342dце 13357, який виявився ідентифікатором вузла, коли я перехопив журнали програми для цього. Це негайно допомогло звузити сайти-кандидати, на яких може відбуватися перезапис стека.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.