Трассировка Python и условные переходы - PullRequest
6 голосов
/ 04 августа 2011

Я пишу concolic engine для Python с использованием функциональности sys.settrace().

Основная задача при выполнении такого рода - записать ограничения на входные переменные. Ограничения - это не что иное, как условия операторов if, которые создают две ветви (ветвь then и else).

Когда выполнение завершено, механизм выбирает ограничение и находит подходящие значения для входных данных, чтобы выполнение пошло вниз по другой ветви (при выполнении x оно переходит в ветвь then, при выполнении x + 1 он идет вдоль ветви "else").

Это немного контекста о том, почему я делаю то, что пытаюсь сделать ...

Комбинируя settrace() и модуль dis, я вижу байт-код каждой строки исходного кода непосредственно перед его выполнением. Таким образом, я легко могу записать условия if, которые появляются во время выполнения.

Но тогда у меня есть большая проблема. Мне нужно знать, в какую сторону пошла if, какую ветку взяла на себя казнь. Так что, если мой код что-то вроде:

if x > a:
  print x
else:
  print a

в определенный момент моя трассирующая вещь увидит:

t: if x > 0: 

тогда интерпретатор python выполнит if и прыгнет (или нет) куда-нибудь. И я увижу:

t + 1: print x

Так есть ли инструкция t + 1 в ветви "then" или в ветви "else"? Помните, что функция трассировки видит только некоторый байт-код в текущем блоке.

Я знаю два способа сделать это. Одним из них является оценка состояния, чтобы точно определить, является ли оно истинным или ложным. Это работает, только если нет побочных эффектов.

Другой способ - попытаться посмотреть на указатель инструкции на t + 1 и попытаться понять, где мы находимся в коде. Это способ, которым я сейчас пользуюсь, но он очень деликатный, потому что на t + 1 я могу оказаться в другом месте (другой модуль, встроенная функция и т. Д.).

Итак, наконец, у меня возник вопрос: есть ли способ получить из самого Python или из модуля / расширения C / чего-либо еще результат последнего условного перехода?

В качестве альтернативы, есть ли более точные параметры трассировки? Что-то вроде выполнения байт-кода по одному коду операции за раз. С функциональностью settrace() максимальное разрешение, которое я получаю - это целые строки исходного кода.

В худшем случае, я думаю, я могу изменить интерпретатор Python для предоставления такой информации, но я бы оставил это в качестве крайней меры по очевидным причинам.

Ответы [ 2 ]

6 голосов
/ 04 августа 2011

В трассировщике нет информации о последней взятой ветке.

Что я сделал для реализации измерения покрытия ветки в cover.py, так это вел запись каждого стекового кадра последней выполненной строки,затем в следующий раз, когда вызывается функция трассировки, я могу записать пару номеров строк, которые образуют дугу выполнения.

О более тонкой трассировке: вы можете обмануть интерпретатор Python, чтобы он дал вам байтинформация о кодеМой эксперимент в этом описан здесь: Злой хак: трассировка байт-кода Python

Мне было бы очень интересно посмотреть, как эта работа продвигается!

4 голосов
/ 31 августа 2011

В конце концов это то, что я сделал.Я реализовал инструментарий AST, и он работает довольно хорошо.

Играя с AST, вам нужно переместить все вызовы функций (а также атрибуты и подписки из-за getattr() и друзей из условий if насоздание временных переменных. Также вам нужно разделить операторы and и or.

Затем добавить вызов вашей собственной функции в начале каждой ветви с логическим параметром True для затем ветвь и False для else ветвь.

После этого я написал преобразователь AST в источник (есть где-то в сети, но нетработа с текущими версиями Python).

Работа с AST очень проста и довольно проста, в итоге я сделал три прохода преобразования, добавив также несколько операторов import.

Это первыйpass, в качестве примера. Он разбивает условия, если они содержат операторы or или and:

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

Возможно, это не очень полезно для приложений покрытия кода, потому чтоэто очень сильно портит код.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...