Как определить, где находится ошибка в коде, который вызывает ошибку сегментации ?
Может ли мой компилятор ( 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>
затем он запустит вашу программу и отобразит трассировки стека для любых ошибок segfaults, а также для любых недопустимых операций чтения или записи памяти и утечек памяти. Это действительно довольно полезно.
--leak-check=full
не поможет отладить сегфо. Это полезно только для устранения утечек памяти.
Вы также можете использовать дамп ядра и затем проверить его с помощью gdb. Для получения полезной информации также необходимо скомпилировать с -g
флагом.
Всякий раз, когда вы получаете сообщение:
Segmentation fault (core dumped)
основной файл записывается в ваш текущий каталог. И вы можете проверить это с помощью команды
gdb your_program core_file
Файл содержит состояние памяти при сбое программы. Дамп ядра может быть полезен при развертывании вашего программного обеспечения.
Убедитесь, что ваша система не устанавливает нулевой размер файла дампа памяти. Вы можете установить его неограниченным с помощью:
ulimit -c unlimited
Осторожнее, хотя! что основные дампы могут стать огромными.
Существует ряд доступных инструментов, которые помогают отлаживать ошибки сегментации, и я хотел бы добавить свой любимый инструмент в список: Address 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"'
для отображения обратного следа, введя «ядро». И штамп с датой, чтобы убедиться, что я смотрю на правильный файл :(.
Добавлено : Если есть ошибка повреждения стека , то обратная трассировка, применяемая к дампу ядра, часто является мусором. В этом случае запуск программы в GDB может дать лучшие результаты в соответствии с принятым ответом (при условии, что ошибка легко воспроизводима). А также остерегайтесь нескольких процессов, сбрасывающих ядро одновременно; некоторые ОС добавляют PID к имени основного файла.
ulimit -c unlimited
сначала включить дамп ядра.
Все вышеперечисленные ответы верны и рекомендуются; этот ответ предназначен только в качестве крайней меры, если ни один из вышеупомянутых подходов не может быть использован.
Если все остальное терпит неудачу, вы всегда можете перекомпилировать вашу программу с различными временными операторами отладочной печати (например fprintf(stderr, "CHECKPOINT REACHED @ %s:%i\n", __FILE__, __LINE__);
), разбросанными по всему, что вы считаете соответствующими частями вашего кода. Затем запустите программу и посмотрите, какой последний отладочный отпечаток был напечатан непосредственно перед тем, как произошел сбой - вы знаете, что ваша программа зашла так далеко, поэтому сбой должен был произойти после этого момента. Добавьте или удалите отладочные отпечатки, перекомпилируйте и снова запустите тест, пока вы не сузите его до одной строки кода. В этот момент вы можете исправить ошибку и удалить все временные отладочные отпечатки.
Это довольно утомительно, но имеет преимущество в том, что работает практически везде - единственное, чего не может быть, - это если у вас по какой-то причине нет доступа к stdout или stderr, или если ошибка, которую вы пытаетесь исправить, является гонкой. условие, поведение которого изменяется при изменении времени программы (поскольку отладочные отпечатки замедляют работу программы и изменяют ее время)