59. Crash Dump, Core Dump и анализ падений
Что такое Core Dump и зачем он Go-разработчику
Каждый бэкенд-инженер хотя бы раз в карьере сталкивался с леденящей душу ситуацией: высоконагруженный сервис в продакшене внезапно перестает отвечать, процесс мгновенно и бесследно исчезает из таблицы процессов, а в стандартном журнале stderr зияет абсолютная пустота. Никаких привычных логов, никаких удобных трассировок Go-паники. Процесс испарился, унеся причину катастрофы с собой в небытие.
В подобных инцидентах единственным надежным инструментом расследования (post-mortem debugging) становится Core Dump (дамп памяти процесса) — точный снимок всего виртуального адресного пространства программы в момент ее аварийной гибели. Это своеобразный «черный ящик» самолета: дамп сохраняет полный срез оперативной памяти процесса, регистры всех физических ядер CPU, отображенные библиотеки, открытые файловые дескрипторы и состояние всех системных потоков операционной системы.
Многие разработчики ошибочно полагают, что встроенного механизма паники в Go (panic) вполне достаточно для диагностики любых сбоев. Однако стандартный panic-стек языка Go выводит в консоль информацию только о текущей упавшей горутине. Он бессилен, если сбой вызван:
- Разрушением памяти (memory corruption) в CGo-библиотеке или коде на C/C++.
- Некорректным использованием низкоуровневых операций с пакетом
unsafe(разыменование битых указателей). - Аппаратными прерываниями процессора (
SIGBUS,SIGSEGV). - Сложнейшими гонками данных (Data Race), которые повреждают внутренние управляющие структуры самого Go Runtime (
runtime.mheap, списки горутин).
В продакшене без централизованно настроенного сбора дампов памяти вы теряете до 80% критической информации, необходимой для фундаментального анализа первопричин (Root Cause Analysis).
flowchart TD
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
subgraph CrashScenario ["Причина сбоя"]
direction TB
GoPanic["Штатная Go Panic: runtime ловит ошибку, стек в stderr"]:::error
FatalCrash["Фатальный сбой: CGo, unsafe, SIGSEGV, повреждение рантайма"]:::process
end
subgraph OSReaction ["Реакция ядра Linux"]
direction TB
TrapSignal["Аппаратное исключение CPU -> Сигнал (SIGSEGV/SIGBUS/SIGABRT)"]
KernelCoreCheck{"ulimit -c > 0 && kernel.core_pattern настроен?"}:::warning
DoCoredump["do_coredump(): чтение mm_struct и сериализация памяти"]
NoDump["Процесс уничтожен без сохранения состояния"]:::process
end
subgraph Diagnostics ["Посмертный анализ (Post-Mortem)"]
direction TB
ElfCore["Файл ELF Core Dump на диске"]:::process
DelveGDB["Отладчики: Delve (dlv core) / GDB (runtime-gdb.py)"]
RootCause["Восстановление стеков всех горутин и регистров CPU"]:::system
end
GoPanic -->|"Вывод в stderr"| ExitCode["Завершение с кодом 2"]
FatalCrash --> TrapSignal
TrapSignal --> KernelCoreCheck
KernelCoreCheck -->|"Да"| DoCoredump
KernelCoreCheck -->|"Нет"| NoDump
DoCoredump --> ElfCore
ElfCore --> DelveGDB
DelveGDB --> RootCause
Как ОС создаёт Core Dump (Под капотом)
Когда процесс сталкивается с неисправимой ошибкой адресации памяти или получает сигналы терминации по умолчанию (SIGSEGV, SIGBUS, SIGABRT или SIGQUIT), аппаратный процессор генерирует прерывание, и управление перехватывает ядро Linux.
Важное уточнение: по умолчанию Go-программа core dump не порождает. При panic и при fatal error (в том числе all goroutines are asleep) рантайм сам завершает процесс с кодом 2, и дамп не создаётся, даже если ulimit -c unlimited. Чтобы получить дамп, запустите программу с GOTRACEBACK=crash: после печати трассировки рантайм пошлёт процессу SIGABRT (код возврата 134), и ядро сделает дамп. Дальше всё описанное ниже работает как обычно.
Если для процесса активирована генерация дампа памяти (параметр ulimit -c больше нуля), ядро инициирует процедуру посмертного сохранения данных через внутреннюю функцию do_coredump().
Маршрутизация дампа: sysctl kernel.core_pattern
Ключевым механизмом ядра, определяющим судьбу аварийного снимка, является системная переменная sysctl kernel.core_pattern. Она может принимать два принципиально разных формата:
- Прямая запись в локальный файл:
Например,kernel.core_pattern = /var/crash/core.%e.%p, где%e— имя исполняемого файла, а%p— PID упавшего процесса. В этом случае ядро самостоятельно создает файл на диске и последовательно сбрасывает страницы памяти через слой VFS. - Перенаправление в конвейер (Piped Core Dump):
Если значение начинается с символа конвейера|, ядро запускает указанный системный демон и передает ему поток байтов дампа через стандартный ввод (stdin). В современных дистрибутивах Linux сsystemdиспользуется конвейер вида:
|/usr/lib/systemd/systemd-coredump %p %u %s %t %c %h %E.
Демонsystemd-coredumpсжимает дамп алгоритмом zstd/lz4, регистрирует метаданные в системном журнале и сохраняет сжатый архив в/var/lib/systemd/coredump/.
sequenceDiagram
autonumber
participant CPU as Процессор (MMU)
participant Kernel as Ядро Linux (do_coredump)
participant VFS as VFS / Page Cache
participant Systemd as systemd-coredump / abrt
participant Storage as Дисковое хранилище
CPU->>Kernel: Page Fault / Сигнал SIGSEGV
Kernel->>Kernel: Проверка ulimit -c и прав доступа
Kernel->>Kernel: Заморозка всех тредов процесса
Kernel->>Kernel: Обход mm_struct и связного списка vm_area_struct
alt Настроен конвейер kernel.core_pattern = |...
Kernel->>Systemd: Передача ELF-потока через stdin pipe
Systemd->>Storage: Сжатие и запись в /var/lib/systemd/coredump/
else Запись в локальный файл
Kernel->>VFS: Запись ELF Core Dump (/var/crash/core.%e.%p)
VFS->>Storage: Асинхронный сброс страниц на накопитель
end
Анатомия сохранения памяти в ядре
Внутри функции do_coredump() ядро Linux выполняет филигранную работу:
- Блокировка и обход адресного пространства: Ядро обращается к дескриптору памяти процесса
mm_struct. - Считывание областей памяти: Проходит по древовидной структуре (красно-черному дереву и связному списку)
vm_area_struct(VMA) — структурам, описывающим каждый выделенный виртуальный регион (пользовательский стек, кучу brk/mmap, анонимные страницы, разделяемую память). - Формирование структуры ELF: Ядро формирует стандартный исполняемый файл формата
ELF Core Dump, содержащий сегментыPT_LOADи информационные секцииPT_NOTE.
Важный нюанс производительности и безопасности:
Запись дампа выполняется в контексте пространства ядра. Если процесс занимал десятки гигабайт в оперативной памяти, а диск перегружен или работает по медленной сети (NFS), операция сброса дампа может затянуться на десятки секунд. Все это время физические страницы памяти остаются занятыми, что может спровоцировать системный OOM-killer (OOM) для соседних сервисов, если память сервера исчерпана.
Что внутри Core Dump файла?
Файл дампа памяти представляет собой стандартный бинарный контейнер формата ELF (Executable and Linkable Format) с типом ET_CORE. В отличие от обычных бинарников, в нем отсутствуют секции исходного машинного кода .text (они подгружаются отладчиком из самого бинарного файла сервиса), но присутствуют служебные блоки PT_NOTE и срезы страниц оперативной памяти PT_LOAD.
flowchart TD
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
subgraph ELFStructure ["Структура файла ELF Core Dump"]
direction TB
ELFHeader["ELF Header: Type ET_CORE, Machine x86_64 / ARM64"]:::process
subgraph NoteSection ["Секции заметок (PT_NOTE)"]
PRSTATUS["NT_PRSTATUS: Состояние регистров CPU (RIP, RSP, RAX) для каждого треда"]
PRPSINFO["NT_PRPSINFO: Имя бинарника, аргументы CLI, PID, UID, GID"]:::process
SIGINFO["NT_SIGINFO: Сигнал падения (SIGSEGV), код причины, адрес ошибки fault address"]
FILES["NT_FILE: Отображенные в память файлы (библиотеки, mmap-файлы)"]
end
subgraph MemorySegments ["Сегменты памяти (PT_LOAD)"]
StackSeg["Сегменты стеков системных тредов OS"]:::process
HeapSeg["Сегменты кучи (Heap: страницы аллокатора Go mheap)"]
DataSeg["Глобальные переменные (.data, .bss)"]
end
end
ELFHeader --> NoteSection
ELFHeader --> MemorySegments
Основные информационные блоки PT_NOTE:
NT_PRSTATUS: Содержит моментальный снимок аппаратных регистров процессора (RIP — указатель инструкций, RSP — указатель вершины стека, RAX, RBX и др.) индивидуально для каждого системного потока ОС. Именно отсюда отладчик извлекает адрес места падения.NT_PRPSINFO: Содержит сводную информацию о процессе: имя исполняемого файла, параметры командной строки, состояние зомби/сна, эффективные UID и GID.NT_SIGINFO: Хранит детальную причину крушения: номер сигнала (SIGSEGV,SIGBUS,SIGABRT), код причины (например,SEGV_MAPERR— обращение по немаппированному адресу) и сам сбойный адрес памяти (fault address).NT_FILE: Таблица маппинга адресов виртуальной памяти на физические файлы на диске (для сегментов памяти, разделяемых библиотек и файлов, открытых через системный вызовmmap).- Срезы тредов ОС: Включают все системные потоки ядра. В контексте среды исполнения Go это системные потоки
M(Machine), привязанные к логическим процессорамP(Processor), и их системные стеки.
Go Runtime vs Core Dump
В нормальных условиях среда исполнения Go бережно изолирует программиста от сурового мира системных прерываний. Когда в коде происходит деление на ноль или разыменование нулевого указателя на уровне языка, Go Runtime перехватывает системное исключение, превращает его в штатную панику (panic) и выполняет контролируемое завершение:
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: code=0x1 addr=0x0 pc=0x45a1b2]
goroutine 1 [running]:
main.processData(...)
/app/main.go:42 +0x22
Это поведение обрабатывается внутренним планировщиком: функция sigpanic вызывает runtime.gopanic, раскручивает стек вызвавшей горутины, выводит читаемый отчет в консоль и завершает процесс с кодом 2. Это так называемая fatal error или штатная паника — она не создает Core Dump.
Однако наступают ситуации, когда сам защитный каркас Go Runtime оказывается бессилен:
flowchart TD
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
CrashEvent["Аварийное событие"]:::warning --> CheckType{"Источник сбоя?"}
CheckType -->|"Чистый Go код"| GoPanicFlow["Go Runtime перехватывает сигнал"]
GoPanicFlow --> Sigpanic["Вызов sigpanic() -> runtime.gopanic()"]
Sigpanic --> PrintStderr["Печать стека текущей горутины в stderr"]
PrintStderr --> Exit2["Завершение процесса (exit code 2, без дампа)"]
CheckType -->|"CGo / unsafe / BitFlip"| SevereCorruption["Разрушение памяти ядра рантайма"]
SevereCorruption --> CorruptedHeap["Куча повреждена, mheap/mspan невалидны"]
CorruptedHeap --> RuntimeDeadlock["Runtime не может выделить стек для sigpanic"]
RuntimeDeadlock --> KernelTakesOver["Ядро Linux безусловно перехватывает SIGSEGV/SIGBUS"]
KernelTakesOver --> GenerateCore["Формирование полноценного Core Dump"]
Анализ падений: Практика и инструменты
Имея на руках бинарный файл с отладочной информацией (скомпилированный без усечения символов -w -s) и файл дампа памяти, инженер может приступить к детальному расследованию.
Для анализа дампов процессов Go применяются три основных инструмента:
1. Delve (dlv core) — Нативный выбор для Go
Delve изначально проектировался с глубоким пониманием структур рантайма Go. Он нативно умеет находить структуры G, читать стек горутин из кучи и сопоставлять локальные переменные:
# Запуск анализа дампа: dlv core <путь_к_бинарнику> <путь_к_дампу>
dlv core ./bin/myapp /var/crash/core.myapp.12345
# Внутри интерактивной консоли Delve:
(dlv) goroutines # Показать список ВСЕХ горутин и их статусы
(dlv) goroutine 42 # Переключиться на контекст конкретной горутины
(dlv) stack # Распечатать трассировку вызовов (backtrace)
(dlv) locals # Распечатать значения локальных переменных в кадре
(dlv) registers # Посмотреть регистры процессора на момент падения
2. GDB с плагином Go Runtime
Классический отладчик GNU Debugger (gdb core / gdb -c core) работает на уровне машинного кода процессора. Чтобы GDB научился понимать горутины, необходимо подключить специальный python-скрипт отладки, поставляемый в составе дистрибутива Go:
# Запуск классического отладчика GDB
gdb -c core.myapp.12345 ./bin/myapp
# В консоли GDB загружаем плагин рантайма Go:
(gdb) source /usr/lib/golang/go/src/runtime/runtime-gdb.py
(gdb) info goroutines # Список всех горутин в памяти
(gdb) goroutine 1 bt # Раскрутить бэктрейс для горутины номер 1
(gdb) bt # Машинный стек системного потока (thread backtrace)
3. Быстрая консольная диагностика: pstack и crash
pstack <pid>или извлечениеbtиз дампа: утилитаpstack coreпозволяет моментально распечатать стеки всех физических потоков без запуска интерактивной сессии отладчика.crash: системный отладчик ядра, используемый в ситуациях, когда упал не прикладной сервис, а само ядро Linux (генерация аварийного дампа ядраvmcore).
Ловушки и особенности в продакшене
Эксплуатация механизмов сбора дампов в высоконагруженных распределенных системах сопряжена с рядом серьезных инженерных вызовов:
- Ловушка нулевого размера (Пустые дампы):
В подавляющем большинстве дистрибутивов Linux размер дампа по умолчанию ограничен нулем (ulimit -c 0). Если не переопределить это значение через команду оболочкиulimit -c unlimitedили директивуLimitCORE=infinityв конфигурационном файле systemd, ядро проигнорирует падение, и файл не появится. Также проверьте параметр ядраkernel.core_uses_pid(рекомендуется выставлять в1для добавления PID к имени файла, чтобы дампы нескольких падений не перезаписывали друг друга; параметраvm.core_uses_pidне существует). И не забудьте проGOTRACEBACK=crash: без него Go-программа завершается сама, и дампа не будет. - Гигантские дампы и дисковый коллапс:
Дамп включает все страницы анонимной памяти процесса. Если Go-сервис хранил в памяти in-memory кэш на 32 ГБ, файл дампа займет ровно 32 ГБ дискового пространства! Падение десятка реплик такого сервиса мгновенно забьет дисковый накопитель сервера, спровоцировав каскадный отказ других служб.
Решение: Настраивайте конвейерную компрессию на лету черезkernel.core_patternс вызовом архиватора (|/usr/bin/bzip2 > /var/crash/core.%e.%p.bz2) или используйте менеджерsystemd-coredump, в конфигурации которого (/etc/systemd/coredump.conf) заданы жесткие лимитыMaxUseиKeepFree. - Асинхронный сброс и блокировки ввода-вывода (I/O hang):
Если сервис завис в блокирующем системном вызове ввода-вывода (writeв перегруженный сетевой сокет или медленную сетевую файловую систему NFS), ядро попытается перевести процесс в состояние генерации дампа, но не сможет завершить операцию до ответа от драйвера файловой системы. В результате процесс надолго зависает в состоянии D (Uninterruptible Sleep). - Управление дампами через
coredumpctl:
В современных дистрибутивах Linux с systemd ручной поиск дампов по файловой системе не требуется. Используйте стандартные инструменты:coredumpctl list— вывод таблицы всех недавних падений сервисов с указанием PID, сигналов и времени.coredumpctl dump <pid>— безопасное извлечение и распаковка дампа конкретного процесса в локальный файл для последующего анализа в Delve.coredumpctl debug <pid>— мгновенный запуск gdb на сохраненном дампе в одну команду.
Итог
- Core Dump — это полный слепок виртуального адресного пространства процесса в момент аварии. Он незаменим для post-mortem расследования падений, вызванных сбоями CGo,
unsafe-указателей или разрушением внутренних структур самого Go Runtime. - Архитектура ядра Linux: Генерация дампа выполняется функцией
do_coredump(), которая обходитmm_structи структурыvm_area_struct, формируя файл форматаELF Core Dumpс информационными блокамиPT_NOTE(NT_PRSTATUS,NT_PRPSINFO,NT_SIGINFO). - Go Runtime и дампы: Встроенная паника Go (
panic) защищает процесс от простых ошибок, но бессильна перед повреждением кучи и системными сигналамиSIGSEGV/SIGBUS. Дамп памяти позволяет восстановить полную картину состояния всех горутинG, потоковMи структур аллокатора (mheap,mcentral,mspan). - Инструментарий расследования: Delve (
dlv core) предоставляет нативную поддержку структур данных Go и горутин. Классический GDB (gdb -c core) требует подключения скриптаruntime-gdb.py. - Надежность продакшена: Всегда следите за лимитами
ulimit -c, настраивайте сжатие дампов черезsystemd-coredumpи применяйте утилитыcoredumpctl listиcoredumpctl dump <pid>для оперативной локализации инцидентов.
Мы подробно разобрали, как спасти и расшифровать последние мгновения жизни упавшего процесса. Однако высшее инженерное мастерство заключается в том, чтобы диагностировать проблемы производительности, деградацию задержек и утечки ресурсов задолго до того, как сервис упадет в Core Dump. В следующей лекции мы освоим фундаментальный арсенал профилирования и мониторинга живой Linux-системы: встречайте 60. perf, top, vmstat, iostat и другие инструменты Linux.