Як можна визначити, де помилка в коді, що викликає помилку сегментації ?
Чи може мій компілятор ( gcc
) показати місце несправності в програмі?
Як можна визначити, де помилка в коді, що викликає помилку сегментації ?
Чи може мій компілятор ( gcc
) показати місце несправності в програмі?
Відповіді:
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, як правило, є лише підказки щодо того, де в коді є "помилка, яка викликає". Дане місце розташування не обов'язково там, де проблема знаходиться.
bt
як скорочення для backtrace
.
Також можна valgrind
спробувати: якщо встановити valgrind
та запустити
valgrind --leak-check=full <program>
тоді вона запустить вашу програму і відобразить сліди стека для будь-яких segfault, а також будь-яких недійсних зчитування чи запису пам'яті та витоку пам'яті. Це дійсно досить корисно.
--leak-check=full
не допоможе налагодити segfault. Це корисно лише для налагодження витоків пам'яті.
Ви також можете використовувати основний дамп, а потім вивчити його за допомогою gdb. Для отримання корисної інформації вам також потрібно скласти -g
прапор.
Щоразу, коли ви отримуєте повідомлення:
Segmentation fault (core dumped)
основний файл записується у ваш поточний каталог. І ви можете розглянути це за допомогою команди
gdb your_program core_file
У файлі міститься стан пам'яті при збої програми. Основний дамп може бути корисним під час розгортання вашого програмного забезпечення.
Переконайтесь, що ваша система не встановлює нульовий розмір файлу дамп-файлу. Ви можете встановити її необмежену за допомогою:
ulimit -c unlimited
Але обережно! що основні звалища можуть стати величезними.
Існує ряд інструментів, які допомагають налагоджувати помилки сегментації, і я хотів би додати до списку свій улюблений інструмент: Адрес 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+ .
Відповідь Лукаса щодо основних відвалів хороша. У своєму .cshrc у мене є:
alias core 'ls -lt core; echo where | gdb -core=core -silent; echo "\n"'
щоб відобразити зворотній слід, ввівши "core". І штамп дати, щоб переконатися, що я переглядаю потрібний файл :(
Додано : Якщо є помилка корупції стека , то зворотний шлях, застосований до основного дампа, часто є сміттям. У цьому випадку запуск програми в gdb може дати кращі результати, відповідно до прийнятої відповіді (якщо припустити, що помилка легко відтворюється). А також остерігайтеся одночасного скидання ядра одночасно; деякі ОС додають PID до імені основного файлу.
ulimit -c unlimited
в першу чергу включити основні скиди.
Усі вищезазначені відповіді правильні та рекомендовані; ця відповідь призначена лише в крайньому випадку, якщо жоден із вищезгаданих підходів не може бути використаний.
Якщо все інше не вдається, ви завжди можете перекомпілювати свою програму з різними тимчасовими вивідками для друку налагодження (наприклад fprintf(stderr, "CHECKPOINT REACHED @ %s:%i\n", __FILE__, __LINE__);
), посипаними по всій частині, на яку ви вважаєте, що є відповідною частиною коду. Потім запустіть програму і поспостерігайте за тим, який останній вивідок налагодження був надрукований безпосередньо перед тим, як стався збій - ви знаєте, що ваша програма зайшла так далеко, тому збій, мабуть, стався після цього моменту. Додайте або видаліть налагодження друку, перекомпілюйте та запустіть тест ще раз, поки ви не звузили його до єдиного рядка коду. У цей момент ви можете виправити помилку та видалити всі тимчасові відладки-відбитки.
Це досить виснажливо, але він має перевагу працювати майже де завгодно - єдиний раз, коли це не може бути, якщо ви з якоїсь причини не маєте доступу до stdout або stderr, або якщо помилка, яку ви намагаєтеся виправити, - це гонка -умова, поведінка якої змінюється, коли змінюється терміни програми (оскільки відлагодження друку сповільнить програму та змінить її терміни)