Создание и отладка файлов дампа Linux
Знание того, как обращаться с файлами дампа, поможет вам найти и исправить трудновоспроизводимые ошибки в приложении.
Дамп сбоя, дамп памяти, дамп ядра, дамп системы… все дают один и тот же результат: файл, содержащий состояние памяти приложения в определенный момент времени — обычно при сбое приложения.
Знание того, как обращаться с этими файлами, может помочь вам найти основную причину сбоя. Даже если вы не разработчик, файлы дампа, созданные в вашей системе, могут быть очень полезны (а также доступны) для понимания программного обеспечения.
Это практическая статья. Можете ли вы последовать примеру, клонировав репозиторий примера приложения с помощью:
git clone https://github.com/hANSIc99/core_dump_example.git
Как сигналы связаны с дампами
Сигналы — это своего рода межпроцессное взаимодействие между операционной системой и пользовательскими приложениями. Linux использует сигналы, определенные в стандарте POSIX. В вашей системе вы можете найти стандартные сигналы, определенные в /usr/include/bits/signum-generic.h
. Существует также информативная страница сигналов, если вы хотите получить больше информации об использовании сигналов в своем приложении. Проще говоря, Linux использует сигналы для запуска дальнейших действий в зависимости от того, были ли они ожидаемыми или неожиданными.
Когда вы завершаете работающее приложение, оно обычно получает сигнал SIGTERM
. Поскольку ожидается сигнал выхода такого типа, это действие не приведет к созданию дампа памяти.
Следующие сигналы приведут к созданию файла дампа (источник: библиотека GNU C):
- SIGFPE: ошибочная арифметическая операция
- СИГИЛЛ: Незаконное указание.
- SIGSEGV: неверный доступ к хранилищу.
- SIGBUS: Ошибка шины
- SIGABRT: ошибка, обнаруженная программой и сообщенная при вызове прерывания.
- SIGIOT: В Fedora этот сигнал помечен как устаревший, раньше он запускал
abort()
на PDP-11, а теперь отображается в SIGABRT.
Создание файлов дампа
Перейдите в каталог core_dump_example
, запустите make
и выполните пример с ключом -c1
:
./coredump -c1
Приложение должно выйти в состоянии 4 с ошибкой:
(Стефан Авенведде, CC BY-SA 4.0)
«Abgebrochen (Speicherabzug geschrieben)» примерно переводится как «Ошибка сегментации (сброс ядра)».
Создаст ли он дамп ядра или нет, определяется ограничением ресурсов пользователя, запускающего процесс. Вы можете изменить ограничения ресурсов с помощью команды ulimit
.
Проверьте текущие настройки создания дампа ядра:
ulimit -c
Если он выводит unlimited
, то используется (рекомендуемое) значение по умолчанию. В противном случае исправьте предел с помощью:
ulimit -c unlimited
Чтобы отключить создание дампов ядра:
ulimit -c 0
Число указывает ресурс в килобайтах.
Что такое дампы ядра?
Способ, которым ядро обрабатывает дампы ядра, определен в:
/proc/sys/kernel/core_pattern
Я использую Fedora 31, и в моей системе файл содержит:
/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h
Это показывает, что дампы ядра пересылаются в утилиту systemd-coredump
. Содержимое core_pattern
может сильно различаться в разных версиях дистрибутивов Linux. Когда используется systemd-coredump
, файлы дампа сохраняются в сжатом виде в /var/lib/systemd/coredump
. Вам не нужно напрямую касаться файлов; вместо этого вы можете использовать coredumpctl
. Например:
coredumpctl list
показывает все доступные файлы дампа, сохраненные в вашей системе.
С помощью coredumpctl dump
вы можете получить информацию из последнего сохраненного файла дампа:
[stephan@localhost core_dump_example]$ ./coredump
Application started…
(…….)
Message: Process 4598 (coredump) of user 1000 dumped core.
Stack trace of thread 4598:
#0 0x00007f4bbaf22625 __GI_raise (libc.so.6)
#1 0x00007f4bbaf0b8d9 __GI_abort (libc.so.6)
#2 0x00007f4bbaf664af __libc_message (libc.so.6)
#3 0x00007f4bbaf6da9c malloc_printerr (libc.so.6)
#4 0x00007f4bbaf6f49c _int_free (libc.so.6)
#5 0x000000000040120e n/a (/home/stephan/Dokumente/core_dump_example/coredump)
#6 0x00000000004013b1 n/a (/home/stephan/Dokumente/core_dump_example/coredump)
#7 0x00007f4bbaf0d1a3 __libc_start_main (libc.so.6)
#8 0x000000000040113e n/a (/home/stephan/Dokumente/core_dump_example/coredump)
Refusing to dump core to tty (use shell redirection or specify — output).
Это показывает, что процесс был остановлен SIGABRT
. Трассировка стека в этом представлении не очень подробная, поскольку не включает имена функций. Однако с помощью coredumpctl debug
вы можете просто открыть файл дампа с помощью отладчика (по умолчанию GDB). Введите bt
(сокращение от backtrace), чтобы получить более подробное представление:
Core was generated by `./coredump -c1'.
Program terminated with signal SIGABRT, Aborted.
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
50 return ret;
(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007fc37a9aa8d9 in __GI_abort () at abort.c:79
#2 0x00007fc37aa054af in __libc_message (action=action@entry=do_abort, fmt=fmt@entry=0x7fc37ab14f4b "%s\n") at ../sysdeps/posix/libc_fatal.c:181
#3 0x00007fc37aa0ca9c in malloc_printerr (str=str@entry=0x7fc37ab130e0 "free(): invalid pointer") at malloc.c:5339
#4 0x00007fc37aa0e49c in _int_free (av=<optimized out>, p=<optimized out>, have_lock=0) at malloc.c:4173
#5 0x000000000040120e in freeSomething(void*) ()
#6 0x0000000000401401 in main ()
Адреса памяти: main()
и freeSomething()
довольно малы по сравнению с последующими кадрами. Поскольку общие объекты отображаются в область в конце виртуального адресного пространства, можно предположить, что SIGABRT
был вызван вызовом в общей библиотеке. Адреса памяти общих объектов не являются постоянными между вызовами, поэтому совершенно нормально, когда вы видите меняющиеся адреса между вызовами.
Трассировка стека показывает, что последующие вызовы происходят из malloc.c
, что указывает на то, что что-то с (де)распределением памяти могло пойти не так.
В исходном коде видно (даже без каких-либо знаний C++), что он пытался освободить указатель, который не был возвращен функцией управления памятью. Это приводит к неопределенному поведению и вызывает SIGABRT
:
void freeSomething(void *ptr){
free(ptr);
}
int nTmp = 5;
int *ptrNull = &nTmp;
freeSomething(ptrNull);
Утилита systemd coredump может быть настроена в /etc/systemd/coredump.conf
. Ротацию очистки файла дампа можно настроить в /etc/systemd/system/systemd-tmpfiles-clean.timer
.
Дополнительную информацию о coredumpctl
можно найти на его странице руководства.
Компиляция с отладочными символами
Откройте Makefile
и закомментируйте последнюю часть строки 9. Теперь это должно выглядеть так:
CFLAGS =-Wall -Werror -std=c++11 -g
Переключатель -g
позволяет компилятору создавать отладочную информацию. Запустите приложение, на этот раз с помощью переключателя -c2
:
./coredump -c2
Вы получите исключение с плавающей запятой. Откройте дамп в GDB с помощью:
coredumpctl debug
На этот раз вам указывают непосредственно на строку исходного кода, вызвавшую ошибку:
Reading symbols from /home/stephan/Dokumente/core_dump_example/coredump…
[New LWP 6218]
Core was generated by `./coredump -c2'.
Program terminated with signal SIGFPE, Arithmetic exception.
#0 0x0000000000401233 in zeroDivide () at main.cpp:29
29 nRes = 5 / nDivider;
(gdb)
Введите list
, чтобы получить лучший обзор исходного кода:
(gdb) list
24 int zeroDivide(){
25 int nDivider = 5;
26 int nRes = 0;
27 while(nDivider > 0){
28 nDivider--;
29 nRes = 5 / nDivider;
30 }
31 return nRes;
32 }
Используйте команду info locals
, чтобы получить значения локальных переменных с момента сбоя приложения:
(gdb) info locals
nDivider = 0
nRes = 5
В сочетании с исходным кодом видно, что вы столкнулись с делением на ноль:
nRes = 5 / 0
Заключение
Знание того, как обращаться с файлами дампа, поможет вам найти и исправить трудновоспроизводимые случайные ошибки в приложении. А если это не ваше приложение, отправка дампа ядра разработчику поможет ему найти и устранить проблему.