A mucha gente le gusta personalizar su sistema de escritorio al máximo posible. Por ello, Microsoft acostumbra a incorporar en sus sistemas operativos bastantes menús y cuadros de diálogo capaces de personalizar muchos de los detalles de funcionamiento de Windows. Sin embargo, hay ciertos detalles que no son modificables desde la interfaz gráfica, solamente desde el Registro; siempre siguiendo los pasos de algún artículo de la documentación oficial de Microsoft (KB, MSDN LIbrary, Technet Library, etc.). También hay detalles que no están documentados pero que pueden "descubrirse" utilizando herramientas de monitoreo del Registro, como Process Monitor, tal y como se explica en este artículo.
En el caso que nos ocupa, el usuario quería modificar el tiempo de aparición de la vista en miniatura de una aplicación en la barra de tareas de Windows Vista. En Windows Vista, la funcionalidad Windows Aero permite ver, a golpe de ratón, el contenido de todas y cada una de las ventanas minimizadas en la barra de tareas. El usuario intentaba buscar una forma de acortar ese tiempo de aparición de la vista en miniatura, pues le parecía excesivo. En Internet la alternativa que es más popular es la de modificar el valor de Registro MouseHoverTime de la clave de Registro HKEY_CURRENT_USER\Control Panel\Mouse. Sin embargo, este valor influye en el tiempo que tarda en reconocerse un posicionamiento del cursor del ratón, en cualquier parte de la interfaz gráfica de Windows. Era necesario buscar algún valor de Registro más específico, y para ello iba a recurrir a Process Monitor.
Antes de nada hay que comentar que no tenía la garantía de que existiera un valor de Registro que controlara ese aspecto tan específico de la interfaz gráfica de Windows. De existir, suelen ser parámetros que solo se tienen en cuenta en las versiones de depuración (checked) del sistema operativo. De todas formas, abrí Process Monitor y establecí un filtro de aquellos eventos de Registro que contuvieran en su ruta la palabra "Thumbnail". Inicié Explorer.exe y rápidamente me encontré con tres eventos bastante interesantes:

El primer evento indica el momento en que Explorer.exe examina una directiva, TaskbarNoThumbnail, configurable desde Directiva de grupo, encargada de desactivar las vistas en miniatura de la barra de tareas. La lectura de este valor me hizo pensar que los dos valores siguientes podrían estar relacionados con lo que buscaba.
Esos dos eventos siguientes hacen referencia a una búsqueda de cierta rama en el Registro (ExplorerThumbnails), tanto en la rama HKEY_CURRENT_USER como en la rama HKEY_LOCAL_MACHINE. El primer evento hace referencia al SID de mi usuario dentro de la rama HKEY_USERS (HKU) simplemente porque ejecuté Process Monitor desde una cuenta limitada, proporcionando obviamente el nombre de usuario y contraseña del administrador.
¿Qué sería esa rama ExplorerThumbnails? La verdad es que no encontré nada documentado en Internet, pero su nombre era lo suficientemente atractivo como para seguir indagando. Una de las cosas que suelo hacer en casos como este es crear la clave que busca en este caso Explorer.exe y ver qué valores o subclaves de Registro busca a continuación. Eso hice: Abrí Editor del Registro y creé la clave de Registro ExplorerThumbnails. Volví a capturar una traza con Process Monitor y el resultado ofreció otros tres valores tan interesantes como desconocidos:

Los valores InitialThumbnail, InitialTooltip y AutoPopTooltip me hacían pensar que efectivamente estaban relacionados con lo que realmente buscaba: Modificar el tiempo de aparición de la vista en miniatura. En este caso podría haber experimentado introduciendo algunos valores aleatorios para esos valores de Registro y observar los resultados, aunque en este caso recurrí a una lectura del código fuente de Explorer.
Al parecer, el valor InitialThumbnail tiene que ver con el tiempo en milisegundos que tarda en aparecer la vista en miniatura desde que se sitúa el cursor del ratón en la aplicación minimizada en la barra de tareas; InitialTooltip influye en el tiempo de aparición del texto emergente que siempre aparece encima de la vista en miniatura, un tiempo después. Por último, AutoPopTooltip influye en el tiempo que tarda en desaparecer el texto emergente. Por defecto, si no se encuentran esos valores en el Registro, el sistema supone, respectivamente, 500, 1000 y 5000 milisegundos.
Para concluir, codifiqué una aplicación gráfica que facilitara la modificación de estos valores en el Registro: http://winvista.mvps.org/Ficheros/TweakThumbnails.zip
Ya conoce otro posible uso de Process Monitor: Intentar descubrir valores de Registro (o ficheros) no documentados pero que pueden permitirnos configurar aspectos más o menos interesantes del sistema operativo.
Nota: Como indico en el margen izquierdo de este blog, toda la información que no está documentada por Microsoft es muy susceptible de cambiar e incluso de desaparecer en versiones posteriores de Windows. Este es el caso particular de esta rama del Registro, pues no es tenida en cuenta en Windows 7. Adicionalmente, deberá tomar las medidas oportunas si aplica este tip en un entorno de producción, pues de seguro se trata de un parámetro que no está tan probado como el resto de personalizaciones conocidas en el sistema operativo.
En el anterior artículo vimos cómo abrir nuestra aplicación en modo usuario dentro de Windbg y comprendimos mejor qué es lo que ocurre cuando este se hace cargo de nuestra aplicación. Ahora tenemos a nuestra disposición una ventana con salida de texto y una caja de entrada de comandos pero, ¿qué podemos hacer?
Es importante saber que podemos manejar la ventana de Windbg e introducir comandos en su caja de texto porque nuestra aplicación está detenida debido a la excepción STATUS_BREAKPOINT que se recibe por defecto nada más abrir la aplicación en Windbg. Así pues, lo primero que buscamos es una forma de permitir que nuestra aplicación continúe con su ejecución. El comando g (o presionar F5) consigue esto mismo. Este comando hace que la aplicación siga ejecutándose hasta que se encuentre con alguna condición de parada (posteriormente aprenderemos a configurar esto), o bien ocurra algún tipo de excepción (por ejemplo, la aplicación quiere acceder a una posición de memoria incorrecta).
El comando g admite alguna que otra variante. Si, por ejemplo, queremos que nuestra aplicación siga ejecutándose hasta que se alcance una determinada posición de memoria, podemos usar la sintaxis g <Dirección>. Por otra parte, si solo nos interesa que continúe la ejecución hasta que finalice la función en curso, podemos usar el comando gu.
¿Cómo detener la ejecución de la aplicación en los momentos que nos interese?
Uno de los aspectos más importantes en la depuración de aplicaciones es la creación de breakpoints. Windbg nos proporciona los medios necesarios para crear todo tipo de breakpoints, desde los más sencillos hasta los más imaginativos. Saber qué tipo de breakpoint se debe usar en cada caso nos permitirá ahorrar horas de trabajo infructuoso.
Establecer breakpoints en código
El primer tipo de breakpoints que voy a tratar son los clásicos breakpoints en código. Como su nombre indica, son breakpoints que se alcanzan en una determinada dirección de código (típicamente el inicio de una función, pero podría ser cualquier otra cosa). El comando de Windbg para crear este tipo de breakpoints es bp. Este comando recibe como parámetro la dirección en la que queremos detener la ejecución del programa y, opcionalmente, algunas restricciones y comandos que se ejecuten automáticamente cuando se llegue al breakpoint. El abanico de restricciones que se pueden establecer conforma un amplio tipo de breakpoints que se conocen como breakpoints condicionales, que se tratarán más adelante en este artículo. Por ejemplo, si introducimos el comando bp MiAplicacion!MiFuncion "kb" y pulsamos INTRO, lo que va a ocurrir es que cuando reanudemos la ejecución de nuestra aplicación (con el comando g, como ya sabe) y se llegue a la función cuyo símbolo es MiAplicacion!MiFuncion, el depurador informará sobre esto y automáticamente ejecutará el comando kb, que como verá posteriormente, sirve para ver el estado de una estructura de datos muy importante para un proceso/hilo, la pila de ejecución.
Una cosa que quizá se pregunte es, ¿cómo averiguar qué símbolo de función me interesa? Una opción de fuerza bruta consiste en mostrar en pantalla todos los símbolos que cumplan con un determinado patrón y buscar aquella función que puede interesarnos. El comando x consigue esto mismo. Por ejemplo, si quisiera mostrar los símbolos de MiAplicacion que contuvieran la palabra "Window", podríamos usar la sintaxis x MiAplicacion!*Window*. Al pulsar INTRO, WIndbg buscará entre los símbolos disponibles aquellos que coincidan con el patrón establecido y los mostrará en pantalla junto con sus correspondientes direcciones. Si bien esta aproximación de fuerza bruta es útil en algunos casos, lo más probable es que el símbolo en el que quiera detener la ejecución del programa lo obtenga de los resultados que le proporcione Windbg al ejecutar otros comandos.
Por supuesto, también podemos crear múltiples breakpoints sin necesidad de tener que establecerlos uno a uno. Supongamos que nuestra aplicación tiene algún problema a la hora de escribir ciertos archivos en el disco. Podríamos suponer que los símbolos que más nos interesa son los de aquellas funciones que contengan la palabra "Write". Para agregar breakpoints para todos y cada uno de los símbolos que cumplan con este patrón, podemos usar el comando bm, de la siguiente manera: bm MiAplicacion!*Write*
Si la aplicación que se está depurando tiene un cierto grado de complejidad, es bastante probable que haga uso de librerías dinámicas (DLL) cuya carga no se conoce de antemano y se produce en tiempo de ejecución. Por este motivo, si necesitara parar la ejecución en alguna función dentro de esas librerías dinámicas no le sería posible, pues Windbg no conoce la dirección de la función donde debe detenerse. A fin de cuentas, Windbg solo entiende de direcciones de memoria. Para dar solución a este problema, existe el comando bu. Por ejemplo, si nuestra aplicación hiciera uso de una DLL para calcular los primeros dígitos decimales del número pi, podemos detener la ejecución en dicha función ejecutando el comando bu MiDLL!CalcularPI. Una vez introducido el comando lo que ocurre es que Windbg sabe que justo en el momento en que el módulo correspondiente a esa DLL (MiDLL) se cargue en memoria (eche un vistazo al artículo anterior para obtener más información sobre la carga de módulos), debe crear el correspondiente breakpoint, ahora que sabe cuál es su dirección de memoria.
Establecer breakpoints por acceso a un dato
En muchos casos vamos a necesitar parar la ejecución de nuestro programa no cuando se llegue a una porción de código, sino cuando se acceda a un dato. El escenario típico de esto es una aplicación que en apariencia sufre un bug del tipo buffer overrun. Básicamente este tipo de problemas implica que se sobreescriban posiciones de memoria contiguas a las de una porción de datos del programa, por ejemplo un vector. En esta situación, quizá necesitemos indagar sobre todos y cada uno de los accesos que se produzcan hacia este vector. Podemos lograr esto ejecutando el comando ba w4 miVector, suponiendo que el símbolo miVector sea el correspondiente al vector que queremos examinar. Por supuesto, podemos sustituir el símbolo por una dirección de memoria. ¿Qué significa el w4 que aparece como primer parámetro del comando ba? "W" indica que estamos buscando accesos de tipo escritura. Otras posibilidades son "E" (ejecución) y "R" (lectura). El "4" indica el número de bytes que queremos monitorizar. En las arquitecturas de 32 bits este valor puede ser 1, 2 ó 4. En las de 64 bits existe la posibilidad de establecer también 8 bytes como tamaño.
Como habrá podido observar, la creación de breakpoints es un aspecto complejo de Windbg. La capacidad más interesante a mi juicio es la posibilidad de crear breakpoints condicionales. Como vimos, para crear un breakpoint se usa el comando bp. Uno de los parámetros de este comando va entrecomillado y representa una condición adicional a la condición estándar (la llegada a la correspondiente función). Incluso se puede omitir la función y dejar solamente la condición. Supongamos que nuestro programa tiene una variable Contador que va incrementándose cada vez que se itera en un bucle, por ejemplo. Quizá estemos interesados en parar la ejecución de nuestra aplicación cuando la variable Contador adquiera el valor 10, que sabemos que es el valor de finalización del bucle. Para ello podríamos usar esta sintaxis:
bp ".if (poi(Contador)==10) {.echo Fin de cuenta }"
Este comando establece un breakpoint (bp) cuya condición (.if) es que la variable Contador sea igual al número 10. En tal caso, sacamos por pantalla (.echo) la cadena "Fin de cuenta". El comando poi se usa porque de no hacerlo, la variable Contador sería interpretada como una dirección de memoria, y esto no es lo que queremos. Como queremos tener acceso al contenido de esa dirección de memoria, los programadores de C que estén leyendo esto contestarán rápidamente que necesitamos desreferenciar ese acceso. En C esto se hace con el operador * y en Windbg con poi.
Cómo mostrar un listado de los breakpoints que hemos añadido
Una vez que hayamos establecido los breakpoints que necesitemos, es posible que queramos ver un listado de los mismos. Puede usar para ello el comando bl o bien usar la interfaz gráfica de Windbg: Si pulsa F9, se abrirá el cuadro de diálogo Breakpoints, desde el que podrá ver un listado de los breakpoints asociados a la sesión de depuración actual y crear de manera más o menos intuitiva otros nuevos. Mi consejo es usar siempre que sea posible la propia consola de comandos de Windbg, pues suele ofrecer más flexibilidad que la interfaz gráfica, pero comento esta posibilidad por si alguien se siente más cómodo/a usando cuadros de diálogo.
En este artículo hemos aprendido a crear breakpoints básicos y sencillos y otros no tan sencillos. Hemos visto también que la potencia de los breakpoints reside en los breakpoints condicionales, pues el único límite prácticamente es el de nuestra imaginación. La documentación de Windbg (F1) ofrece información más detallada sobre cada uno de los comandos que he tratado aquí, pero espero esto que sirva como punto de partida para sacarle el mayor provecho posible a esta funcionalidad que nos proporciona el sistema operativo en conjunción con Windbg. En los próximos artículos veremos comandos que nos permiten examinar el estado de la máquina. Para comprender mejor la salida de estos comandos, se explicarán también algunos conceptos importantes de la estructura de un computador y del sistema operativo, como son el repertorio de registros, la pila de ejecución o el bloque de control de un proceso/hilo.