2013-01-14 103 views
2

我想为Python程序开发一个小型调试工具。对于“动态切片”功能,我需要找到在语句中访问的变量,并找到这些变量的访问类型(读取或写入)。反汇编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

这本字典应该如何? – sloth

+0

你可以通过将'stdout'临时重定向到一个字符串对象来得到一个字符串的结果 - 从那里你需要将它解析成一个字典,但是我不知道字典应该是什么样的,所以这很难说... – mgilson

回答

0

我认为这里面临的挑战是捕获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': ''} 
>>> 
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)]