我该如何理解dis.dis的输出?

时间:2012-10-01 12:14:34

标签: python python-2.7

我想了解如何使用dis (the dissembler of Python bytecode)。具体来说,如何解释dis.dis(或dis.disassemble)的输出?

这是一个非常具体的例子(在Python 2.7.3中):

dis.dis("heapq.nsmallest(d,3)")

      0 BUILD_SET             24933
      3 JUMP_IF_TRUE_OR_POP   11889
      6 JUMP_FORWARD          28019 (to 28028)
      9 STORE_GLOBAL          27756 (27756)
     12 LOAD_NAME             29811 (29811)
     15 STORE_SLICE+0  
     16 LOAD_CONST            13100 (13100)
     19 STORE_SLICE+1

我看到JUMP_IF_TRUE_OR_POP等是字节码指令(虽然有趣的是,BUILD_SET没有出现在此列表中,但我希望它可以作为BUILD_TUPLE。我认为右边的数字是内存分配,左边的数字是goto数字......我注意到它们几乎每次增加3(但不是相当)。

如果我将dis.dis("heapq.nsmallest(d,3)")包裹在函数中:

def f_heapq_nsmallest(d,n):
    return heapq.nsmallest(d,n)

dis.dis("f_heapq(d,3)")

      0 BUILD_TUPLE            26719
      3 LOAD_NAME              28769 (28769)
      6 JUMP_ABSOLUTE          25640
      9 <44>                                      # what is <44> ?  
     10 DELETE_SLICE+1 
     11 STORE_SLICE+1 

2 个答案:

答案 0 :(得分:76)

您正在尝试反汇编包含源代码的字符串,但Python 2中的dis.dis不支持该字符串。对于字符串参数,它将字符串视为包含字节代码(请参阅函数{{3 }})。因此,您会看到基于将源代码误解为字节代码的无意义输出。

Python 3中的情况有所不同,其中disassemble_string in dis.py在反汇编之前:

Python 3.2.3 (default, Aug 13 2012, 22:28:10) 
>>> import dis
>>> dis.dis('heapq.nlargest(d,3)')
  1           0 LOAD_NAME                0 (heapq) 
              3 LOAD_ATTR                1 (nlargest) 
              6 LOAD_NAME                2 (d) 
              9 LOAD_CONST               0 (3) 
             12 CALL_FUNCTION            2 
             15 RETURN_VALUE         

在Python 2中,您需要自己编译代码,然后再将代码传递给dis.dis

Python 2.7.3 (default, Aug 13 2012, 18:25:43) 
>>> import dis
>>> dis.dis(compile('heapq.nlargest(d,3)', '<none>', 'eval'))
  1           0 LOAD_NAME                0 (heapq)
              3 LOAD_ATTR                1 (nlargest)
              6 LOAD_NAME                2 (d)
              9 LOAD_CONST               0 (3)
             12 CALL_FUNCTION            2
             15 RETURN_VALUE        

这些数字是什么意思?最左边的数字1是编译此字节代码的源代码中的行号。左侧列中的数字是字节码中指令的偏移量,右侧的数字是 opargs 。我们来看一下实际的字节码:

>>> co = compile('heapq.nlargest(d,3)', '<none>', 'eval')
>>> co.co_code.encode('hex')
'6500006a010065020064000083020053'

在字节代码的偏移0处,我们找到65LOAD_NAME的操作码,oparg 0000;然后(在偏移3处)6a是操作码LOAD_ATTR,其中0100是oparg,依此类推。请注意,opargs采用little-endian顺序,因此0100是数字1.未记录的opcode模块包含表opname,为您提供每个操作码的名称,{{1给你每个名字的操作码:

opmap

oparg的含义取决于操作码,对于完整的故事,您需要阅读CPython虚拟机dis.dis compiles a string argument的实现。对于>>> opcode.opname[0x65] 'LOAD_NAME' LOAD_NAME,oparg是代码对象的LOAD_ATTR属性的索引:

co_names

对于>>> co.co_names ('heapq', 'nlargest', 'd') ,它是代码对象的LOAD_CONST属性的索引:

co_consts

对于>>> co.co_consts (3,) ,它是传递给函数的参数个数,以16位编码,低字节中的普通参数数量,以及高字节中的关键字参数数量。

答案 1 :(得分:28)

我将答案重新发送到another question,以确保在Google搜索dis.dis()时找到它。

要完成伟大的Gareth Rees's answer,这里只是一个小的逐列摘要来解释反汇编字节码的输出。

例如,给定此功能:

def f(num):
    if num == 42:
        return True
    return False

这可能会被反汇编成(Python 3.6):

(1)|(2)|(3)|(4)|          (5)         |(6)|  (7)
---|---|---|---|----------------------|---|-------
  2|   |   |  0|LOAD_FAST             |  0|(num)
   |-->|   |  2|LOAD_CONST            |  1|(42)
   |   |   |  4|COMPARE_OP            |  2|(==)
   |   |   |  6|POP_JUMP_IF_FALSE     | 12|
   |   |   |   |                      |   |
  3|   |   |  8|LOAD_CONST            |  2|(True)
   |   |   | 10|RETURN_VALUE          |   |
   |   |   |   |                      |   |
  4|   |>> | 12|LOAD_CONST            |  3|(False)
   |   |   | 14|RETURN_VALUE          |   |

每列都有特定目的:

  1. 源代码中相应的行号
  2. 可选择表示当前指令已执行(例如,当字节码来自frame object时)
  3. 一个标签,表示可能 JUMP从之前的说明到此
  4. 字节码中地址对应于字节索引(那些是2的倍数,因为Python 3.6每个指令使用2个字节,而在以前的版本中可能会有所不同)
  5. 指令名称(也称为 opname ),每一个都在the dis module中简要解释,它们的实现可以在ceval.c(CPython的核心循环)< / LI>
  6. Python内部使用的指令的参数(如果有),用于获取某些常量或变量,管理堆栈,跳转到特定指令等。
  7. 指令参数的人性化解释