2

Python プログラム用の小さなデバッグ ツールを開発したいと考えています。「動的スライシング」機能では、ステートメントでアクセスされる変数を見つけ、それらの変数へのアクセスの種類 (読み取りまたは書き込み) を見つける必要があります。

しかし、Python に組み込まれている唯一の逆アセンブリ機能はdis.disassembleであり、逆アセンブリを標準出力に出力するだけです。

>>> dis.disassemble(compile('x = a + b', '', 'single'))
  1           0 LOAD_NAME                0 (a)
              3 LOAD_NAME                1 (b)
              6 BINARY_ADD          
              7 STORE_NAME               2 (x)
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE        

次のように、逆アセンブリを、各命令で使用される変数を記述するセットの辞書に変換できるようにしたいと考えています。

>>> my_disassemble('x = a + b')
{'LOAD_NAME': set(['a', 'b']), 'STORE_NAME': set(['x'])}

これどうやってするの?

4

2 に答える 2

3

モジュールのソース コードをdis読むと、独自の逆アセンブリを実行して、好きな出力形式を生成するのが簡単であることがわかります。コード オブジェクトで一連の命令を生成するコードとその引数を次に示します。

from opcode import *

def disassemble(co):
    """
    Disassemble a code object and generate its instructions.
    """
    code = co.co_code
    n = len(code)
    extended_arg = 0
    i = 0
    free = None
    while i < n:
        c = code[i]
        op = ord(c)
        i = i+1
        if op < HAVE_ARGUMENT:
            yield opname[op],
        else:
            oparg = ord(code[i]) + ord(code[i+1])*256 + extended_arg
            extended_arg = 0
            i = i+2
            if op == EXTENDED_ARG:
                extended_arg = oparg*65536L
            if op in hasconst:
                arg = co.co_consts[oparg]
            elif op in hasname:
                arg = co.co_names[oparg]
            elif op in hasjrel:
                arg = repr(i + oparg)
            elif op in haslocal:
                arg = co.co_varnames[oparg]
            elif op in hascompare:
                arg = cmp_op[oparg]
            elif op in hasfree:
                if free is None:
                    free = co.co_cellvars + co.co_freevars
                arg = free[oparg]
            else:
                arg = oparg
            yield opname[op], arg

そして、これが分解の例です。

>>> def f(x):
...     return x + 1
... 
>>> list(disassemble(f.func_code))
[('LOAD_FAST', 'x'), ('LOAD_CONST', 1), ('BINARY_ADD',), ('RETURN_VALUE',)]

これを、必要なセットの辞書データ構造に簡単に変換できます。

>>> from collections import defaultdict
>>> d = defaultdict(set)
>>> for op in disassemble(f.func_code):
...     if len(op) == 2:
...         d[op[0]].add(op[1])
... 
>>> d
defaultdict(<type 'set'>, {'LOAD_FAST': set(['x']), 'LOAD_CONST': set([1])})

(または、セットの辞書データ構造を直接生成することもできます。)

アプリケーションでは、実際には各オペコードの名前を調べる必要はないことに注意してください。代わりに、辞書で必要なオペコードを調べてopcode.opmap、おそらく次のように名前付き定数を作成できます。

LOAD_FAST = opmap['LOAD_FAST'] # actual value is 124
...
for var in disassembly[LOAD_FAST]:
    ...

更新:Python 3.4では、新しいを使用できますdis.get_instructions

>>> def f(x):
...     return x + 1
>>> import dis
>>> list(dis.get_instructions(f))
[Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='x',
             argrepr='x', offset=0, starts_line=1, is_jump_target=False),
 Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval=1,
             argrepr='1', offset=3, starts_line=None, is_jump_target=False),
 Instruction(opname='BINARY_ADD', opcode=23, arg=None, argval=None,
             argrepr='', offset=6, starts_line=None, is_jump_target=False),
 Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None,
             argrepr='', offset=7, starts_line=None, is_jump_target=False)]
于 2013-01-14T18:49:10.473 に答える
-1

disここでの課題は、出力を解析して辞書を作成するのではなく、出力をキャプチャすることだと思います。2 番目の部分を取り上げない理由は、辞書の形式とフィールド (キー、値) が言及されておらず、些細なことだからです。

前述したように、OP をキャプチャするのが難しい理由disは、リターンではなく印刷ですが、これはコンテキスト マネージャーを介してキャプチャできます。

def foo(co):
    import sys
    from contextlib import contextmanager
    from cStringIO import StringIO
    @contextmanager
    def captureStdOut(output):
        stdout = sys.stdout
        sys.stdout = output
        yield
        sys.stdout = stdout
    out = StringIO()
    with captureStdOut(out):
        dis.disassemble(co.func_code)
    return out.getvalue()

import dis
import re
dict(re.findall("^.*?([A-Z_]+)\s+(.*)$", line)[0] for line in foo(foo).splitlines() 
                                                  if line.strip())
{'LOAD_CONST': '0 (None)', 'WITH_CLEANUP': '', 'SETUP_WITH': '21 (to 107)', 'STORE_DEREF': '0 (sys)', 'POP_TOP': '', 'LOAD_FAST': '4 (out)', 'MAKE_CLOSURE': '0', 'STORE_FAST': '4 (out)', 'IMPORT_FROM': '4 (StringIO)', 'LOAD_GLOBAL': '5 (dis)', 'END_FINALLY': '', 'RETURN_VALUE': '', 'LOAD_CLOSURE': '0 (sys)', 'BUILD_TUPLE': '1', 'CALL_FUNCTION': '0', 'LOAD_ATTR': '8 (getvalue)', 'IMPORT_NAME': '3 (cStringIO)', 'POP_BLOCK': ''}
>>> 
于 2013-01-14T18:48:46.193 に答える