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

151

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

Может ли мой компилятор ( gcc) показать местоположение ошибки в программе?

Trilarion
источник
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, это, как правило, только ключ к пониманию того, где «ошибка, которая вызывает», находится в коде. Указанное местоположение не обязательно является проблемой.

nc3b
источник
28
Обратите внимание, что там, где происходит segfault, это всего лишь подсказка, где находится «ошибка, которая вызывает», в коде. Важная подсказка, но это не обязательно, где проблема находится.
mpez0
9
Вы также можете использовать (bt full), чтобы получить больше деталей.
ant2009
1
Я нахожу это полезным: gnu.org/software/gcc/bugs/segfault.html
Вероятность любви
2
Использовать btкак сокращение для backtrace.
rustyx
43

Кроме того, вы можете valgrindпопробовать: если вы устанавливаете valgrindи запускаете

valgrind --leak-check=full <program>

затем он запустит вашу программу и отобразит трассировки стека для любых ошибок segfaults, а также для любых недопустимых операций чтения или записи памяти и утечек памяти. Это действительно довольно полезно.

jwkpiano1
источник
2
+1, Valgrind намного быстрее / проще в использовании для выявления ошибок памяти. В неоптимизированных сборках с отладочными символами он точно сообщает , где произошла ошибка и почему.
Тим Пост
1
К сожалению, мой segfault исчезает при компиляции с -g -O0 и объединяется с valgrind.
JohnMudd
2
--leak-check=fullне поможет отладить сегфо. Это полезно только для устранения утечек памяти.
ks1322
@JohnMudd У меня segfault появляются только около 1% проверенных входных файлов, если вы повторите неудачный ввод, он не потерпит неудачу. Моя проблема была вызвана многопоточностью. До сих пор я не выяснил строку кода, вызывающую эту проблему. Я использую повтор, чтобы скрыть эту проблему на данный момент. Если использовать опцию -g, ошибка исчезнет!
Кемин Чжоу
18

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

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

 Segmentation fault (core dumped)

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

 gdb your_program core_file

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

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

ulimit -c unlimited

Осторожнее, хотя! что основные дампы могут стать огромными.

Лукас
источник
Я недавно перешел на arch-linux. Мой текущий каталог не содержит файл дампа ядра. Как я могу сгенерировать это?
Абхинав
Вы не генерируете это; Linux делает. Основные дампы хранятся в разных местах на разных Линукс - Google вокруг. Для Arch Linux прочитайте этот wiki.archlinux.org/index.php/Core_dump
Mawg говорит восстановить Monica
7

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

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

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

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

для отображения обратного следа, введя «ядро». И штамп с датой, чтобы убедиться, что я смотрю на правильный файл :(.

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

Джозеф Куинси
источник
4
и не забудьте ulimit -c unlimitedсначала включить дамп ядра.
Джеймс Моррис
@James: правильно. Лукас уже упоминал об этом. А для тех из нас, кто все еще застрял в csh, используйте «limit». И я никогда не мог читать стековые дампы CYGWIN (но я не пробовал в течение 2 или 3 лет).
Джозеф Куинси
2

Все вышеперечисленные ответы верны и рекомендуются; этот ответ предназначен только в качестве крайней меры, если ни один из вышеупомянутых подходов не может быть использован.

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

Это довольно утомительно, но имеет преимущество в том, что работает практически везде - единственное, чего не может быть, - это если у вас по какой-то причине нет доступа к stdout или stderr, или если ошибка, которую вы пытаетесь исправить, является гонкой. условие, поведение которого изменяется при изменении времени программы (поскольку отладочные отпечатки замедляют работу программы и изменяют ее время)

Джереми Фризнер
источник