【问题标题】:How should I understand the output of dis.dis?我应该如何理解 dis.dis 的输出?
【发布时间】:2012-09-22 06:58:05
【问题描述】:

我想了解如何使用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 

【问题讨论】:

标签: python python-2.7


【解决方案1】:

我将我的答案转发给another question,以确保在谷歌搜索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的核心循环)
  6. 指令的参数(如果有)(如果有),Python 内部使用它来获取一些常量或变量、管理堆栈、跳转到特定指令等。
  7. 指令参数的人性化解释

【讨论】:

  • 这就是我要找的东西!但我在官方文档中没有找到,谢谢
  • 那个漂亮的输出表是手动创建的,还是有办法从dis 获得?我在文档中看到这个 docs.python.org/3/library/dis.html#dis.disco 条目表明我应该看到那些箭头和列,但在我的 repl 中我没有......
  • @d8aninja 我手动创建了表格以简化解释。 :) 你不应该期望看到dis 输出中的列。 (2) 和 (3) 上的箭头虽然是可能的,但这取决于您要反汇编的代码。
【解决方案2】:

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

在 Python 3 中情况有所不同,其中 dis.dis compiles a string argument 在反汇编之前:

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 的操作码,操作码为 0000;然后(在偏移量 3 处)6a 是操作码 LOAD_ATTR0100 是 oparg,依此类推。请注意,opargs 是小端顺序,因此0100 是数字 1。未记录的 opcode 模块包含表 opname 为您提供每个操作码的名称,opmap 为您提供每个操作码名称:

>>> opcode.opname[0x65]
'LOAD_NAME'

oparg 的含义取决于操作码,完整的故事你需要阅读 CPython 虚拟机的实现in ceval.c。对于LOAD_NAMELOAD_ATTR,oparg 是代码对象的co_names 属性的索引:

>>> co.co_names
('heapq', 'nlargest', 'd')

对于LOAD_CONST,它是代码对象的co_consts 属性的索引:

>>> co.co_consts
(3,)

对于CALL_FUNCTION,它是传递给函数的参数个数,以 16 位编码,低字节为普通参数个数,高字节为关键字参数个数。

【讨论】:

猜你喜欢
  • 2021-07-06
  • 2011-05-01
  • 2018-07-10
  • 1970-01-01
  • 1970-01-01
  • 2013-09-15
  • 2018-11-26
  • 2020-04-13
  • 2020-09-14
相关资源
最近更新 更多