¿Cómo se determina dónde está el error en el código que causa una falla de segmentación ?
¿Puede mi compilador ( gcc
) mostrar la ubicación de la falla en el programa?
¿Cómo se determina dónde está el error en el código que causa una falla de segmentación ?
¿Puede mi compilador ( gcc
) mostrar la ubicación de la falla en el programa?
Respuestas:
GCC no puede hacer eso, pero GDB (un depurador ) sí puede. Compile su programa usando el -g
interruptor, así:
gcc program.c -g
Luego usa gdb:
$ gdb ./a.out
(gdb) run
<segfault happens here>
(gdb) backtrace
<offending code is shown here>
Aquí hay un buen tutorial para comenzar con GDB.
El lugar donde se produce la falla predeterminada es generalmente solo una pista de dónde está "el error que causa" en el código. La ubicación dada no es necesariamente donde reside el problema.
bt
como una abreviatura de backtrace
.
Además, puede valgrind
intentarlo: si instala valgrind
y ejecuta
valgrind --leak-check=full <program>
luego ejecutará su programa y mostrará los rastros de la pila para cualquier segfault, así como cualquier lectura o escritura de memoria no válida y pérdidas de memoria. Es realmente bastante útil.
--leak-check=full
no ayudará a depurar segfaults. Es útil solo para depurar fugas de memoria.
También puede usar un volcado de núcleo y luego examinarlo con gdb. Para obtener información útil también necesita compilar con la -g
bandera.
Cada vez que recibes el mensaje:
Segmentation fault (core dumped)
se escribe un archivo central en su directorio actual. Y puedes examinarlo con el comando
gdb your_program core_file
El archivo contiene el estado de la memoria cuando el programa se bloqueó. Un volcado de núcleo puede ser útil durante la implementación de su software.
Asegúrese de que su sistema no establezca el tamaño del archivo de volcado del núcleo en cero. Puede establecerlo en ilimitado con:
ulimit -c unlimited
¡Pero cuidado! que los basureros pueden volverse enormes.
Hay varias herramientas disponibles que ayudan a depurar fallas de segmentación y me gustaría agregar mi herramienta favorita a la lista: Desinfectantes de direcciones (a menudo abreviado ASAN) .
Los compiladores modernos vienen con la práctica -fsanitize=address
bandera, agregando algo de tiempo de compilación y tiempo de ejecución, lo que hace más verificación de errores.
De acuerdo con la documentación, estas comprobaciones incluyen la detección de fallas de segmentación de forma predeterminada. La ventaja aquí es que obtiene un seguimiento de la pila similar a la salida de gdb, pero sin ejecutar el programa dentro de un depurador. Un ejemplo:
int main() {
volatile int *ptr = (int*)0;
*ptr = 0;
}
$ gcc -g -fsanitize=address main.c
$ ./a.out
AddressSanitizer:DEADLYSIGNAL
=================================================================
==4848==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x5654348db1a0 bp 0x7ffc05e39240 sp 0x7ffc05e39230 T0)
==4848==The signal is caused by a WRITE memory access.
==4848==Hint: address points to the zero page.
#0 0x5654348db19f in main /tmp/tmp.s3gwjqb8zT/main.c:3
#1 0x7f0e5a052b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)
#2 0x5654348db099 in _start (/tmp/tmp.s3gwjqb8zT/a.out+0x1099)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /tmp/tmp.s3gwjqb8zT/main.c:3 in main
==4848==ABORTING
El resultado es un poco más complicado de lo que generaría gdb, pero hay ventajas:
No es necesario reproducir el problema para recibir un seguimiento de la pila. Simplemente habilitar la bandera durante el desarrollo es suficiente.
Los ASAN detectan mucho más que solo fallas de segmentación. Se capturarán muchos accesos fuera de límites incluso si esa área de memoria fuera accesible para el proceso.
¹ Eso es Clang 3.1+ y GCC 4.8+ .
La respuesta de Lucas sobre los vertederos es buena. En mi .cshrc tengo:
alias core 'ls -lt core; echo where | gdb -core=core -silent; echo "\n"'
para mostrar la traza hacia atrás ingresando 'core'. Y el sello de fecha, para asegurarme de que estoy mirando el archivo correcto :(.
Agregado : si hay un error de corrupción de la pila , la traza inversa aplicada al volcado del núcleo a menudo es basura. En este caso, ejecutar el programa dentro de gdb puede dar mejores resultados, según la respuesta aceptada (suponiendo que la falla sea fácilmente reproducible). Y también tenga cuidado con los procesos múltiples que descargan el núcleo simultáneamente; algunos sistemas operativos agregan el PID al nombre del archivo central.
ulimit -c unlimited
habilitar los volcados de núcleo en primer lugar.
Todas las respuestas anteriores son correctas y recomendadas; esta respuesta está destinada solo como último recurso si no se puede utilizar ninguno de los enfoques antes mencionados.
Si todo lo demás falla, siempre puede recompilar su programa con varias declaraciones temporales de impresión de depuración (por ejemplo fprintf(stderr, "CHECKPOINT REACHED @ %s:%i\n", __FILE__, __LINE__);
) esparcidas a lo largo de lo que cree que son las partes relevantes de su código. Luego ejecute el programa y observe cuál fue la última impresión de depuración impresa justo antes de que ocurriera el bloqueo: sabe que su programa llegó tan lejos, por lo que el bloqueo debe haber sucedido después de ese punto. Agregue o elimine impresiones de depuración, vuelva a compilar y ejecute la prueba nuevamente, hasta que la haya reducido a una sola línea de código. En ese punto, puede corregir el error y eliminar todas las impresiones de depuración temporales.
Es bastante tedioso, pero tiene la ventaja de funcionar en casi cualquier lugar: las únicas veces que podría no serlo es si no tiene acceso a stdout o stderr por alguna razón, o si el error que está tratando de solucionar es una carrera -condición cuyo comportamiento cambia cuando cambia el tiempo del programa (ya que las impresiones de depuración ralentizarán el programa y cambiarán su tiempo)