Walk This Way: Jugando con el Call Stack
Bueno, dado que soy el dueño y señor indiscutible de los reinos del metal en esta casa, y que puedo permitirme un descanso entre tema y tema con el Guitar Hero, he conseguido sacar un poco de tiempo para ir escribiendo el siguiente artículo sobre depuración con WinDbg.
El pequeño artículo de hoy se limitará a describir la pila de llamadas (en adelante lo llamaré por su termino sajón: call stack. Es común referirse a él con esta terminología, y además en inglés tiene un cool factor mucho mayor :P ), a visualizarlos mediante WinDbg y a explorar algunos comandos nuevos que nos permitirán ver las variables locales, ver el contenido de una variable de un tipo concreto, establecer un punto de parada, etc.
Eso si, antes de ponernos manos a la obra, un pequeño detalle. Como alguno de vosotros me hizo llegar por correo, en el artículo anterior de la serie los volcados se realizaron sobre una aplicación de 64 bits, lo que se reflejaba en unas direcciones de memoria aberrantemente grandes. Si bien hasta hace poco no eran habituales, las máquinas de 64 bits ya están a la orden del día; lo he pensado un poco y he decidido continuar realizando las demos de los siguientes artículos sobre una máquina de 64 bits, pero me gustaría tener feedback vuestro para saber que preferís. Si preferís trabajar con las direcciones de memoria a las que estamos acostumbrados, me monto una máquina virtual y hacemos las siguientes entradas de la serie en 32 bits, y más adelante daremos, si os apetece y me seguís leyendo, el salto a los 64 bits :) En fin, como vosotros veáis.
El Call Stack
Supongo que casi todos estaremos familiarizados con el concepto de Call Stack, por lo que no me extenderé en su descripción. Simplemente recordaré que es una estructura que almacena la información necesaria para poder anidar llamadas a funciones o métodos, y poder retornar al punto de origen de la llamada a la función una vez que ésta termina.
En el diagrama que os pongo a continuación (¡cómo me gusta hacer dibujitos!) os muestro un ejemplo de un proceso de Windows, con múltiples hilos de ejecución, y dentro de cada hilo de ejecución podemos ver su pila de llamadas, que almacena los diferentes frames o contextos de ejecución.
Como vemos en el ejemplo, el primer hilo comienza con el punto de entrada de la aplicación (lo deducimos del primer frame, cuyo nombre de función es Main), y a continuación invoca a un método llamado Init() en la clase App. Supongamos, para nuestro ejemplo, que dentro de este método se crean otros hilos auxiliares (del hilo 2 al hilo n, en el diagrama) que se encargarán de otras actividades (monitorización, worker threads, etc). Cada uno de estos hilos adicionales tendrá su propio call stack, con sus frames independientes.
Continuando con nuestro ejemplo, podemos deducir que hubo algún problema durante la inicialización de la clase App, y la aplicación decidió generar un volcado de memoria (por el método Dump, en la clase ErrMgr). Todo esto lo intuimos gracias a la semántica contenida en los símbolos, que nos permiten ver los nombres de las funciones en el call stack, como comentamos en el anterior artículo.
He comentado el concepto de frame muy brevemente, y he sido injusto, ya que es la estructura clave del Call Stack; almacena toda la información relevante, como son las variables locales de la función, la dirección de retorno de la misma (para volver, una vez finalizada la ejecución del método o función, a la siguiente instrucción en el frame anterior), los registros almacenados, las cookies de seguridad, etc.
A continuación os pongo el aspecto que tiene un frame de una aplicación compilada con Visual Studio 2003.
No voy a entrar ahora en detalles como el manejador de excepciones del frame, o las Cookies de Seguridad (para las que tengo un articulo en mente). Me conformo con que os quedéis con la idea de que dentro de cada frame tenemos acceso a la lista de parámetros que se le han pasado a la función o método, así como a todas las variables locales al frame y, por supuesto, la dirección de retorno de ka función.
minesweeper.exe: el proceso
Después de este poquito de teoría, vamos a pasar a la practica diseccionando alguna aplicación del sistema ya conocida. Si en el post anterior usamos como conejillo de indias el notepad.exe, en esta caso vamos a utilizar al eterno buscaminas, en esta ocasión en su encarnación bajo Windows Vista.
Con nuestro objetivo claro, ejecutamos el buscaminas y a continuación abrimos el WinDbg (si estamos en Windows Vista nos aseguramos de ejecutarlo como administrador). Siguiendo las instrucciones que ya detallamos en el post anterior, adjuntamos el WinDbg al proceso minesweeper.exe (F6), recargamos los símbolos, y podemos comprobar como el depurador se ha adjuntado y nos permite explorar el proceso, mientras que si cambiamos a la ventana del buscaminas podemos ver como la interfaz de usuario se encuentra congelada. Esto se debe a que el proceso realmente esta congelado por el depurador, y eso impide que la interfaz de usuario responda a los eventos de la bomba de mensajes de Windows.
Ok, vamos a empezar explorando todos los hilos del proceso y visualizando sus stacks. Para ello, y cómo ya vimos en su día, empleamos el comando ~*k (recordemos que ~ significa hilo, * significa todos y k ordena mostrar el call stack. Si hubiéramos querido mostrar solo el stack del hilo 4 podríamos haber lanzado un ~4k, etc.). El resultado es el siguiente:
0:008> ~*k
0 Id: 440.1224 Suspend: 1 Teb: 000007ff`fffde000 Unfrozen
Child-SP RetAddr Call Site
00000000`0011efa8 00000000`779fd908 ntdll!NtDelayExecution+0xa
00000000`0011efb0 00000000`ffe65fa3 kernel32!SleepEx+0x84
00000000`0011f030 00000000`ffe65d23 MineSweeper!RunEngine+0x125
00000000`0011f060 00000000`ffe4dec8 MineSweeper!InitializeEngine+0x11a2
00000000`0011f7b0 00000000`ffeaf525 MineSweeper!WinMain+0x150
00000000`0011f830 00000000`779fcdcd MineSweeper!operator new[]+0x27f
00000000`0011f8f0 00000000`77b4c6e1 kernel32!BaseThreadInitThunk+0xd
00000000`0011f920 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
...
# 8 Id: 440.7d8 Suspend: 1 Teb: 000007ff`fffaa000 Unfrozen
Child-SP RetAddr Call Site
00000000`0a7bf758 00000000`77be33e8 ntdll!DbgBreakPoint
00000000`0a7bf760 00000000`779fcdcd ntdll!DbgUiRemoteBreakin+0x38
00000000`0a7bf790 00000000`77b4c6e1 kernel32!BaseThreadInitThunk+0xd
00000000`0a7bf7c0 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
(Solo he mostrado el primer y último de los 9 hilos que había en el proceso en el momento que se adjunto el depurador.)
Podemos ver que el primer hilo (con identificador 0) es el que contiene el punto de entrada de la aplicación, ya que su cuarto frame es el método WinMain. Vamos a ponernos en este hilo (~0s, con la s indicando step, es decir, situarnos sobre el hilo 0) y a mostrar el stack, pero esta vez con el comando kn, que nos agrega el identificador de frame a cada elemento del stack:
0:008> ~0s
ntdll!NtDelayExecution+0xa:
00000000`77b505ba c3 ret
0:000> kn
# Child-SP RetAddr Call Site
00 00000000`0011efa8 00000000`779fd908 ntdll!NtDelayExecution+0xa
01 00000000`0011efb0 00000000`ffe65fa3 kernel32!SleepEx+0x84
02 00000000`0011f030 00000000`ffe65d23 MineSweeper!RunEngine+0x125
03 00000000`0011f060 00000000`ffe4dec8 MineSweeper!InitializeEngine+0x11a2
04 00000000`0011f7b0 00000000`ffeaf525 MineSweeper!WinMain+0x150
05 00000000`0011f830 00000000`779fcdcd MineSweeper!operator new[]+0x27f
06 00000000`0011f8f0 00000000`77b4c6e1 kernel32!BaseThreadInitThunk+0xd
07 00000000`0011f920 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
¿Veis el método MineSweeper.InitializeEngine()? No se a vosotros, pero a mi me suena interesante la idea de meterme en ese contexto y ver que variables locales están definidas en ese método. ¿Como podemos hacerlo? Bueno, primero usamos el comando .frame para poder meternos en el contexto de ese método:
0:000> .frame 03
03 00000000`0011f060 00000000`ffe4dec8 MineSweeper!InitializeEngine+0x11a2
Ok, y ahora ¿como vemos las variables locales? Tenemos dos posibilidades, usar el comando x (explore) sin parámetros, o bien usar el dv (dump variables). Vamos a probar...
0:000> x
0:000> dv
Unable to enumerate locals, HRESULT 0x80004005
Private symbols (symbols.pri) are required for locals.
Type ".hh dbgerr005" for details.
Ups.. esto no tiene buena pinta. El comando x no nos devuelve nada, y el dv nos indica que necesitamos símbolos privados para poder acceder a las variables locales. En este caso estamos trabajando con los símbolos públicos que expone Microsoft en http://msdl.microsoft.com/download/symbols, por lo que podemos decir que nuestro gozo en un pozo... :(
De todos modos, no todo esta perdido... conociendo la estructura del frame podemos acceder a los parámetros y a las varaibles locales incluso sin tener símbolos, aunque es un poco más laborioso. Esto lo dejaremos para la siguiente entrada de ésta serie.
Para terminar vamos a devolver el control al proceso de modo que vuelva a responder a los mensajes de Windows, de modo que su interfaz de usuario vuelva a funcionar correctamente. Para ello emplearemos el comando g (go) del depurador. Una vez lanzado, el control solo volverá al depurador cuando se encuentre con un punto de parada (breakpoint) o cuando pulsemos la combinación de teclas Crtl+Intr desde la ventana de WinDbg.
Conclusiones:
Como hemos podido ver, el mero hecho de visualizar los call stack de los diferentes hilos de un proceso nos puede dar muchísima información útil para procesos de troubleshooting o de ingeniería inversa, o simplemente para pasar un buen rato usando el WinDbg :)
La lista de comandos que hemos usado de WinDbg no ha crecido mucho desde el primer artículo, y es que realmente no es necesario recordar de memoria la tremenda cantidad de mnemónicos expuestos por el depurador; con un puñado tenemos suficiente para hacer virguerías... Virguerías que, por cierto, espero empezar a hacer en el próximo artículo, ¡si decidís acompañarme nuevamente! :)
Rock Tip:
Este post viene acompañado del temazo 'Walk this Way' de Aerosmith. ¿Que decir de Aerosmith? Unos músicos como la copa de un pino, divertidos, eternos... A parte de ello, yo creo que el amigo Perry y el amigo Tyler han triunfado allí donde fracasó Don Juan Ponce de León... (y ahora es cuando vosotros pensáis que se me acaba de ir la pinza una vez más :D) Me explico: estos dos señores tienen mas años que mis padres, y mientras a mis padres les cuesta subir la escalera de casa, a estos elementos se les ve noche tras noche pegando botes y carreras de un lado al otro de un escenario del tamaño de un campo de fútbol, y todo ello después de más de 4 décadas de excesos... ¿como es posible? Sólo se me ocurre que hayan encontrado la fuente de la eterna juventud. O eso, o el tema de venta de alma al diablo.. pero esa se la reservamos a Robert Johnson.
Pero... ¿que relación tiene este tema con el post? Pues poca... francamente poca :) Pero resulta que veréis mucho la expresión 'walk down the stack' o simplemente 'walk the stack' cuando se recorre el call stack desapilando frames, y siempre que leo/oigo/digo esa expresión me acuerdo de este tema de Aerosmith.