Para depurar mis programas, uso principalmente los siguientes métodos:
Use printf (o equivalente en otros idiomas) para verificar el valor de una variable después de una declaración en particular o para verificar si el programa ingresa una declaración condicional o un bucle.
Use relojes / puntos de interrupción cuando use IDEs.
Soy capaz de resolver los problemas usando los métodos anteriores. Pero he notado que hay dos tipos de compilaciones: depuración y lanzamiento. Por el nombre, puedo entender que la compilación de depuración sería útil para la depuración. También he leído que la compilación de depuración almacena algo llamado información de tabla de símbolos y otras cosas.
¿Cómo puedo usar la compilación de depuración para la depuración? ¿Hay alguna otra técnica de depuración que deba aprender?
Respuestas:
Aprende tu depurador
Es realmente útil familiarizarse con el depurador, ya sea basado en texto, IDE completo o alguna combinación de los mismos. No das muchos detalles, así que describiré el caso general:
1) puntos de interrupción
Además de detenerse en una línea de código, muchos depuradores le permiten especificar que se rompa cuando surge una condición (por ejemplo, "x> 5"), después de varias pasadas a través del código o cuando alguna memoria cambia el valor. Esto es muy útil para comprender cómo su código entra en un mal estado, por ejemplo, observar cuándo un puntero se vuelve nulo en lugar de detectar el bloqueo cuando se desreferencia.
2) Pasando por el código
Puede ingresar a las funciones, línea por línea a lo largo del código, saltar sobre las líneas ('establecer la siguiente declaración') y luego 'subir' fuera de las funciones. Es una forma realmente poderosa de seguir la ejecución de su código para verificar que hace lo que cree que hace :-)
3) Evaluar expresiones
Por lo tanto, puede colocar variables en una lista / ventana de Observación y ver su cambio de valor cuando alcanza un punto de interrupción o atraviesa el código, pero también puede hacer evaluaciones de expresiones complejas, por ejemplo, se evaluará "x + y / 5". Algunos depuradores también le permiten poner llamadas a funciones en las listas de vigilancia. Puede hacer cosas como "time ()", "MyFunction (...)", "time ()" y obtener la temporización del tiempo que tardó su función.
4) Excepciones y manejo de señales
Entonces, si su idioma admite excepciones y / o señales, generalmente puede configurar el depurador para saber cómo reaccionar ante esto. Algunos depuradores le permiten entrar en el código en el punto donde la excepción está por suceder, en lugar de después de que no se haya detectado. Esto es útil para rastrear problemas extraños como errores de "Archivo no encontrado" porque el programa se ejecuta como una cuenta de usuario diferente.
5) Adjuntar a un proceso / núcleo
Entonces, a veces tiene que usar el depurador para saltar a un proceso existente que está yendo mal. Si tiene el código fuente cerca y los símbolos de depuración están intactos, puede sumergirse como si hubiera comenzado en el depurador en primer lugar. Esto también es similar para los volcados de núcleo, excepto que generalmente no puede continuar la depuración en esos (el proceso ya ha muerto).
Configuración de compilación
Hay varias variaciones de compilación que puede realizar activando o desactivando funciones como símbolos de depuración, optimizaciones y otros indicadores del compilador:
1) Depuración
Tradicionalmente, esta es una compilación simple sin características especiales, lo que hace que sea fácil de depurar y predecible. Varía un poco según la plataforma, pero puede haber algo de margen adicional, por ejemplo, asignaciones y tamaños de búfer para garantizar la fiabilidad. Por lo general, estará presente un símbolo de compilador como DEBUG o Conditional ("Debug") para que se introduzca el código específico de depuración. Esta es a menudo la compilación que se envía, con símbolos de nivel de función intactos, especialmente si la confiabilidad y / o la repetibilidad son preocupación.
2) Lanzamiento / construcción optimizada
La habilitación de las optimizaciones del compilador permite que algunas instalaciones de generación de código de bajo nivel en el compilador hagan un código más rápido o más pequeño basado en suposiciones sobre su código. Los aumentos de velocidad posibles son irrelevantes si la elección de su algoritmo es deficiente, pero para los cálculos intensivos esto puede marcar una gran diferencia a través de la eliminación de subexpresión común y el desenrollado de bucle, etc. A veces, las suposiciones hechas por el optimizador son incompatibles con su código, por lo que tiene que ser reducido una muesca. Los errores del compilador en el código optimizado también han sido un problema en el pasado.
3) Construcción instrumentada / perfilada
Su código está construido con un código de instrumentación específico para medir la cantidad de veces que se llama a una función y cuánto tiempo se pasa en esa función. Este código generado por el compilador se escribe al final del proceso para su análisis. A veces es más fácil usar una herramienta de software especializada para esto, ver más abajo. Este tipo de compilación nunca se envía.
4) Construcción segura / comprobada
Todas las 'válvulas de seguridad' se habilitan mediante símbolos de preprocesador o configuraciones del compilador. Por ejemplo, los parámetros de la función de verificación de macros ASSERT, los iteradores verifican las colecciones no modificadas, los canarios se colocan en la pila para detectar la corrupción, las asignaciones del montón se llenan con valores centinela (0xdeadbeef es memorable) para detectar la corrupción del montón. Para los clientes que tienen problemas persistentes que solo pueden reproducirse en su sitio, es algo útil.
5) Construcción de características
Si tiene diferentes clientes que tienen diferentes requisitos de su producto de software, es común hacer una compilación para cada cliente que ejercite las diferentes partes durante la prueba. Por ejemplo, un cliente quiere la funcionalidad sin conexión y otro quiere solo en línea. Es importante probar en ambos sentidos si el código se construye de manera diferente.
Registro y rastreo
Así que hay que escribir algunas declaraciones útiles para printf () y luego hay que escribir información de rastreo integral y estructurada en los archivos de datos a medida que avanza. Esta información se puede extraer para comprender el comportamiento / características de tiempo de ejecución de su software. Si su código no falla, o si toma algún tiempo reprobarlo, es útil tener una imagen de, por ejemplo, una lista de subprocesos, sus transiciones de estado, asignaciones de memoria, tamaños de grupo, memoria libre, número de identificadores de archivos, etc. realmente depende del tamaño, la complejidad y los requisitos de rendimiento de su aplicación, pero como ejemplo, los desarrolladores de juegos quieren asegurarse de que no haya "picos" en el uso de CPU o memoria mientras un juego está en progreso, ya que eso probablemente afectará la velocidad de fotogramas. El sistema mantiene parte de esta información, algunas las bibliotecas y el resto el código.
Otras herramientas
No siempre es necesario crear una compilación diferente para cubrir estos escenarios: se pueden elegir algunos aspectos en tiempo de ejecución a través de la configuración del proceso (trucos del Registro de Windows), haciendo que las bibliotecas alternativas estén disponibles con mayor prioridad que las bibliotecas estándar, por ejemplo, en su ruta del cargador o utilizando un software ICE o un depurador especializado para sondear su software en busca de características de tiempo de ejecución (por ejemplo, Intel v-Tune). Algunos de estos cuestan mucho dinero, algunos son herramientas de código libre, Xtrace.
fuente
Una compilación de depuración se crea con símbolos de depuración en el ejecutable. Básicamente, lo que eso significa es que cada línea de código fuente está vinculada al código de máquina que se generó a partir de él, y se mantienen todos los nombres de variables y funciones. En GCC esto se hace con la
-g
bandera cuando compila archivos fuente. Otra cosa que a menudo se hace es desactivar la optimización, esto se debe a que el compilador hace algunos trucos geniales que hacen que su programa vaya más rápido, pero hace que la depuración sea imposible. En GCC esto se hace con-O0
.La herramienta más útil que he usado para depurar es gdb . Es una versión de texto de los puntos de interrupción IDE que mencionó. Sin embargo, puede hacer mucho más con gdb que con un IDE. De hecho, algunos IDE son solo una envoltura alrededor de gdb, pero algunas características se pierden. Puede ver ubicaciones de memoria, imprimir ensamblajes, imprimir cuadros de pila, cambiar el manejo de la señal y mucho más. Si se toma en serio la depuración, aprendería gdb o algún programa de depuración basado en texto equivalente.
Otra cosa que a menudo encuentro útil es valgrind . Encuentra pérdidas de memoria y realiza un seguimiento de si la memoria se inicializa o no. Lo usa con su compilación de depuración, porque luego obtiene números de línea donde suceden cosas interesantes.
fuente
gdb
es programable, IDEs normalmente no lo son. No necesito ningún "poder" en la punta de mis dedos cuando una computadora puede hacer todo el trabajo por mí mientras estoy tomando mi té.bt
engdb
) en la mayoría de los casos le proporcionaría información más que suficiente para una hipótesis 0 (si este no es el caso, su código está, muy por debajo del umbral de calidad inferior al estándar) . He estado usando depuradores desde VMS, probé todos los sabores posibles, incluidas algunas bestias exóticas extremas como los depuradores de tiempo, pero hasta ahora no pude encontrar ninguno realmente útil. Estás probando tus suposiciones de forma interactiva y luego estás perdiendo el tiempo. Estoy probando mis suposiciones en lote (podrían ser muchas en paralelo), rápidamente y con muy poco esfuerzo.Hay una técnica de depuración extremadamente poderosa, que aún no se menciona en las otras respuestas. Está disponible de forma gratuita para algunos entornos de desarrollo, o se puede agregar con relativamente poco esfuerzo a casi cualquier otra cosa.
Esto es un REPL incorporado, que permite conectarse en cualquier momento a través de un socket, ejecutarse en un hilo dedicado y puede usar todas las formas posibles de reflexión para el código en ejecución, modificando o reemplazando completamente el código en ejecución, agregando cosas nuevas, en ejecución funciones, etc.
Lo tendrá listo para usar si codifica, por ejemplo, Common Lisp, Smalltalk, Python, Ruby, etc. Es posible integrar un intérprete liviano (por ejemplo, Lua, Guile, JavaScript o Python) en una aplicación nativa . Para entornos basados en JVM o .NET, hay muchos compiladores e intérpretes incorporables disponibles, y existe una reflexión bastante poderosa de forma gratuita.
Este enfoque es mucho más eficiente que los depuradores interactivos / iterativos (como gdb, visual studio debugger, etc.), y para obtener los mejores resultados debe usarse junto con una instalación de registro adecuada y aserciones correctamente colocadas.
fuente
Viaweb
historia: se han beneficiado mucho de este enfoque, al tener la capacidad de depurar el sistema de producción en ejecución.La herramienta de depuración más importante que he usado es el volcado de memoria post-mortem (volcado de núcleo en Linux, dmp de usuario en Windows).
Es un tema realmente complejo, así que aquí hay un enlace :
Básicamente (en Windows, la plataforma con la que tengo más experiencia en la depuración post mortem), construye sus aplicaciones con símbolos que se guardan en un archivo separado (un archivo .pdb). Los mantiene a salvo (para que nadie pueda realizar ingeniería inversa de su código fácilmente) y espere un bloqueo. Cuando lo hace (y tiene DrWatson o similar ejecutándose para capturar el bloqueo y generar el archivo de volcado), carga el archivo .dmp en WinDbg (depurador de Windows) junto con los símbolos (y opcionalmente, una ruta al código fuente) y le mostrará mucha información como la pila de llamadas, registros, variables, valores de memoria, etc. Es hermoso cuando funciona.
Para una compilación de depuración, todo esto está configurado para suceder automáticamente. Debe habilitar los símbolos al generar la versión. Las compilaciones de depuración también agregan otras cosas como protectores de memoria (que activan excepciones si intentas escribir en ellas, esto muestra desbordamientos de búfer o corrupción de memoria muy fácilmente; o afirma cosas que no son del todo correctas). En general, aunque las compilaciones de depuración son para que los desarrolladores ejecuten y prueben su código antes de pasarlo.
Ahora esto es para la depuración de código nativo, el código .NET es un PiTA para cosas post-mortem como esta, pero a veces puede obtener cosas de excepción .NET cargando sos .
Todas las cosas complejas, pero no es tan malo. Sin embargo, no esperes una buena GUI puntiaguda, esta es la belleza de la línea de comando.
fuente
También debe usar cualquier tipo de declaración de afirmación que se ofrezca.
También debe escribir pruebas unitarias.
Perfecto. Sabes todo lo que necesitas saber.
No. No es que debas aprender. Puede obtener más información si cree que ayudará. Pero no necesitas nada más de lo que tienes.
Durante los últimos 30 años, he usado un depurador solo unas pocas veces (quizás tres o cuatro). Y luego, solo lo he usado para leer volcados de memoria post-mortem para encontrar la llamada a la función que falló.
El uso del depurador no es una habilidad esencial . La declaración impresa es suficiente.
fuente
Aquí hay una lista rápida de técnicas:
También puede implementar comportamientos personalizados con herramientas de estilo AOP, así como recorrer un largo camino con una buena herramienta de análisis estático.
fuente
Aprenda todo sobre su herramienta de depuración.
A menudo ocultan características realmente poderosas que pueden ayudarlo a comprender mejor lo que está sucediendo. (en particular, el depurador C ++ de Visual Studio)
fuente