Core Dump (Дамп ядра) - это файл, который автоматически генерируется ядром Linux после сбоя программы. Этот файл содержит данные о памяти, значениях регистров и стеке вызовов приложения на момент сбоя. Да, обычно появление сообщения о создании Core Dump является не слишком приятным сюрпризом, которых лучше бы было поменьше. Но если уж Core Dump был создан, то лучше, чтобы он содержал максимум полезной информации, которая поможет разработчикам и администраторам разобраться в причинах возникшего сбоя.
В этой статье мы поговорим о том, как правильно настраивать создание дампов ядра.
Что же такое дамп
Итак, как мы уже сказали, дамп памяти - это файл, в который записывается адресное пространство (память) процесса при его нештатном завершении. Дампы ядра могут создаваться по требованию (например, отладчиком) или автоматически при завершении процесса.
Дампы памяти создаются ядром при сбое программы и могут быть переданы вспомогательной программе для дальнейшей обработки. Также, они, как правило не используется обычными пользователями, но дамп может быть передан разработчикам по их запросу, которым будет очень полезно иметь снимок состояния программы на момент сбоя, особенно если сбой трудно воспроизвести.
Для того, чтобы разобраться дампами мы рассмотрим несколько примеров. Для начала мы поговорим о том, как завершить работу программы и принудительно создать дамп ядра. Для этого мы будем использовать команду kill, которая использует сигналы для завершения работы приложения.
В качестве примера давайте используем sleep как команду, которая выполняется достаточно долго, чтобы ее можно было принудительно остановить. Выполним эту команду, а затем отправим сигнал SIGTRAP соответствующему процессу. Напомним, что SIGTRAP это сигнал, посылаемый для информирования отладчика о возникновении интересующего события.
$ sleep 500
[1] 5464
$ kill -s SIGTRAP $(pgrep sleep)
[1]+ Trace/breakpoint trap (core dumped) sleep 500
Мы видим сообщение “core dumped”, которое указывает на успешное создание дампа ядра. Теперь, когда у нас есть нужный дамп, давайте посмотрим, как настраивается сохранение дампа ядра.
Какие дампы бывают
Существует два способа настройки дампа ядра. Один из них заключается в передаче дампа ядра через конвейер, а другой - в его сохранении в файле.
Основным параметром конфигурации является kernel.core_pattern. Это применимо как к файловым, так и к конвейерным дампам ядра. В дополнение к этому параметру конфигурации, файловые дампы имеют ограничение по размеру. Мы можем настроить этот размер с помощью команды ulimit, предназначенной для отображения, распределения и ограничения ресурсов. Для этого нужно выставить необходимое значение в файле /etc/security/limits.conf для параметра core.
Далее мы рассмотрим оба типа конфигурации для сохранения дампов.
Используем конвейер
Давайте посмотрим, как настроить нашу систему для создания дампа ядра через конвейер. Во-первых, нам нужен пример программы для выполнения данной задачи. После этого мы настроим ядро так, чтобы оно предоставляло нашей программе имя приложения в качестве аргумента и дамп ядра.
Для этого мы напишем небольшую программу, которая будет создавать дамп ядра только, если проблемный процесс называется sleep:
#!/usr/bin/python2.7
import sys
# Expect sys.argv to have %e configured in kernel.core_pattern
process_filename = sys.argv[1]
if process_filename == "sleep":
with open("/tmp/sleep_core_dump", "wb") as core_dump:
core_contents = bytearray(sys.stdin.read())
core_dump.write(core_contents)
Файл скрипта мы сохраним это в /tmp/core_dump_example.py и дадим ему разрешение на выполнение.
chmod +x /tmp/core_dump_example.py
Теперь мы хотели бы, чтобы наш скрипт вызывался всякий раз, когда создается дамп ядра. Для этого настроим kernel.core_pattern с помощью sysctl:
sudo sysctl -w kernel.core_pattern="|/tmp/core_dump_example.py %e"
Конвейер указывает на то, что операционная система должна передать содержимое дампа ядра в наш скрипт через стандартный ввод данных. Обратите внимание на %e - это шаблон, который расширяется до имени процесса сбойного приложения.
Теперь давайте снова попробуем создать дамп ядра:
sleep 500 &
[1] 8828
kill -s SIGTRAP $(pgrep sleep)
[1]+ Trace/breakpoint trap (core dumped) sleep 500
Посмотрим общую информацию по файлу, который мы создали, с помощью нашего скрипта на python:
file /tmp/sleep_core_dump
/tmp/sleep_core_dump: ELF 64-bit LSB core file, x86-64, version 1 (SYSV), SVR4-style, from 'sleep 500', real uid: 1000, effective uid: 1000, real gid: 1000, effective gid: 1000, execfn: '/usr/bin/sleep', platform: 'x86_64'
Мы без помощи отладчика и другого специального ПО можем видеть, что программа, которая завершает работу, - это /usr/bin/sleep. Он также показывает нам другую информацию, такую как UID, который запустил этот процесс и прочее.
Сохраняем в файл
Теперь давайте настроим нашу систему на создание файла дампа ядра. Для этого мы присваиваем kernel.core_pattern желаемое имя файла:
sudo sysctl -w kernel.core_pattern="/tmp/%e_core_dump.%p"
Когда приложение sleep завершает работу, мы ожидаем, что в каталоге /tmp появится файл с шаблоном sleep_core_dump.pid. Где %e - это название программы, а %p - PID программы.
Обратите внимание, что вместо абсолютного пути мы могли бы указать имя файла. Это создало бы файл дампа ядра в текущем рабочем каталоге упавшего процесса.
Когда мы делали дамп с помощью конвейера, ограничения на основе ulimit, о которых мы говорили в начале статьи никак не влияли на наш файл. Но когда мы сохраняем в файл, эти ограничения на нас распространяются. Размер дампа ядра определяется в блоках. Давайте выясним, сколько байт приходится на каждый блок:
stat -fc %s .
4096
Используя значение 4096 байт на блок, мы можем установить ограничение в 5 МБ, поскольку мы не ожидаем, что в примерах будут генерироваться дампы ядра размером более 5 МБ. Это может быть рассчитано как
Количество блоков = желаемый предел_блоков / размер_блока,
где желаемый предел_блоков и размер_блока указаны в байтах. 5 МБ эквивалентно 1280 блокам
(5 * 1024 * 1024) / 4096 = 1280
Для дампа ядра по умолчанию установлено жесткое ограничение, равное 0. Чтобы установить ограничения, нам нужно добавить следующие две строки в файл /etc/security/limits.conf:
имя_пользователя hard core 1280
имя_пользователя soft core 1280
Здесь у нас присутствуют два вида ограничений. Жесткие ограничения - это общесистемные ограничения, а мягкие ограничения - пользовательские ограничения. Мягкое ограничение должно быть меньше или равно соответствующего жесткого ограничения. После этого нам потребуется перезагрузка.
Далее проверим ограничение на размер файлов дампа ядра после перезагрузки:
$ ulimit -c
1280
Отлично, это возымело действие. Теперь давайте снова попробуем создать дамп ядра:
$ sleep 500 &
[1] 9183
$ kill -s SIGTRAP $(pgrep sleep)
[1]+ Trace/breakpoint trap (core dumped) sleep 500
$ ls /tmp/*_core_*
-rw------- 1 user user 372K Jun 26 23:31 /tmp/sleep_core_dump.1780
Мы создали файл дампа ядра с нужным шаблоном.
Дампим работающий процесс
Иногда может оказаться полезным сгенерировать дамп ядра для запущенного процесса. Для этой цели можно конечно использовать отладчик GDB, но мы рассмотрим утилиту gcore, которая может записывать дамп ядра запущенного процесса.
Давайте попробуем захватить дамп ядра с помощью утилиты gcore. Мы не будем передавать работающему процессу какие-либо сигналы, а просто запустим утилиту:
sleep 500 &
[1] 3000
sudo gcore -o sleep 3000
0x00007f975eee630e in clock_nanosleep () from /lib/x86_64-linux-gnu/libc.so.6
warning: target file /proc/3000/cmdline contained unexpected null characters
warning: Memory read failed for corefile section, 4096 bytes at 0xffffffffff600000.
Saved corefile sleep.3000
[Inferior 1 (process 3000) detached]
Мы видим, что процесс был запущен с PID равным 3000. После запуска gcore создал файл дампа ядра sleep.3000 и отсоединился. А исходный процесс sleep продолжил работать без изменений.
Заключение
В этой статье мы поговорили о том, как можно делать дампы ядра с помощью конвейера и с помощью сохранения в файл. Также рассмотрели, как можно сделать дамп работающего процесса.
Также хочу пригласить вас на бесплатные вебинары курса Administrator Linux:
Роутер на Linux. Зарегистрироваться.
Запускаем тестовый стенд с Vagrant и Ansible. Зарегистрироваться.
Комментарии (3)
kozlyuk
10.08.2024 02:54Важное отличие записи через конвейер от записи в файл в том, что конвейер запускается в initial namespace, и поэтому работает для дампов из контейнера, а путь к файлу интерпретируется в mount namespace процесса, дамп памяти которого пишется. Для контейнер настройка с файлом позволит получить дамп, только если каталог настроенного пути примонтирован как том контейнера.
IZh
Core dump лучше переводить как дамп памяти процесса. В английском есть «core» и «kernel», оба из которых на русский переводятся как «ядро». Но при этом ядро Linux (Linux kernel) тоже может упасть. И есть способ получить дамп памяти таки ядра ОС при помощи системного вызова kexec и запуска специального образа, сохраняющего образ памяти на диск для последующей отладки. Подробнее о настройке, например, здесь.