¿Cómo funciona un depurador?
Sigo preguntándome ¿cómo funciona un depurador? Particularmente el que se puede "adjuntar" a un ejecutable que ya se está ejecutando. Entiendo que el compilador traduce el código al lenguaje de máquina, pero entonces, ¿cómo "sabe" el depurador a qué se está adjuntando?
Los detalles de cómo funciona un depurador dependerán de lo que esté depurando y del sistema operativo. Para la depuración nativa en Windows, puede encontrar algunos detalles en MSDN: API de depuración Win32 .
El usuario le dice al depurador a qué proceso adjuntar, ya sea por nombre o por ID de proceso. Si es un nombre, el depurador buscará el ID del proceso e iniciará la sesión de depuración mediante una llamada al sistema; En Windows, esto sería DebugActiveProcess .
Una vez conectado, el depurador ingresará a un bucle de eventos muy similar a cualquier interfaz de usuario, pero en lugar de eventos provenientes del sistema de ventanas, el sistema operativo generará eventos basados en lo que sucede en el proceso que se está depurando (por ejemplo, si ocurre una excepción). Consulte WaitForDebugEvent .
El depurador puede leer y escribir la memoria virtual del proceso de destino e incluso ajustar sus valores de registro a través de las API proporcionadas por el sistema operativo. Consulte la lista de funciones de depuración para Windows.
El depurador puede utilizar información de archivos de símbolos para traducir direcciones a nombres de variables y ubicaciones en el código fuente. La información del archivo de símbolos es un conjunto separado de API y no es una parte central del sistema operativo como tal. En Windows, esto se realiza a través del SDK de acceso a la interfaz de depuración .
Si está depurando un entorno administrado (.NET, Java, etc.), el proceso normalmente será similar, pero los detalles son diferentes, ya que el entorno de la máquina virtual proporciona la API de depuración en lugar del sistema operativo subyacente.
Según tengo entendido:
Para puntos de interrupción de software en x86, el depurador reemplaza el primer byte de la instrucción con CC
( int3
). Esto se hace WriteProcessMemory
en Windows. Cuando la CPU llega a esa instrucción y ejecuta int3
, esto hace que la CPU genere una excepción de depuración. El sistema operativo recibe esta interrupción, se da cuenta de que el proceso se está depurando y notifica al proceso de depuración que se alcanzó el punto de interrupción.
Una vez que se alcanza el punto de interrupción y se detiene el proceso, el depurador busca en su lista de puntos de interrupción y lo reemplaza CC
con el byte que estaba allí originalmente. El depurador establece el TF
indicador de trampaEFLAGS
(modificando el CONTEXT
) y continúa el proceso. El indicador de trampa hace que la CPU genere automáticamente una excepción de un solo paso ( INT 1
) en la siguiente instrucción.
Cuando el proceso que se está depurando se detiene la próxima vez, el depurador reemplaza nuevamente el primer byte de la instrucción del punto de interrupción con CC
y el proceso continúa.
No estoy seguro de si así es exactamente como lo implementan todos los depuradores, pero escribí un programa Win32 que logra depurarse a sí mismo utilizando este mecanismo. Completamente inútil, pero educativo.
En Linux, la depuración de un proceso comienza con la llamada al sistema ptrace(2) . Este artículo tiene un excelente tutorial sobre cómo implementar ptrace
algunas construcciones de depuración simples.