Skip to content

Instantly share code, notes, and snippets.

@mgarciaisaia
Created April 1, 2020 00:49
Show Gist options
  • Save mgarciaisaia/16a7db528d1cdd7d30cacbd459ddee56 to your computer and use it in GitHub Desktop.
Save mgarciaisaia/16a7db528d1cdd7d30cacbd459ddee56 to your computer and use it in GitHub Desktop.
¿Cómo funciona un debugger?

(extracto de un post viejo)

Por último, con Esteban nos colgamos pensando cómo era que hacía un debugger para ir pausando al proceso debuggeado. Encontré este post (escrito en un inglés medio medio, y que ya en algún momento me pondré a traducir en un español apenas mejor) en el que explica cómo lo hacen.

La base está en que el debugger modifica el código del proceso debuggeado, reemplazando instrucciones de assembler por INT 3, una instrucción que genera una interrupción, para que el debugger pueda pedir al sistema operativo ser notificado de ello, y entrar a inspeccionar el proceso debuggeado. Cuando es notificado, el debugger reemplaza el byte de esa instrucción por el byte que había antes de sobreescribirlo (byte que convenientemente se había guardado de modo temporal), y cambia el instruction pointer para que vuelva un byte atrás, cosa de, ahora sí, ejecutar la instrucción real - y no el INT 3 que le habían inyectado.

¡Oiga, mentirosos! Me juraron durante toda la vida que un proceso no tiene acceso al espacio de direcciones de otro, y que el segmento de código de un proceso es de sólo lectura. ¡Me han engañado cual quinceañera!

Bueno, quizá un poco, pero tampoco tanto. Yo pensé esto mismo (y pensé en dejar la carrera, dedicarme a cargar la SUBE en la estación de Morón y ser feliz sin pensar estas cosas), pero no es tan así el tema: para poder hacer todo esto, el kernel tiene que darnos soporte. En particular, en Linux hay dos formas de "activar" este soporte: o bien el proceso a ser debuggeado le pide al kernel "quiero que me vigilen" (usando la syscall ptrace(PTRACE_TRACEME)), o bien el debugger pide anexarse a un proceso ya funcionando.

Pero, ¿cómo hacemos, por ejemplo, para correr ls - un programa ya existente - y que éste pida que lo vigilen? La magia está en que es el proceso el que pide que lo traceen, no el programa. Cuando le pedimos al debugger que ejecute un programa en modo debug, el debugger se forkea (creando un nuevo proceso con el mismo código), y el proceso recién creado ejecuta el ptrace(PTRACE_TRACEME), y luego usa execve para reemplazar su imagen por la del programa a debuggear (por ejemplo, /bin/ls). De ese modo, ls queda con su código intacto (bueno, salvando que el debugger le va a inyectar las interrupciones, pero no necesita declarar código especial para soportar ser debuggeado).

De todos modos, si pueden, leanse el post, que obviamente lo explica bastante mejor que yo haciendo un resumen.

PD: Esteban me pasó también este link en el que explican cómo funciona el debuggeo en Windows. Aún no lo leí, pero es un lindo complemento para ver cómo funciona esto en otro SO. OS X (y los UNIX, en general) usan ptrace, así que a priori existirían "esos dos bandos" (aunque ptrace tiene variaciones entre sistema y sistema).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment