martes, 19 de mayo de 2015

Problemas con la terminal y el depurador (parte 2)

Empecé el artículo anterior diciendo que iba a mencionar dos problemas con las terminales y la depuración en ZinjaI, pero como se hizo largo describí solo uno. Pues aquí viene el segundo. Es un problema levemente relacionado, muy molesto, pero generalmente inofensivo. Es el mensaje "&warning: GDB failed to set controlling terminal: Operation not permited" que se muestra al comenzar la depuración en GNU/Linux. Desde la versión 7.0 de gdb, muchos IDEs sufren este mensaje, que asusta al usuario inexperto y genera consultas de lo más variadas. Hay miles de hilos al respecto en foros varios en Internet, pero absolutamente ninguna solución. Yo tampoco pude solucionarlo, pero al menos llegué a entenderlo y me convencí de por qué en general no se puede, y qué consecuencias puede traer. Vengo a documentarlo porque leí miles de respuestas, y la mayoría no están ni remotamente cerca de la verdadera causa, y sus consecuencias.

Como expliqué en el artículo anterior, ZinjaI usa la interfaz de linea de comandos que ofrece gdb para dialogar sin que el usuario lo vea. Básicamente, lanzo el proceso gdb de forma "oculta" y abro tuberías para acceder a sus entradas y salidas. Es lo que haría cualquier IDE o interfaz de depuración. Luego, tengo que crear una terminal usando xterm o similar, y decirle a gdb que mande la ejecución del programa depurado a esa otra terminal. Entonces esa otra terminal no es propiedad de gdb: no la lanzó él mismo, sino que la lanzó ZinjaI y se la "prestó". Por culpa de eso, gdb no puede hacer todo lo que se le antoje con esa otra terminal. Y por eso, avisa con ese warning.

Sesión de depuración básica en GNU/Linux. 
Sale el molesto warning al comenzar la ejecución

Para entender qué pasa por dentro, habría que hablar un poco de cómo se gestionan los procesos en GNU/Linux. Digamos rápidamente que varios procesos se agrupan en grupos de procesos, y en sesiones. Entonces, cada proceso tiene un identificador de proceso (pid), un identificador de grupo (gid) y otro de sesión (sid). Cada grupo y cada sesión tienen un proceso líder. Hasta acá, solo cuestiones de organización (más info aquí). El detalle clave es que por cada sesión puede haber una sola terminal asociada, a la que todos los procesos de la sesión pueden acceder directamente. Casi todos tienen autorización para hacer la mayoría de las cosas que quieran hacer, pero algunas pocas cosas muy particulares solo las puede hacer el primero, el líder de la sesión, y verdadero "dueño" de la terminal. Y aquí "terminal" no es la ventanita, sino el concepto de tty de GNU/Linux. Para diferenciarlo, diré a partir de ahora tty. Es, digamos, el objeto (representado por una especie de archivo en /dev) que gestiona la lógica de la terminal, más allá de que esto se vea en una ventana o no.

Sesión, grupo e id de proceso de cada uno de los involucrados en una 
depuración de un programa de consola, como en la primer captura.

El problema es que gdb quiere hacer ciertas configuraciones en la terminal donde va a ejecutar el programa depurado, que solo puede hacer el líder de la sesión. Pero el proceso del programa depurado, no solo no es líder, sino que en realidad ni siquiera está en la misma sesión. Veamos cómo es esto. Yo lanzo tanto la terminal (xterm por ejemplo), como gdb, desde el mismo proceso (ZinjaI), por lo que todos quedan en (heredan) la misma sesión. Hasta ahí todo bien. Pero cuando gdb lanza el proceso del programa a depurar, lo hace adrede en una nueva sesión. Lo hace así, porque como en una sesión todos comparten la terminal, de otra forma no podría haber terminales diferentes para los comandos de gdb y para el programa depurado. Entonces, necesita hacerlo así, y me tomé el trabajo de analizar esta parte del código fuente de gdb, y veo que lo menciona explícitamente como justificación en sus comentarios (ver captura abajo). Por la misma lógica, xterm lanza el proceso que ejecuta en la terminal (usualmente el shell, en este caso el runner de ZinjaI) también en su propia nueva sesión, y es el verdadero dueño de la terminal que queremos para gdb.

gdb, siendo depurado con gdb... y el universo no se detuvo

Entonces no se puede evitar el lío de las sesiones. Cuando gdb intenta configurar ciertas cosas en la terminal que le ofrezco mediante xterm para ese proceso nuevo, sencillamente no puede porque la terminal no le pertenece completamente. De ahí el "Operation not permitted", resultante de la llamada a la función del sistema operativo ioctl. Busqué esta parte también en el código fuente de gdb, encontré la linea que genera el warning y empecé a seguir las llamadas a funciones desde que ioctl falla hasta que el warning sale por la terminal para ver si había algún if con alguna bandera o configuración que pueda setear para evitar imprimir el warning, pero no encontré nada. Es decir, el problema está, no lo puedo evitar, ya lo sé, y ahora quería al menos que no me repita el mensaje a cada rato. Bien, en esa cadena de llamadas dentro de gdb no hay ninguna configuración que me permita interrumpirla. Así como están las cosas, gdb va a mostrar el mensaje pase lo que pase.

El último intento desesperado por esconderlo se basa la simple idea de limpiar la pantalla justo después de que gdb imprima el mensaje, y justo antes de que la ejecución comience. Pero parece que tampoco puedo separar esos dos momentos, ya que el mensaje no se genera cuando configuro en gdb que tty debe utilizar, sino cuando efectivamente comienza a utilizarla (cuando le pido que ejecute).

Por último, solo resta discutir qué problemas me puede traer esta situación. Para el 99% de los programas, absolutamente ninguno, porque la mayoría no intenta hacer cosas raras con la terminal. Pero hay algunas excepciones. Una que aparece mucho al buscar el problema en Internet es, por ejemplo, cuando se usa la biblioteca ncurses (biblioteca justamente para controlar mejor la terminal y hacer menúes y esas cosas). Para que la biblioteca no tenga problemas, la solución es manual y pasa por definir a pata algunas variables en el entorno del proceso depurado. Otro problema que sí me ha tocado, es el del manejo de las señales. Entre las pocas cosas que gdb no puede configurar como quiere, está el manejo de las señales (por ejemplo, la SIGTSTP que se envía al presionar Ctrl+Z, que uso para trucos como este). Estas señales, digamos que las recibe el líder de sesión, y no el proceso en depuración. El workaround para enviarselas al proceso, es pausar el proceso y hacerlo a través de gdb (en ZinjaI, menú "Depuración"->"Más..."->"Enviar señal..."). O alternativamente, usar el comando "kill" desde otra terminal.

El problema de las señales se puede resolver parcialmente enviándolas desde ZinjaI (a través del propio gdb).

En resumen, como están las cosas ahora no tengo solución real, sino que solo explicaciones detalladas de porqué no tengo esa solución (bah!, excusas). Pero al menos puedo saber qué consecuencias tiene, quedarme tranquilo que para el 99% de los casos estas consecuencias son nulas, y saber qué hacer para sortear el obstáculo en el otro 1%. Peor es nada.

No hay comentarios:

Publicar un comentario