Определить строку кода, которая вызывает ошибку сегментации?

Как определить, где ошибка в нашем коде, которая вызывает ошибку сегментации?

После написания некоторого кода, чтобы определить, где у меня ошибка сегментации, может ли мой компилятор (gcc) показать мне местонахождение ошибки в моей программе?

Ответ 1

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

gcc program.c -g

Тогда используйте GDB:

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

Здесь - хороший учебник, с которого можно начать работу с GDB.

Ответ 2

Кроме того, вы можете попробовать Valgrind: если вы установите Valgrind и запустите valgrind --leak-check = full, то он запустит вашу программу и отобразит трассировки стека для любых segfaults, а также любые недопустимые чтения или записи памяти и утечки памяти. Это действительно полезно.

Ответ 3

Вы также можете использовать дамп ядра, а затем изучить его с помощью gdb. Чтобы получить полезную информацию, вам также нужно скомпилировать флаг -g.

Всякий раз, когда вы получаете сообщение:

 Segmentation fault (core dumped)

основной файл записывается в ваш текущий каталог. И вы можете проверить его с помощью команды

 gdb your_program core_file

Файл содержит состояние памяти при сбое программы. Дамп ядра может быть полезен при развертывании вашего программного обеспечения.

Убедитесь, что ваша система не установила нулевой размер файла дампа ядра. Вы можете установить его без ограничений:

ulimit -c unlimited

Осторожно! что основные свалки могут стать огромными.

Ответ 4

Существует ряд доступных инструментов, которые помогают отлаживать ошибки сегментации, и я хотел бы добавить свой любимый инструмент в список: 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+.

Ответ 5

Ответы Лукаса о дампах ядра хорошие. В моем .cshrc у меня есть:

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

чтобы отобразить обратную линию, введя "ядро". И отметку даты, чтобы убедиться, что я смотрю нужный файл: (.

Добавлено. Если есть ошибка повреждения стека, то обратная трассировка, применяемая к дампу ядра, часто является мусором. В этом случае запуск программы в gdb может дать лучшие результаты в соответствии с принятым ответом (при условии, что ошибка легко воспроизводится). А также остерегайтесь одновременного сбрасывания ядра несколькими процессами; некоторые ОС добавляют PID к имени основного файла.