2011-08-04 11 views
6

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.

Respuesta

4

Al final esto es lo que hice. Implementé la instrumentación AST y funciona bastante bien.

Al jugar con la AST, tiene que mover todas las llamadas de función (también atributos y suscripciones, debido a getattr() y amigos, fuera del caso de condiciones mediante la creación de variables temporales. También es necesario dividir los and y or operadores.

a continuación, agregue una llamada a su propia función en el comienzo de cada rama, con un parámetro booleano, True para la rama continuación y False para la otra rama.

Después de eso me wrot e un convertidor AST a origen (hay uno en algún lugar de la red, pero no funciona en las versiones actuales de Python).

Trabajar con AST es muy fácil y bastante simple, terminé haciendo tres pases de transformación, agregando también algunas declaraciones import.

Este es el primer pase, como un ejemplo. Se divide si las condiciones si contienen o orand operadores:

class SplitBoolOpPass1(ast.NodeTransformer): 
    def visit_If(self, node): 
     while isinstance(node.test, ast.BoolOp): 
     new_node = ast.If(test=node.test.values.pop(), body=node.body, orelse=node.orelse) 
     if isinstance(node.test.op, ast.And): 
      if len(node.test.values) == 1: 
      node.test = node.test.values[0] 
      node.body = [new_node] 
     else: 
      if len(node.test.values) == 1: 
      node.test = node.test.values[0] 
      node.orelse = [new_node] 
     node = self.generic_visit(node) # recusion 
     return node 

Probablemente no es muy útil para aplicaciones de cobertura de código, ya que puede confundir con el código bastante.

5

No hay información en la instalación de seguimiento sobre la última rama tomada.

Lo que hice para implementar la medición de cobertura de rama en coverage.py es mantener un registro para cada marco de pila de la última línea ejecutada, luego la próxima vez que se llama la función de rastreo, puedo grabar un par de números de línea que formar un arco de ejecución from-to.

Acerca del seguimiento detallado: puede engañar al intérprete de Python para que le proporcione información sobre el código de bytes. Mi experimento en esto se describe aquí: Wicked hack: Python bytecode tracing

¡Estaría muy interesado en ver cómo progresa este trabajo!

+0

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). –

+0

Sí, también es una posibilidad, no una que he experimentado conmigo mismo. –