Files
printer-driver/docs/09-debugging.md
T
2026-10-02 11:36:59 +02:00

3.6 KiB

Depurar: segfault y gdb

Qué es un segmentation fault

El programa accede a una dirección que no tiene mapeada en su espacio virtual, o la usa sin permiso (por ejemplo, escribe en memoria de solo lectura). La MMU no encuentra la traducción, el procesador avisa al núcleo, y el núcleo envía al proceso la señal SIGSEGV, que por defecto lo mata (ver 03-process-memory.md).

  • La shell lo muestra como Segmentation fault (core dumped).
  • El código de salida es 139 = 128 + 11 (SIGSEGV es la señal 11). Es la misma cuenta que el 137 de SIGKILL en el OOM killer.
  • El caso más típico es escribir o leer a través de un puntero NULL. La página de la dirección 0 nunca se mapea, precisamente para que este error se detecte siempre.

Ojo: no todo acceso indebido da segfault. Si te sales de un bloque pero caes en memoria que sí es del proceso, no pasa nada visible: corrompes datos en silencio. Para eso están Valgrind y AddressSanitizer.

En Go: el mismo fallo da panic: runtime error: invalid memory address or nil pointer dereference, con la traza de la pila. Go captura el SIGSEGV y lo convierte en un panic. En C no hay traza: hay que pedírsela a gdb.

free(): invalid pointer y Aborted

glibc comprueba algunas cosas al hacer free. Si el puntero no es el inicio de un bloque que haya dado malloc, calloc o realloc, imprime free(): invalid pointer y llama a abort().

  • La shell muestra Aborted (core dumped), con código 134 = 128 + 6 (SIGABRT).
  • Causas típicas: hacer free de la dirección de una variable local (&x), de un literal ("hola"), de un puntero al interior de un bloque, o de un bloque ya liberado (double free).

Para depurarlo, mejor Valgrind que gdb. gdb te dice dónde se cae (en el free), pero el error está antes, donde el puntero recibió un valor malo. Valgrind te dice de dónde viene la dirección:

Invalid free() / delete / delete[] / realloc()
   at free
   by escpos_buffer_free (buffer.c:36)
   by main (hello.c:22)
 Address 0x1ffefff544 is on thread 1's stack

"Is on thread 1's stack": la dirección es de la pila, así que nunca la dio malloc. Después busca cada línea que asigna un valor a ese puntero (grep -n "data =" src/*.c) y mira cuál le da una dirección de la pila.

Método general

  1. Lee el mensaje. Valgrind casi siempre dice qué pasó (Invalid write, Invalid free), dónde y de qué tipo es la dirección (pila, N bytes después de un bloque, bloque ya liberado...).
  2. El síntoma (donde se cae) no suele ser la causa. Pregúntate de dónde salió el valor malo.
  3. Busca todas las asignaciones de esa variable y descarta una a una.

gdb: lo mínimo

Hace falta compilar con -g. El Makefile ya lo hace.

gdb ./build/hello
Comando de gdb Qué hace
run (r) Ejecuta el programa. Si se cuelga, gdb se para en la línea exacta
bt (backtrace) Muestra la pila de llamadas: quién llamó a quién hasta llegar aquí
print expr (p) Muestra el valor de una variable o expresión: p buffer->data
break fichero.c:línea (b) Pone un punto de parada
next (n) Ejecuta la línea actual sin entrar en las funciones
step (s) Ejecuta la línea actual entrando en las funciones
continue (c) Sigue hasta el siguiente punto de parada
quit (q) Sale

Al arrancar, Fedora pregunta si quieres descargar símbolos de depuración del sistema (debuginfod). Para nuestro código no hacen falta: responde n.

En Go el equivalente es Delve (dlv debug), con comandos casi iguales.