Estoy escribiendo un concolic engine para Python usando la funcionalidad sys.settrace()
.Trazado de Python y saltos condicionales
La tarea principal durante este tipo de ejecución es registrar las restricciones en las variables de entrada. Las restricciones no son más que las condiciones de las sentencias if, que crean dos ramas (la rama 'then' y 'else').
Cuando se completa una ejecución, el motor elige una restricción y encuentra los valores apropiados para las entradas para que la ejecución baje a lo largo de la otra rama (en la ejecución x va la rama 'then', en la ejecución x + 1 va a lo largo de la rama 'else').
Este es tener un poco de contexto sobre por qué estoy haciendo lo que estoy tratando de hacer ...
Al combinar settrace()
y el módulo dis
, llego a ver el código de bytes de cada línea de código fuente, justo antes de que se ejecute. De esta forma puedo registrar fácilmente las condiciones if tal como aparecen durante la ejecución.
Pero luego tengo el gran problema. Necesito saber en qué dirección fue el if, qué rama tomó la ejecución. Así que si mi código es algo como:
if x > a:
print x
else:
print a
en un momento dado mi cosa trazado verá:
t: if x > 0:
entonces el intérprete de Python ejecutará el caso y saltar (o no) en alguna parte. Y yo veo:
t + 1: print x
Así es la instrucción t + 1
en la rama "y luego" o en el "otro"? Tenga en cuenta que la función de rastreo solo ve algunos bytes en el bloque actual.
Sé de dos maneras de hacerlo. Una es evaluar la condición para ver exactamente si es verdadera o falsa. Esto funciona solo si no hay efectos secundarios.
La otra manera es tratar de buscar y el puntero de instrucción en t + 1
y tratar de entender dónde estamos en el código. Esta es la forma en que estoy usando ahora, pero es muy delicado porque al t + 1
podría encontrarme en un lugar completamente diferente (otro módulo, una función incorporada, etc.).
Así que, finalmente, la pregunta que tengo es esta: ¿hay alguna manera de obtener de Python, o de un módulo C/extensión/lo que sea, el resultado del último salto condicional?
En alternativa, ¿hay más opciones de rastreo de grano fino? Algo así como ejecutar bytecode un opcode a la vez. Con la funcionalidad settrace()
, la resolución máxima que obtengo es de líneas de código fuente completas.
En el peor de los casos, creo que puedo modificar el intérprete de Python para exponer dicha información, pero lo dejaría como último recurso, por razones obvias.
Vi también que existe la posibilidad de construir un árbol de sintaxis abstracto a partir del código, modificarlo, compilarlo y ejecutarlo. Debería ser posible usar eso y agregar instrumentación muy simple (como una llamada a función al comienzo de cada rama que le indique en qué dirección fue el if). –
Sí, también es una posibilidad, no una que he experimentado conmigo mismo. –