86 lines
3.6 KiB
Markdown
86 lines
3.6 KiB
Markdown
# 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](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.
|