Depuración post-mortem: kdump, crash y drgn
Cuando el kernel ya está muerto, kgdb no sirve: no hay nadie a quien pausar. kexec y kdump capturan la memoria del kernel caído en un vmcore, y la crash utility o drgn hacen la autopsia sobre el cadáver, leyendo la pila de la tarea que entró en panic, el dmesg y las estructuras que la mataron.
kgdb (nivel 6.2) es maravilloso mientras el kernel respira: pausas la CPU, inspeccionas, avanzas. Pero un kernel que entró en panic no respira, y no hay CPU que pausar ni stub que responda. La depuración post-mortem es otra disciplina: no interrogas a un sistema vivo, haces la autopsia de un cadáver. Y como el cadáver es la memoria física entera del kernel caído, primero hay que preservarla antes de que el reinicio la borre. Ese es el trabajo de kdump; leerla, el de crash y drgn.
- Entender por qué kgdb no vale para un kernel ya muerto.
- Ver cómo kexec y kdump capturan la memoria en un vmcore.
- Hacer la autopsia con la crash utility: bt, log, ps y las estructuras.
- Conocer drgn, el depurador programable de kernel de 2026.
kgdb frente al kernel muerto
kgdb mete un stub de gdb dentro del kernel: te conectas por serie o red, y echo g > /proc/sysrq-trigger lo hace entrar en el depurador. Sirve para lo vivo: parar en un breakpoint, mirar una variable, seguir una syscall paso a paso. Pero exige un kernel funcional que ejecute el stub y hable el protocolo. Un panic rompe justamente eso: puede haber ocurrido con las interrupciones desactivadas, con la pila corrompida o dentro del propio manejador de fallos. No hay a quién preguntar.
La solución invierte el problema. En vez de mantener vivo al moribundo, se preserva su cadáver intacto y se arranca un kernel nuevo y sano que haga la copia. Eso es kexec.
kexec y kdump: capturar el cadáver
kexec es un cargador de arranque dentro de Linux: carga un segundo kernel en memoria y salta a él sin pasar por la BIOS ni el firmware. kdump lo usa para lo siguiente: en el arranque se reserva un trozo de RAM con el parámetro crashkernel=, aislado del kernel de producción, y se precarga ahí un kernel de captura. Cuando el kernel principal entra en panic, su última acción útil es crash_kexec(), que salta al kernel de captura ya residente en la memoria reservada. El kernel muerto queda congelado en su propia RAM, sin tocar.
# reservar memoria para el kernel de captura (en la linea de arranque)
crashkernel=512M
# precargar el kernel de captura sobre la region reservada
kexec -p /boot/vmlinuz-7.3.0 \
--initrd=/boot/initramfs-kdump.img \
--append="irqpoll nr_cpus=1 reset_devices"
El kernel de captura arranca en un espacio minúsculo, con una CPU y un initramfs propio, y ve la memoria del kernel muerto como un fichero: /proc/vmcore, un ELF con las regiones físicas y las notas por CPU que guardan los registros del momento del panic. makedumpfile lo lee, descarta lo que no aporta —páginas libres, cachés, memoria de usuario— y lo comprime en un fichero manejable:
# volcar el nucleo muerto, filtrando paginas inutiles y comprimiendo
makedumpfile -d 31 -c /proc/vmcore /var/crash/vmcore
En la práctica nadie invoca makedumpfile a mano: el initramfs de kdump trae un servicio que, al arrancar el kernel de captura, monta el destino, ejecuta makedumpfile con la política configurada y reinicia. La máscara -d merece entenderse bit a bit —1 descarta páginas cero, 2 cachés no privadas, 4 cachés privadas, 8 páginas de usuario, 16 páginas libres—, y 31 es la suma de todas, el volcado más agresivo, que conserva solo la memoria del kernel en uso; de un servidor con cientos de gigas de RAM, el vmcore útil baja a unos pocos. Si sospechas el bug en datos de usuario, bajas la máscara para no tirarlos. pstore con ramoops es la alternativa ligera cuando kdump es demasiado: guarda el último dmesg en una región de RAM persistente que sobrevive al reinicio, sin capturar la memoria entera.
La reserva crashkernel= roba RAM al sistema desde el arranque, y la configuración es delicada: memoria insuficiente y el kernel de captura no arranca; dispositivos que no se reinician bien y se cuelga a mitad del volcado. Un kdump solo cuenta como red de seguridad si lo has disparado a propósito —con echo c > /proc/sysrq-trigger— y has confirmado que produce un vmcore legible. La primera vez que lo necesites de verdad es el peor momento para descubrir que estaba mal configurado.
La autopsia: crash y drgn
Con el vmcore a salvo y el vmlinux con símbolos que le corresponde, empieza la autopsia. La crash utility —construida sobre gdb, con conocimiento incrustado de las estructuras del kernel— es la herramienta clásica:
crash /usr/lib/debug/boot/vmlinux-7.3.0 /var/crash/vmcore
KERNEL: /usr/lib/debug/boot/vmlinux-7.3.0
PANIC: "Kernel panic - not syncing: VFS: Unable to mount root fs"
PID: 1801
COMMAND: "insmod"
crash> bt # pila de la tarea que entro en panic
crash> log # el dmesg completo, incluido el oops
crash> ps # todos los procesos vivos en el instante del panic
crash> bt -a # la pila de la tarea activa en cada CPU
crash> struct task_struct.comm,pid ffff888104a1c000
crash> dis mi_probe # desensamblar la funcion culpable
crash> kmem -s # estado de las caches slab
El bt de la tarea que murió es lo primero que se mira, y se lee como el call trace del oops pero navegable hacia arriba y hacia abajo:
crash> bt
PID: 1801 TASK: ffff888104a1c000 CPU: 3 COMMAND: "insmod"
#0 [ffffc9000123f9f8] __crash_kexec at ffffffff81169a3d
#1 [ffffc9000123fab8] panic at ffffffff81a0f8c2
#2 [ffffc9000123fb38] mi_probe at ffffffffc0a0202a [mi_modulo]
#3 [ffffc9000123fb90] really_probe at ffffffff8188d0db
El flujo mental de la autopsia: bt da la pila de la muerte; log da el contexto que llevó hasta ahí; ps sitúa qué corría en cada núcleo; y con struct, p y rd desciendes a las estructuras concretas —la lista que se corrompió, el lock que quedó tomado, el puntero que era nulo— hasta reconstruir la cadena causal. mod -s carga los símbolos de un módulo para que sus marcos se resuelvan también. crash corre también sobre un sistema vivo (crash vmlinux, leyendo /proc/kcore), útil para husmear un kernel que se porta mal pero no ha caído; y foreach bt vuelca la pila de todas las tareas de golpe, la vista que necesitas cuando el síntoma es un cuelgue por deadlock (nivel 19) y no un panic: ahí buscas dos tareas esperándose en círculo, cada una dormida sobre el lock que la otra retiene.
La alternativa moderna, y el estándar de 2026, es drgn: un depurador programable en Python que lee tanto un kernel vivo (/proc/kcore) como un vmcore, exponiendo las estructuras del kernel como objetos manipulables. Donde crash tiene comandos fijos, drgn tiene un lenguaje entero:
# drgn -c /var/crash/vmcore -s vmlinux
from drgn.helpers.linux import list_for_each_entry, for_each_task
# recorrer todas las tareas y encontrar las que estaban en estado D
for task in for_each_task():
if task.__state == 0x2: # TASK_UNINTERRUPTIBLE
print(task.pid.value_(), task.comm.string_().decode())
drgn convierte la autopsia en scripting: puedes recorrer listas enlazadas, dereferenciar punteros, sumar contadores per-CPU o buscar todos los objetos de una caché sin salir de Python, y reutilizar el análisis como un programa. crash sigue siendo insuperable para la primera mirada rápida; drgn brilla cuando la pregunta es compleja o hay que automatizarla.
Ambos son tiquismiquis con los símbolos: exigen el vmlinux con información de depuración exactamente de la versión que produjo el vmcore —el paquete kernel-debuginfo de la distribución, o el vmlinux de tu propia compilación—. Si no casan, crash se niega a arrancar o da basura. Por eso, en una flota, archivar el debuginfo de cada kernel desplegado no es opcional: sin él, el vmcore que tanto costó capturar es un fichero ilegible.
flowchart LR P[panic del kernel] --> CK[crash_kexec salta al kernel de captura] CK --> VM[proc vmcore expone la RAM muerta] VM --> MK[makedumpfile filtra y comprime] MK --> FILE[vmcore en disco] FILE --> CR[crash: bt log ps struct] FILE --> DR[drgn: autopsia programable] CR --> CAUSE[Causa raiz] DR --> CAUSE style P fill:#f38ba8,color:#11111b style CAUSE fill:#a6e3a1,color:#11111b
La depuración post-mortem encierra una inversión filosófica que vale la pena hacer explícita. Toda la depuración en vivo —gdb sobre QEMU, kgdb, un breakpoint— comparte un supuesto: que el sistema sigue ahí para ser interrogado, que puedes pausarlo y preguntarle. Un panic destruye ese supuesto de raíz, porque el fallo más grave es justamente el que no deja a nadie en pie para responder. La respuesta del kernel es brillante por lo contraintuitiva: en lugar de intentar sobrevivir para depurarse —imposible, el sistema que fallaría es el mismo que debería investigar—, gasta su último instante de vida en preservar su propio cadáver y en resucitar en un cuerpo nuevo y sano que pueda examinarlo desde fuera. El kernel de captura no es el kernel muerto reanimado; es un forense independiente que arranca en la memoria que el muerto tuvo la precaución de reservarle mientras aún vivía. Y una vez que el estado está congelado en un vmcore, la muerte deja de ser un evento y se vuelve un objeto: un ELF que puedes copiar, archivar, abrir semanas después en otra máquina, recorrer con crash o interrogar con drgn tantas veces como quieras, sin prisa y sin la presión de un sistema que se degrada mientras lo miras. Congelar el estado es lo que transforma la depuración de una carrera contra el reloj en un análisis pausado y reproducible. Es la misma idea que atraviesa todo este nivel —hacer determinista y examinable lo que era fugaz y silencioso—, llevada a su conclusión última: hasta la muerte del kernel se puede volver un experimento repetible.
- Configura
crashkernel=y precarga un kernel de captura conkexec -p; confirma concat /proc/cmdlineque la reserva existe. - Dispara un panic controlado con
echo c > /proc/sysrq-triggery verifica que el kernel de captura arranca y produce un vmcore. - Redúcelo con
makedumpfile -d 31 -cy ábrelo concrash vmlinux vmcore. - Ejecuta
bt,logyps, e identifica la tarea que entró en panic y la línea del oops. - Repite el análisis con drgn y escribe un script que recorra
for_each_taskbuscando tareas en estado D.