El programa de notas que se cierra solo
Un depurador es un programa que ejecuta otro paso a paso y enseña su estado. Con gdb encuentras la línea exacta donde un programa en C se rompe, en lugar de adivinarla.
Un programa de notas se cierra con el aviso "Segmentation fault" al calcular la media. Revisar cien líneas a ojo cuesta una tarde, y gdb señala la línea en un minuto.
Al final de esta lección
Nota
Puedo encontrar con un depurador la línea donde se rompe un programa en C.
Palabras clave
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Depurador | un programa que ejecuta otro y deja mirarlo por dentro | gdb |
| Fallo de segmentación | el sistema para un programa que toca memoria que no es suya | |
| Punto de interrupción | una línea donde el depurador detiene el programa | break suma |
| Traza de pila | la lista de funciones activas cuando algo se rompe | bt |
| Señal | un aviso que el sistema envía a un proceso | SIGSEGV |
| Símbolos de depuración | los nombres y líneas que gcc guarda con la opción -g |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un fallo de segmentación es una señal
Cuando un programa lee o escribe memoria que no es suya, el sistema le envía la señal SIGSEGV. El programa acaba y la terminal escribe "Segmentation fault".
El estado de salida de un proceso matado por una señal es 128 más el número de la señal. SIGSEGV es la 11, así que el estado es 139.
El programa de prueba usa un puntero nulo
El programa fallo.c suma tres números de un vector, pero main le pasa NULL en lugar del vector. La línea 8 crea el puntero nulo, la 9 llama a suma, y la 4 intenta leer v[i].
Leer a través de NULL es un acceso a la dirección 0, que nunca pertenece al programa. Compílalo con -g, para que gdb conozca las líneas.
Entra en la carpeta de trabajo
mkdir -p ~/c && cd ~/cSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe fallo.c, con un puntero nulo
printf '#include <stdio.h>\nint suma(int *v, int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total = total + v[i];\n return total;\n}\nint main(void) {\n int *datos = NULL;\n int r = suma(datos, 3);\n printf("%%d\\n", r);\n return 0;\n}\n' > fallo.cSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Compila con símbolos de depuración
gcc -g -O0 -o fallo fallo.cSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecuta fallo y mira el estado de salida
./fallo; echo $?Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
La traza de pila dice quién llamó
Para ejecutar el programa bajo gdb, escribes gdb ./fallo y la orden run. Cuando llega la señal, gdb detiene el programa y no lo mata.
La orden bt muestra la traza de pila: la función donde se rompió y las que la llamaron, cada una con su línea. Con la opción -batch y varias -ex, gdb ejecuta las órdenes y sale solo.
Ejemplo resuelto: leer la traza de fallo
| Marco | Función | Línea | Qué significa |
|---|---|---|---|
| #0 | suma | 4 | aquí se rompió: leyó v[i] |
| #1 | main | 9 | main llamó a suma con datos |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Pide la traza y deja solo las líneas
gdb -batch -ex run -ex bt ./fallo 2>&1 | grep -o 'fallo.c:[0-9]*'Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Cuenta cuántas veces aparece la señal
gdb -batch -ex run ./fallo 2>&1 | grep -c SIGSEGVSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
print muestra el valor de una variable
Detenido el programa, print enseña el valor de una variable. Un puntero nulo se muestra como 0x0.
Con break suma pides que gdb pare al entrar en esa función. frame 1 cambia al marco de main, para ver sus variables. Así encuentras la causa: datos valía NULL desde la línea 8.
Para en suma y mira n y v
gdb -batch -ex 'break suma' -ex run -ex 'print n' -ex 'print v' ./fallo 2>&1 | grep '^\$'Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Sube al marco de main y mira datos
gdb -batch -ex run -ex 'frame 1' -ex 'print datos' ./fallo 2>&1 | grep '^\$'Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Intenta leer lo que apunta v
gdb -batch -ex run -ex 'print *v' ./fallo 2>&1 | grep 'Cannot access'Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Cuenta los marcos de un informe
# Un informe de fallo escrito a mano, como lo imprimiría gdb.
report = [
"Program received signal SIGSEGV",
"address 0x0",
"#0 suma",
"#1 main",
]
frames = 0
for line in report:
if line[0] == "#":
frames = frames + 1
print("marcos:", frames)
print("señal:", report[0])
# Añade la línea "#2 arranque" a la lista y mira cuántos marcos salen.
Un error frecuente
Advertencia
Mucha gente cree que la línea donde se rompe el programa es la línea con el error. Es solo donde se notó. Aquí se rompió en la línea 4, pero el error está en la 8, que creó el puntero nulo. Sube por la traza hasta encontrar el dato mal creado.