¿Determinar la línea de código que causa una falla de segmentación?

151

¿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?

Trilarion
fuente
55
No gcc / gdb no puede. Puede averiguar dónde se produjo la falla predeterminada, pero el error real podría estar en una ubicación totalmente diferente.

Respuestas:

218

GCC no puede hacer eso, pero GDB (un depurador ) sí puede. Compile su programa usando el -ginterruptor, 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.

nc3b
fuente
28
Tenga en cuenta que el lugar donde se produce la falla por defecto es generalmente solo una pista de dónde está "el error que causa" en el código. Una pista importante, pero no es necesariamente donde reside el problema.
mpez0
9
También puede usar (bt full) para obtener más detalles.
ant2009
1
Esto me parece útil: gnu.org/software/gcc/bugs/segfault.html
Loves Probability
2
Úselo btcomo una abreviatura de backtrace.
rustyx
43

Además, puede valgrindintentarlo: si instala valgrindy 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.

jwkpiano1
fuente
2
+1, Valgrind es mucho más rápido / fácil de usar para detectar errores de memoria. En las compilaciones no optimizadas con símbolos de depuración, le dice exactamente dónde ocurrió una falla de seguridad y por qué.
Tim Post
1
Lamentablemente, mi segfault desaparece al compilar con -g -O0 y combinar con valgrind.
JohnMudd
2
--leak-check=fullno ayudará a depurar segfaults. Es útil solo para depurar fugas de memoria.
ks1322
@JohnMudd Tengo una segfault que solo aparece aproximadamente el 1% de los archivos de entrada probados, si repite la entrada fallida, no fallará. Mi problema fue causado por multihilo. Hasta ahora no he descubierto la línea de código que causa este problema. Estoy usando reintentar para ocultar este problema por ahora. Si usa la opción -g, la falla desaparece.
Kemin Zhou
18

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 -gbandera.

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.

Lucas
fuente
Cambié a arch-linux recientemente. Mi directorio actual no contiene el archivo de volcado del núcleo. ¿Cómo puedo generarlo?
Abhinav el
No lo generas; Linux lo hace. Los volcados de núcleo se almacenan en diferentes ubicaciones en diferentes Linuces: Google alrededor. Para Arch Linux, lea este wiki.archlinux.org/index.php/Core_dump
Mawg dice que reinstale a Monica el
7

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=addressbandera, 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+ .

asynts
fuente
Esto es de gran ayuda para mí. Tengo un error muy sutil que ocurre al azar con una frecuencia de aproximadamente 1%. Proceso gran cantidad de archivos de entrada con (16 pasos principales; cada uno realizado por un binario C o C ++ diferente). Un paso posterior desencadenará una falla de segmentación solo aleatoriamente debido a subprocesos múltiples. Es difícil de depurar. Esta opción activó la salida de información de depuración, al menos me dio un punto de inicio para la revisión del código para encontrar la ubicación del error.
Kemin Zhou
2

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.

Joseph Quinsey
fuente
44
y no olvide ulimit -c unlimitedhabilitar los volcados de núcleo en primer lugar.
James Morris
@ James: Correcto. Lucas ya mencionó esto. Y para aquellos de nosotros que todavía estamos atrapados en el csh, use 'limit'. Y nunca he podido leer los stackdumps de CYGWIN (pero no lo he intentado durante 2 o 3 años).
Joseph Quinsey
2

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)

Jeremy Friesner
fuente