Визначте рядок коду, який викликає помилку сегментації?


151

Як можна визначити, де помилка в коді, що викликає помилку сегментації ?

Чи може мій компілятор ( gcc) показати місце несправності в програмі?


5
Жоден gcc / gdb не може. Ви можете дізнатись, де стався сегментарний сегмент, але фактична помилка могла бути зовсім в іншому місці.

Відповіді:


218

GCC не може цього зробити, але GDB ( налагоджувач ) впевнений. Скомпілюйте програму за допомогою -gперемикача, наприклад:

gcc program.c -g

Потім використовуйте gdb:

$ gdb ./a.out
(gdb) run
<segfault happens here>
(gdb) backtrace
<offending code is shown here>

Ось приємний підручник для початку роботи з GDB.

Там, де відбувається segfault, як правило, є лише підказки щодо того, де в коді є "помилка, яка викликає". Дане місце розташування не обов'язково там, де проблема знаходиться.


28
Зауважте, що там, де відбувається segfault, це загальна лише підказка щодо того, де в коді є "помилка, яка викликає". Важлива підказка, але це не обов'язково, де проблема знаходиться.
mpez0

9
Ви також можете скористатися (bt full), щоб отримати більш детальну інформацію.
ant2009


2
Використовувати btяк скорочення для backtrace.
іржа

43

Також можна valgrindспробувати: якщо встановити valgrindта запустити

valgrind --leak-check=full <program>

тоді вона запустить вашу програму і відобразить сліди стека для будь-яких segfault, а також будь-яких недійсних зчитування чи запису пам'яті та витоку пам'яті. Це дійсно досить корисно.


2
+1, Valgrind настільки швидше / простіше використовувати для виявлення помилок пам'яті. На неоптимізованих побудовах із налагоджувальними символами він точно показує , де сталося сегментарне місце та чому.
Tim Post

1
На жаль мій segfault зникає при компіляції з -g -O0 і поєднується з valgrind.
ДжонМудд

2
--leak-check=fullне допоможе налагодити segfault. Це корисно лише для налагодження витоків пам'яті.
ks1322

@JohnMudd У мене segfault з'являється лише близько 1% перевірених вхідних файлів, якщо ви повторите невдалий вхід, він не вийде з ладу. Моя проблема була викликана багатопотоковою читанням. Поки що я не з'ясував рядок коду, що викликає цю проблему. Я зараз використовую спробу, щоб приховати цю проблему. Якщо використовується опція -g, помилка відпадає!
Kemin Zhou

18

Ви також можете використовувати основний дамп, а потім вивчити його за допомогою gdb. Для отримання корисної інформації вам також потрібно скласти -gпрапор.

Щоразу, коли ви отримуєте повідомлення:

 Segmentation fault (core dumped)

основний файл записується у ваш поточний каталог. І ви можете розглянути це за допомогою команди

 gdb your_program core_file

У файлі міститься стан пам'яті при збої програми. Основний дамп може бути корисним під час розгортання вашого програмного забезпечення.

Переконайтесь, що ваша система не встановлює нульовий розмір файлу дамп-файлу. Ви можете встановити її необмежену за допомогою:

ulimit -c unlimited

Але обережно! що основні звалища можуть стати величезними.


Нещодавно я перейшов на arch-linux. Мій поточний каталог не містить основного дамп-файлу. Як я можу генерувати це?
Абхінав

Ви не генеруєте його; Linux робить. Основні звалища зберігаються в різних місцях на різних Linuces - Google навколо. Для Arch Linux читайте цей wiki.archlinux.org/index.php/Core_dump
Mawg каже, що відновити Моніку

7

Існує ряд інструментів, які допомагають налагоджувати помилки сегментації, і я хотів би додати до списку свій улюблений інструмент: Адрес Sanitizers (часто скорочено ASAN) .

Сучасні¹ компілятори поставляються із зручним -fsanitize=addressпрапором, додаючи деякий час компіляції та час запуску, що дозволяє перевірити більше помилок.

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

int main() {
  volatile int *ptr = (int*)0;
  *ptr = 0;
}
$ gcc -g -fsanitize=address main.c
$ ./a.out
AddressSanitizer:DEADLYSIGNAL
=================================================================
==4848==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x5654348db1a0 bp 0x7ffc05e39240 sp 0x7ffc05e39230 T0)
==4848==The signal is caused by a WRITE memory access.
==4848==Hint: address points to the zero page.
    #0 0x5654348db19f in main /tmp/tmp.s3gwjqb8zT/main.c:3
    #1 0x7f0e5a052b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)
    #2 0x5654348db099 in _start (/tmp/tmp.s3gwjqb8zT/a.out+0x1099)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /tmp/tmp.s3gwjqb8zT/main.c:3 in main
==4848==ABORTING

Вихід є дещо складнішим, ніж те, що виводить gdb, але є переваги:

  • Не потрібно відтворювати проблему, щоб отримати слід стека. Досить просто включити прапор під час розробки.

  • ASAN вловлюють набагато більше, ніж просто помилки сегментації. Багато доступних поза межами меж буде зафіксовано, навіть якщо ця область пам'яті була доступною для процесу.


¹ Це Clang 3.1+ та GCC 4.8+ .


Це мені найбільше допомагає. У мене дуже тонка помилка, яка трапляється випадковим чином із частотою близько 1%. Я обробляю велику кількість вхідних файлів за допомогою (16 основних кроків; кожен з них виконується різними C або C ++ двійковими). Один пізній крок призведе до помилки сегментації лише випадковим чином через багатонитковість. Важко налагоджувати. Цей параметр ініціював вихід інформації про налагодження, принаймні, це дало мені вихідну точку для перегляду коду, щоб знайти місце помилки.
Кемін Чжоу

2

Відповідь Лукаса щодо основних відвалів хороша. У своєму .cshrc у мене є:

alias core 'ls -lt core; echo where | gdb -core=core -silent; echo "\n"'

щоб відобразити зворотній слід, ввівши "core". І штамп дати, щоб переконатися, що я переглядаю потрібний файл :(

Додано : Якщо є помилка корупції стека , то зворотний шлях, застосований до основного дампа, часто є сміттям. У цьому випадку запуск програми в gdb може дати кращі результати, відповідно до прийнятої відповіді (якщо припустити, що помилка легко відтворюється). А також остерігайтеся одночасного скидання ядра одночасно; деякі ОС додають PID до імені основного файлу.


4
і не забудьте ulimit -c unlimitedв першу чергу включити основні скиди.
Джеймс Морріс

@James: Правильно. Лукас про це вже згадував. А для тих із нас, хто все ще застряг у csh, використовуйте "limit". І я ніколи не міг прочитати стеки CYGWIN (але я не пробував 2 або 3 роки).
Джозеф Квінсі

2

Усі вищезазначені відповіді правильні та рекомендовані; ця відповідь призначена лише в крайньому випадку, якщо жоден із вищезгаданих підходів не може бути використаний.

Якщо все інше не вдається, ви завжди можете перекомпілювати свою програму з різними тимчасовими вивідками для друку налагодження (наприклад fprintf(stderr, "CHECKPOINT REACHED @ %s:%i\n", __FILE__, __LINE__);), посипаними по всій частині, на яку ви вважаєте, що є відповідною частиною коду. Потім запустіть програму і поспостерігайте за тим, який останній вивідок налагодження був надрукований безпосередньо перед тим, як стався збій - ви знаєте, що ваша програма зайшла так далеко, тому збій, мабуть, стався після цього моменту. Додайте або видаліть налагодження друку, перекомпілюйте та запустіть тест ще раз, поки ви не звузили його до єдиного рядка коду. У цей момент ви можете виправити помилку та видалити всі тимчасові відладки-відбитки.

Це досить виснажливо, але він має перевагу працювати майже де завгодно - єдиний раз, коли це не може бути, якщо ви з якоїсь причини не маєте доступу до stdout або stderr, або якщо помилка, яку ви намагаєтеся виправити, - це гонка -умова, поведінка якої змінюється, коли змінюється терміни програми (оскільки відлагодження друку сповільнить програму та змінить її терміни)

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