Поиск по сайту:

Создание и отладка файлов дампа 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

Заключение

Знание того, как обращаться с файлами дампа, поможет вам найти и исправить трудновоспроизводимые случайные ошибки в приложении. А если это не ваше приложение, отправка дампа ядра разработчику поможет ему найти и устранить проблему.

Статьи по данной тематике: