miércoles, 13 de mayo de 2015

Problemas con la terminal y el depurador (parte 1)

Estuve analizando dos diferencias en el comportamiento de la terminal que usa ZinjaI para ejecutar un programa o proyecto entre la ejecución normal y la ejecución para depuración. La primera es que hasta ahora, la terminal donde se ejecuta la depuración se cierra al finalizar el programa, pase lo que pase, y no le hace caso a las opciones de ejecución que pueden decir que espere una tecla, o que no se cierre si el código de salida no es 0. Esto implica que en la mayoría de los casos, si una ejecución en el depurador finaliza como debe, no llegamos a ver los resultados, a menos que nos tomemos el trabajo de poner algún breakpoint al final del main. Tengo solo la mitad de este problema resuelto.

Vamos por partes. Primero, un resumen de cómo funciona esto de las terminales y el depurador. Este depurador, gdb, es un programa de consola, con interfaz por linea de comandos. Entonces, digamos que usualmente corre en una terminal, y las entradas y salidas de esa terminal le corresponderán a gdb o al programa que se está depurando, de acuerdo a si estamos en una pausa de la depuración (a gdb), o si estamos ejecutando parte del programa (al programa depurado). Pero este modelo, totalmente válido cuando se usa gdb directamente, no es muy útil para usarlo a través de un IDE, ya que el IDE intentará controlar a gdb de forma que el usuario no lo veo, sino que solo vea la ejecución de su programa. Es decir, cuando empiezan a depurar en ZinjaI, ZinjaI lanza un gdb oculto, y le pasa comandos para poner breakpoints, evaluar inspecciones, o lo que sea que hagamos. A este gdb, el usuario de ZinjaI no lo ve. Pero sí ve su programa siendo ejecutado. Entonces, de alguna forma ZinjaI le pide a gdb que esta vez no ejecute el programa en su misma terminal (que estará oculta), sino que utilice otra.

Ejemplo de sesión de depuración directamente en gdb. Entre las lineas "Hola mundo!" y "Adios
Zaskar!" el programa depurado tiene el control de la terminal. Antes y después, lo tiene gdb.

La forma de decirle a gdb que use otra terminal es diferente en Windows respecto a GNU/Linux. En Windows, hay un comando que le damos a gdb que hace que gdb se encargue de crear una nueva consola y usarla para la ejecución. Esto es bueno, y malo. Bueno, porque gdb hace casi todo el trabajo y entonces es muy fácil de usar, pero malo porque si gdb hace algo que no queremos, no podemos evitarlo. Y este algo es cerrar esa consola al finalizar la ejecución. Pasado en limpio: cuando la ejecución del programa depurado termina, gdb automáticamente cierra la consola, y escribe un mensaje (en su propia terminal) indicando la situación. Entonces, para cuando el IDE se entera de que la ejecución terminó, no está a tiempo de hacer nada para esperar una tecla en la consola, porque a esa altura la consola ya no existe. Entonces, al usuario no le queda otra opción que utilizar algún punto de interrupción al final del main, para poder ver la consola antes de que el programa termine.

Un programa C++ no termina al final del main. Observen que falta destruir la variable global.

Pero un programa C++ no siempre termina al final del main. Después de eso, todavía falta, por ejemplo, la destrucción de variables globales y estáticas. Entonces, esa idea del breakpoint en la llave que cierra el main no es del todo fiable. Y otros trucos que aparecen googleando el tema como poner breakpoints en las funciones exit y/o _exit (funciones que no vemos, pero que el compilador y la biblioteca de c usan internamente) tampoco resuelven ese problema. Conclusión, parece que no puedo cambiar nada en ZinjaI para evitarlo, así que por el momento, en Windows este problema no tiene solución automática.

En GNU/Linux la cosa es un poco más complicada. En Windows, un programa de consola abre una ventanita con una consola. En GNU/Linux, no. Hay que usar dos programas: un emulador de terminal (desde ahora "terminal" a secas) que provea la ventanita, y el programa mismo, haciendolo correr dentro de esa ventanita/terminal emulada. El emulador de terminal preferido por ZinjaI es xterm, pero podría usar konsole, gnome-terminal, aterm, o casi cualquier otro. Entonces, si usamos gdb directamente, gdb no va a abrir la ventana de la terminal de ejecución por nosotros. Tenemos que por un lado abrir una terminal, y luego por el otro decirle a gdb que use esa terminal. En la terminal, para que quede abierta, pero no interfiera con las entradas y salidas del programa depurado, debemos ejecutar un programa que básicamente no haga nada, pero tampoco termine: algo así como un loop infinito vacío. Para evitar que el loop consuma recursos de cpu, ponemos un "sleep" o alguna instrucción similar que permita al sistema operativo dormir/suspender ese proceso (similar a "while true; do sleep 1000000; done" en bash). Luego, decirle a gdb que use esa terminal y ejecute el programa a depurar. Esto es lo que hace ZinjaI.

Desde la próxima versión, al menos en GNU/Linux, se esperará una tecla 
antes de cerrar la terminal, aún en depuración, pero solo en GNU/Linux.

En esta nueva situación, hay una ventaja respecto a Windows, y es que cuando la ejecución finalice gdb no va a cerrar la ventana, dado que no es "su" ventana. Es responsabilidad de quien lanzó la terminal (por ejemplo ZinjaI) cerrarla. Entonces, ahora sí puedo hacer algo desde ZinjaI para que cuando gdb libere esa terminal ZinjaI la use para mostrar el mensaje de que la ejecución finalizó, y esperar una tecla. De hecho, esto ya está implementado y funcionando en la versión del repositorio. Por eso digo que la mitad de este primer problema está resuelto. Si alguien sabe algún truco para la mitad que falta será bienvenido.

No hay comentarios:

Publicar un comentario