Выберите продукт

Segfault в production на Linux: coredumpctl, gdb и debuginfo — разбор падений без паники

Segfault в проде — это не «рандом», а нехватка артефактов. Показываю пошагово: включить core dump в systemd, проверить core_pattern и systemd-coredump, найти crash через coredumpctl, открыть в gdb, получить thread apply all bt full, подтянуть debuginfo и сопоставить адреса через addr2line.
Segfault в production на Linux: coredumpctl, gdb и debuginfo — разбор падений без паники

Segfault (SIGSEGV) на бою почти всегда выглядит как «случайность»: сервис то живёт, то падает, в логах одни адреса, а systemd успевает перезапустить процесс быстрее, чем вы откроете терминал. Хорошая новость в том, что для первичной диагностики обычно достаточно правильно собрать core dump и один раз снять осмысленный backtrace.

Что такое segfault и почему в production он выглядит «рандомным»

Segfault — сигнал ядра, когда процесс обращается к недоступной области памяти: разыменовал битый указатель, вышел за границы массива, попытался выполнить код по неисполняемому адресу и т.д.

В production это часто воспринимается как «рандом» по нескольким причинам:

  • сборка без символов: в стеке вызовов много ??, нет файлов и строк;
  • ASLR меняет адреса между запусками, из-за чего «адреса в логах» не похожи друг на друга;
  • падение происходит в редком пути: гонки, use-after-free, переполнение, плагины;
  • сервис попадает в crash loop: systemd перезапускает его снова и снова, и вы теряете контекст.

Цель статьи — быстро превратить «segfault в логе» в диагностируемый кейс: core dump → coredumpctlgdbthread apply all bt fulldebuginfo → при необходимости addr2line.

Быстрый чек-лист на инцидент (когда сервис в crash loop)

Когда сервис падает и перезапускается, действуйте по короткой схеме, которая обычно даёт результат за 5–10 минут:

  1. Зафиксируйте время падения, имя юнита, PID/UID из журналов.
  2. Проверьте, что core dumps реально разрешены (лимиты shell и лимиты systemd-юнита).
  3. Найдите запись в coredumpctl и сразу выгрузите core в файл (на случай ротации).
  4. Откройте core в gdb, снимите стек всех потоков, затем полный стек с локальными переменными.
  5. Подтяните debuginfo и повторите backtrace, чтобы получить функции/строки.
  6. Если остались «голые» адреса, сопоставьте их через addr2line для нужного ELF.

Проверка лимитов core dump: ulimit и LimitCORE в systemd

Шаг 1. Убедиться, что core dumps включены: ulimit и лимиты systemd

Самая частая причина «нет core» — их запрещает лимит. Для текущей сессии проверьте:

ulimit -c

Если видите 0, core dumps выключены. Для интерактивного запуска можно временно включить:

ulimit -c unlimited

Но на бою сервисы обычно стартуют через systemd, и ваш ulimit в SSH-сессии на них не влияет. Посмотрите эффективное значение для юнита:

systemctl show -p LimitCORE your-service.service

Если нужно включить core dumps для конкретного сервиса, сделайте drop-in override:

systemctl edit your-service.service

Добавьте:

[Service]
LimitCORE=infinity

Примените и перезапустите:

systemctl daemon-reload
systemctl restart your-service.service

Если сервис падает слишком быстро, сначала имеет смысл увеличить паузу между рестартами (см. ниже про crash loop), чтобы успевать снять артефакты и не забить диски core dump’ами.

Шаг 2. Куда попадает core: core_pattern и systemd-coredump

Маршрут core dump определяет /proc/sys/kernel/core_pattern. Посмотрите текущее значение:

cat /proc/sys/kernel/core_pattern

Есть два типовых сценария:

  • ядро пишет core напрямую в файл (например, core или путь вида /var/coredumps/core.%e.%p);
  • core отправляется через pipe обработчику (значение начинается с |) — чаще всего это systemd-coredump.

На современных дистрибутивах обычно включён systemd-coredump: core хранится в управляемом хранилище и извлекается через coredumpctl.

Если у вас pipe в systemd-coredump, не ищите файл core в рабочем каталоге сервиса: его может не быть. Используйте coredumpctl.

Проверка, что systemd-coredump работает и не режет core

Проверьте состояние обработчика (название может отличаться в зависимости от дистрибутива):

systemctl status systemd-coredump

Если core большие или их много, проверьте настройки в /etc/systemd/coredump.conf и drop-in каталоге /etc/systemd/coredump.conf.d/. На практике чаще всего влияют Storage, ProcessSizeMax, ExternalSizeMax и MaxUse.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Шаг 3. Найти падение через coredumpctl и выгрузить core

Посмотреть последние падения:

coredumpctl list

Отфильтровать по юниту:

coredumpctl list --unit your-service.service

Отфильтровать по имени бинаря:

coredumpctl list your-binary-name

Когда нашли нужную запись (ориентируйтесь по времени, PID, UID), получите подробности:

coredumpctl info PID

Чтобы не зависеть от ротации и лимитов хранения, выгрузите core в файл:

coredumpctl dump PID --output=/root/core.your-service.PID

И отдельно сохраните метаданные (потом это сильно экономит время):

coredumpctl info PID > /root/core.your-service.PID.info.txt

Шаг 4. Открыть core в gdb и снять thread apply all bt full

Самый удобный путь — запуск gdb через coredumpctl: он сам подхватит правильный executable и параметры:

coredumpctl gdb PID

Внутри gdb начните с базового набора:

set pagination off
thread apply all bt
thread apply all bt full
  • thread apply all bt показывает стек по всем потокам (важно для гонок, пулов, фоновых воркеров).
  • thread apply all bt full дополнительно печатает аргументы и локальные переменные, где часто видно «битые» значения.

Если backtrace бедный (много ??, нет файлов/строк), это почти всегда отсутствие символов. Дальше нужен debuginfo.

Шаг 5. Debuginfo: как получить имена функций и номера строк

debuginfo (debug symbols) — отдельные пакеты/артефакты с DWARF-символами для бинарей и библиотек. На бою их часто нет (место, политика, скорость обновлений), поэтому есть два рабочих подхода:

  • временно поставить debuginfo на production-сервер (если политика позволяет);
  • перенести core + бинарь + нужные библиотеки на отдельную debug-машину с теми же версиями пакетов.

Debian/Ubuntu: пакеты *-dbgsym

На Debian/Ubuntu символы обычно в пакетах вида *-dbgsym (иногда *-dbg). Критично, чтобы версии debuginfo точно совпадали с версиями бинаря и библиотек, загруженных в core.

Минимальный набор, который часто нужен для осмысленного стека:

  • ваше приложение (если пакетировано);
  • glibc;
  • libstdc++ (для C++);
  • любые библиотеки, которые фигурируют в backtrace (crypto, ssl, драйверы, плагины).

После установки повторите coredumpctl gdb и thread apply all bt full: разница обычно радикальная.

RHEL/Alma/Rocky/CentOS: debuginfo

В RPM-мире символы обычно в пакетах debuginfo. Старайтесь ставить их точечно: для пакетов, которые реально участвуют в стеке. Так вы быстрее получите результат и не раздуете систему лишними пакетами.

Если приложение собрано вами: храните symbols по build-id

Для собственных C/C++/Rust/Go-бинарей лучше заранее выстроить процесс:

  • в CI сохранять symbol files (DWARF) отдельно от production-артефакта;
  • в production выкатывать «strip»-версию;
  • хранить символы столько же, сколько вы храните релизы и core dumps.

Это превращает «падение в проде» из лотереи в стандартную процедуру.

Анализ core dump в gdb: полный backtrace всех потоков

Шаг 6. Addr2line: когда у вас есть только адреса

Иногда даже с gdb остаются адреса (например, часть стека в stripped-библиотеке без символов или у вас есть только адрес из мониторинга). Тогда помогает addr2line.

Рабочий алгоритм:

  1. Понять, к какому модулю относится адрес (основной ELF или конкретная .so). В gdb помогают info files и info proc mappings.
  2. Взять адрес instruction pointer из фрейма, который выглядит как точка падения (на x86_64 обычно RIP).
  3. Прогнать адрес через addr2line с указанием правильного ELF и наличием символов.

Пример:

addr2line -e /usr/bin/your-binary -f -C 0x0000000000401234
  • -f печатает имя функции;
  • -C деманглит C++ символы;
  • -e указывает бинарь/библиотеку.

Шаг 7. Узнать класс причины по паттернам в backtrace

Один хороший backtrace ещё не всегда даёт корень проблемы, но обычно указывает класс ошибки и направление, куда копать.

Частые сценарии

  • NULL dereference: обращение к адресу около 0x0, а в bt full указатели/аргументы равны нулю.
  • Use-after-free: падение «глубоко» в аллокаторе или при доступе к полям структуры; значения выглядят мусорными, стек может «плавать» между запусками.
  • Stack overflow: очень глубокая рекурсия и повторяющиеся фреймы.
  • ABI mismatch: символы есть, но параметры «невозможные»; часто появляется после обновления библиотек без пересборки модулей.
  • Крэш в сторонней библиотеке: обязательно подтяните debuginfo именно для неё, иначе вы будете слепы на половине стека.

Шаг 8. Crash loop: как остановить лавину и не потерять данные

Crash loop опасен тем, что вы:

  • быстро забиваете лимиты хранения core dumps;
  • заспамливаете journald;
  • создаёте лишнюю нагрузку на CPU/IO;
  • можете усугубить состояние данных, если падение происходит в миграциях/инициализации.

Ограничить рестарты systemd на время расследования

Посмотрите текущее состояние и причины рестартов:

systemctl status your-service.service

Полезные параметры юнита: Restart, RestartSec, StartLimitIntervalSec, StartLimitBurst. На время диагностики иногда разумно увеличить RestartSec, чтобы успевать собирать контекст. Делайте это через drop-in, чтобы легко откатить.

Если у вас воркеры/демоны, которыми управляет systemd, может пригодиться материал про системный подход к управлению процессами: очереди и воркеры под systemd вместо supervisor.

Дисковая гигиена core dumps

Следите за лимитами в systemd-coredump и свободным местом. Отдельно проверьте, не режется ли размер core параметрами ProcessSizeMax и ExternalSizeMax. Обрезанный core может давать «странные» ошибки чтения памяти в gdb и ломать backtrace.

FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Шаг 9. Минимальный пакет артефактов, который стоит сохранять

Чтобы разбор segfault не превращался в археологию, стандартизируйте набор артефактов на инцидент:

  • core dump (файл или стабильный идентификатор в coredumpctl);
  • точная версия бинаря (build-id, хэш, версия пакета);
  • версии ключевых библиотек, которые участвуют в стеке;
  • вывод thread apply all bt full;
  • конфиг лимитов и маршрутизации: LimitCORE, значение core_pattern, настройки systemd-coredump.

Если вы разворачиваете сервисы на VDS, удобно держать отдельную «debug-ноду» с теми же версиями ОС/пакетов: туда можно безопасно переносить core и разбирать падения, не трогая production-пакеты и политики.

Частые проблемы и быстрые ответы

coredumpctl list пустой, но segfault точно был

Проверьте последовательно:

  • лимит core для systemd-юнита: systemctl show -p LimitCORE your-service.service;
  • /proc/sys/kernel/core_pattern (куда вообще пишется core);
  • что хранение не выключено: Storage=none в coredump.conf;
  • лимиты размера: ProcessSizeMax, ExternalSizeMax;
  • не потеряли ли вы падение из-за ротации (поэтому dump в файл лучше делать сразу).

В gdb везде ?? и нет строк

Почти всегда это отсутствие debuginfo или несоответствие версий. Символы должны быть именно для тех версий бинаря и библиотек, которые загружены в core.

Backtrace есть, но выглядит бессмысленно

Чаще всего помогает:

  • снять стек всех потоков: thread apply all bt full;
  • посмотреть регистры и текущий фрейм: info registers, frame 0;
  • проверить признаки повреждения стека (stack smashing);
  • воспроизвести в staging под ASan/UBSan (для C/C++) для точного места ошибки.

Итог

Segfault в production лечится не «магией», а дисциплиной: core dumps включены и не обрезаются, маршрут хранения понятен (core_pattern и systemd-coredump), core быстро находится через coredumpctl, а backtrace становится читаемым после установки debuginfo. Если добавить к этому хранение символов в CI по build-id и шаблон сбора артефактов на инцидент, даже crash loop превращается в задачу с конкретными функциями, строками и проверяемыми гипотезами.

Поделиться статьей

Вам будет интересно

Git dubious ownership и safe.directory в Linux: как чинить в CI/CD и на VDS без дыр в безопасности OpenAI Статья написана AI (GPT 5)

Git dubious ownership и safe.directory в Linux: как чинить в CI/CD и на VDS без дыр в безопасности

Git dubious ownership появляется в CI/CD и на серверах деплоя после смены пользователя, запуска через sudo, Docker volume или shar ...
Linux: Too many links (EMLINK) на ext4/XFS — причины и рабочие обходы OpenAI Статья написана AI (GPT 5)

Linux: Too many links (EMLINK) на ext4/XFS — причины и рабочие обходы

EMLINK «Too many links» появляется внезапно: в CI при деплое, при rsync --link-dest, cp -al или распаковке архивов. Разберём лимит ...
Linux: Cannot fork / Resource temporarily unavailable — ulimit, cgroups v2 и pids.max OpenAI Статья написана AI (GPT 5)

Linux: Cannot fork / Resource temporarily unavailable — ulimit, cgroups v2 и pids.max

Cannot fork: Resource temporarily unavailable (EAGAIN) почти всегда означает исчерпание лимита процессов/потоков: ulimit -u (RLIMI ...