En el último artículo de la serie vimos lo útiles que pueden llegar a ser los hardware breakpoints para depurar aplicaciones que por ejemplo hacen comprobaciones de CRC y no pueden ser depuradas modificando sus datos en memoria.
Hoy vamos a ver como podemos utilizar memory breakpoints para sobrepasar los límites impuestos por los hardware breakpoints y poder entre otras cosas controlar el acceso a grandes partes de la memoria.
Memory Breakpoints
Para empezar hay que decir que los memory breakpoints no son realmente breakpoints. Cuando un depurador fija un memory breakpoint cambia los permisos en una región o página de la memoria.
Una página es la porción más pequeña de memoria que un sistema operativo puede manejar, este valor normalmente es de 4096 Bytes (4K). Cuando se asigna una página, el sistema operativo le otorga una serie de permisos que indican como debe ser accedida. Algunos de esos permisos son los que siguen:
-
Ejecución: Permite la ejecución pero eleva una excepción si el proceso intenta escribir o leer de la página
-
Lectura: Activa el acceso para lectura pero se eleva una excepción ante cualquier intento de escritura o ejecución
-
Escritura: Permite el acceso de escritura
-
Protegida: Cualquier acceso a una página protegida resulta en una excepción y la página vuelve a su estado original
La mayoría de los sistemas operativos modernos permiten combinar estos permisos. Por ejemplo, podemos tener páginas en memoria que permitan el acceso de lectura y escritura mientras que otras pueden permitir el acceso de lectura y la ejecución.
Cada sistema operativo implementa sus propios métodos para consultar los permisos de una página en particular y modificarlos si fuese necesario. El permiso que nos interesa para nuestra tarea de depurado es el de "página protegida.
Este tipo de página es realmente útil para cosas como separar el stack del heap o comprobar que una región de memoria dada no sobrepasa sus límites. Y claro, también es tremendamente útil para detener un proceso cuando accede a una sección de memoria en particular.
¿Cómo se usan?
Imaginemos que estamos depurando un servidor de red, podríamos establecer un memory breakpoint en la región de memoria donde se guarda el cuerpo de un paquete de red al recibirlo. Esto permite determinar cuando y de que manera, la aplicación utiliza el contenido del paquete recibido ya que cualquier acceso a esa región de memoria hará que la CPU se pare y levantará una excepción de página protegida que podremos capturar con el depurador.
Entonces podremos inspeccionar la instrucción que ha accedido a la memoria y determinar que operaciones realiza con el contenido de la misma. Esta técnica también esquiva el problema de comprobación de firma CRC ya que no modificamos nada del código en ejecución.
Y con este último método ya hemos visto todas las formas en las que podemos colocar un punto de interrupción en una aplicación en ejecución para poder depurarla con los fines que sean.
Más en GenbetaDev | Funcionamiento de los depuradores de C y C++