将Python代码反汇编为字典

时间:2013-01-14 18:27:06

标签: python debugging disassembly

我想为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'])}

我该怎么做?

2 个答案:

答案 0 :(得分:2)

阅读the source code for the dis module,您会发现自己的反汇编很容易,并生成您喜欢的任何输出格式。这里有一些代码在代码对象中生成指令序列及其参数:

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)]

答案 1 :(得分:-1)

我认为这里的挑战是捕获dis的输出,而不是解析输出并创建字典。 我不会涵盖第二部分的原因是,字典的格式和字段(键,值)没有被提及而且很简单。

正如我所提到的,捕获dis OP的挑战是,它是打印而不是返回,但这可以通过上下文管理器捕获

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': ''}
>>>