32.12. dis — Python 字节码的反汇编程序

源代码: Lib/dis.py


dis模块pass分解支持 CPython bytecode的分析。此模块作为 Importing 的 CPython 字节码在文件Include/opcode.h中定义,并由编译器和解释器使用。

CPython 实现细节: 字节码是 CPython 解释器的实现细节!无法保证不会在 Python 版本之间添加,删除或更改字节码。不应考虑在 Python VM 或 Python 版本之间使用此模块。

示例:给定函数myfunc()

def myfunc(alist):
    return len(alist)

以下命令可用于反汇编myfunc()

>>> dis.dis(myfunc)
  2           0 LOAD_GLOBAL              0 (len)
              3 LOAD_FAST                0 (alist)
              6 CALL_FUNCTION            1
              9 RETURN_VALUE

(“ 2”是行号)。

dis模块定义以下函数和常量:

  • dis. dis([* bytesource *])

    • 反汇编* bytesource *对象。 * bytesource *可以表示模块,类,方法,函数或代码对象。对于模块,它将分解所有Function。对于一个类,它将分解所有方法。对于单个代码序列,每个字节码指令打印一行。如果未提供任何对象,则它将反汇编最后的回溯。
  • dis. distb([* tb *])

    • 如果没有pass最后的回溯,则反汇编回溯的堆栈顶部函数。指示导致异常的指令。
  • dis. disassemble(* code * [,* lasti *])

    • 反汇编代码对象,如果提供了* lasti *,则指示最后一条指令。输出分为以下几列:
  • 行号,用于每行的第一条指令

  • 当前指令,表示为-->

  • 标有>>的标记说明,

  • 指令的地址,

  • 操作代码名称,

  • 操作参数,以及

  • 括号中参数的解释。

参数解释可识别局部和全局变量名称,常量值,分支目标和比较运算符。

  • dis. disco(* code * [,* lasti *])

    • disassemble()的同义词。键入更方便,并保持与早期 Python 版本的兼容性。
  • dis. findlinestarts(* code *)

    • 此生成器函数使用代码对象* code *的co_firstlinenoco_lnotab属性来查找源代码中行首的偏移量。它们生成为(offset, lineno)对。
  • dis. findlabels(* code *)

    • 检测代码对象* code *中所有作为跳转目标的偏移量,并返回这些偏移量的列表。
  • dis. opname

    • 操作名称序列,可使用字节码索引。
  • dis. opmap

    • 字典将操作名称 Map 到字节码。
  • dis. cmp_op

    • 所有比较操作名称的 Sequences。
  • dis. hasconst

    • 访问常量的字节码序列。
  • dis. hasfree

    • 访问自由变量的字节码序列。
  • dis. hasname

    • 按名称访问属性的字节码序列。
  • dis. hasjrel

    • 具有相对跳转目标的字节码序列。
  • dis. hasjabs

    • 具有绝对跳转目标的字节码序列。
  • dis. haslocal

    • 访问局部变量的字节码序列。
  • dis. hascompare

    • 布尔运算的字节码序列。

32.12.1. Python 字节码指令

Python 编译器当前生成以下字节码指令。

  • STOP_CODE ( ** )

    • 指示编译器未使用的代码结尾。
  • NOP ( ** )

    • 不执行任何代码。字节码优化器用作占位符。
  • POP_TOP ( ** )

    • 删除堆栈顶部(TOS)项。
  • ROT_TWO ( ** )

    • 交换两个最上面的堆栈项。
  • ROT_THREE ( ** )

    • 将第二个和第三个堆栈项目向上提起一个位置,从上向下移动到位置三。
  • ROT_FOUR ( ** )

    • 将第二,第三和第四堆叠物品向上提升一个位置,从顶部向下移至第四位置。
  • DUP_TOP ( ** )

    • 将引用复制到堆栈顶部。

一元运算占据堆栈的顶部,应用该运算,然后将结果推回堆栈。

  • UNARY_POSITIVE ( ** )

    • 实现TOS = +TOS
  • UNARY_NEGATIVE ( ** )

    • 实现TOS = -TOS
  • UNARY_NOT ( ** )

    • 实现TOS = not TOS
  • UNARY_CONVERT ( ** )

    • 实现``TOS =`TOS```。
  • UNARY_INVERT ( ** )

    • 实现TOS = ~TOS
  • GET_ITER ( ** )

    • 实现TOS = iter(TOS)

二进制操作从堆栈中删除堆栈的顶部(TOS)和第二高的堆栈项(TOS1)。他们执行操作,然后将结果放回堆栈。

  • BINARY_POWER ( ** )

    • 实现TOS = TOS1 ** TOS
  • BINARY_MULTIPLY ( ** )

    • 实现TOS = TOS1 * TOS
  • BINARY_DIVIDE ( ** )

    • from __future__ import division无效时实现TOS = TOS1 / TOS
  • BINARY_FLOOR_DIVIDE ( ** )

    • 实现TOS = TOS1 // TOS
  • BINARY_TRUE_DIVIDE ( ** )

    • from __future__ import division生效时实现TOS = TOS1 / TOS
  • BINARY_MODULO ( ** )

    • 实现TOS = TOS1 % TOS
  • BINARY_ADD ( ** )

    • 实现TOS = TOS1 + TOS
  • BINARY_SUBTRACT ( ** )

    • 实现TOS = TOS1 - TOS
  • BINARY_SUBSCR ( ** )

    • 实现TOS = TOS1[TOS]
  • BINARY_LSHIFT ( ** )

    • 实现TOS = TOS1 << TOS
  • BINARY_RSHIFT ( ** )

    • 实现TOS = TOS1 >> TOS
  • BINARY_AND ( ** )

    • 实现TOS = TOS1 & TOS
  • BINARY_XOR ( ** )

    • 实现TOS = TOS1 ^ TOS
  • BINARY_OR ( ** )

    • 实现TOS = TOS1 | TOS

就地操作类似于二进制操作,因为它们删除了 TOS 和 TOS1,并将结果推回堆栈,但是当 TOS1 支持它时,该操作是就地完成的,并且结果 TOS 可能是(但不具有是)原始 TOS1.

  • INPLACE_POWER ( ** )

    • 实现就地TOS = TOS1 ** TOS
  • INPLACE_MULTIPLY ( ** )

    • 实现就地TOS = TOS1 * TOS
  • INPLACE_DIVIDE ( ** )

    • from __future__ import division无效时实现就地TOS = TOS1 / TOS
  • INPLACE_FLOOR_DIVIDE ( ** )

    • 实现就地TOS = TOS1 // TOS
  • INPLACE_TRUE_DIVIDE ( ** )

    • from __future__ import division生效时就地实现TOS = TOS1 / TOS
  • INPLACE_MODULO ( ** )

    • 实现就地TOS = TOS1 % TOS
  • INPLACE_ADD ( ** )

    • 实现就地TOS = TOS1 + TOS
  • INPLACE_SUBTRACT ( ** )

    • 实现就地TOS = TOS1 - TOS
  • INPLACE_LSHIFT ( ** )

    • 实现就地TOS = TOS1 << TOS
  • INPLACE_RSHIFT ( ** )

    • 实现就地TOS = TOS1 >> TOS
  • INPLACE_AND ( ** )

    • 实现就地TOS = TOS1 & TOS
  • INPLACE_XOR ( ** )

    • 实现就地TOS = TOS1 ^ TOS
  • INPLACE_OR ( ** )

    • 实现就地TOS = TOS1 | TOS

切片操作码最多包含三个参数。

  • SLICE+0 ( ** )

    • 实现TOS = TOS[:]
  • SLICE+1 ( ** )

    • 实现TOS = TOS1[TOS:]
  • SLICE+2 ( ** )

    • 实现TOS = TOS1[:TOS]
  • SLICE+3 ( ** )

    • 实现TOS = TOS2[TOS1:TOS]

切片分配甚至需要一个附加参数。就像任何语句一样,它们什么也不放。

  • STORE_SLICE+0 ( ** )

    • 实现TOS[:] = TOS1
  • STORE_SLICE+1 ( ** )

    • 实现TOS1[TOS:] = TOS2
  • STORE_SLICE+2 ( ** )

    • 实现TOS1[:TOS] = TOS2
  • STORE_SLICE+3 ( ** )

    • 实现TOS2[TOS1:TOS] = TOS3
  • DELETE_SLICE+0 ( ** )

    • 实现del TOS[:]
  • DELETE_SLICE+1 ( ** )

    • 实现del TOS1[TOS:]
  • DELETE_SLICE+2 ( ** )

    • 实现del TOS1[:TOS]
  • DELETE_SLICE+3 ( ** )

    • 实现del TOS2[TOS1:TOS]
  • STORE_SUBSCR ( ** )

    • 实现TOS1[TOS] = TOS2
  • DELETE_SUBSCR ( ** )

    • 实现del TOS1[TOS]

Miscellaneous opcodes.

  • PRINT_EXPR ( ** )

    • 为交互模式实现表达式语句。从堆栈中删除 TOS 并进行打印。在非交互模式下,表达式语句以POP_TOP终止。
  • PRINT_ITEM ( ** )

    • 将 TOS 打印到绑定到sys.stdout的类似文件的对象。 print语句中的每个项目都有一条这样的指令。
  • PRINT_ITEM_TO ( ** )

    • 类似于PRINT_ITEM,但是将项目从 TOS 每秒打印到 TOS 的文件状对象。扩展打印语句使用它。
  • PRINT_NEWLINE ( ** )

    • sys.stdout上打印新行。除非该语句以逗号结尾,否则它将作为print语句的最后一个操作生成。
  • PRINT_NEWLINE_TO ( ** )

    • 类似于PRINT_NEWLINE,但是在 TOS 上的类似文件的对象上打印新行。扩展打印语句使用它。
  • BREAK_LOOP ( ** )

    • 由于break语句而终止循环。
  • CONTINUE_LOOP(* target *)

    • 由于continue语句而 continue 循环。 * target *是要跳转到的地址(应该是FOR_ITER指令)。
  • LIST_APPEND(* i *)

    • 拨打list.append(TOS[-i], TOS)。用于实现列表推导。弹出附加值时,列表对象仍保留在堆栈上,因此可用于循环的进一步迭代。
  • LOAD_LOCALS ( ** )

    • 将引用推送到堆栈上当前作用域的本地变量。这在代码中用于类定义:在评估类主体之后,将本地变量传递给类定义。
  • RETURN_VALUE ( ** )

    • 与 TOS 一起返回给函数的调用者。
  • YIELD_VALUE ( ** )

  • IMPORT_STAR ( ** )

    • 将所有不以'_'开头的符号直接从模块 TOS 加载到本地名称空间。加载所有名称后,将弹出该模块。此操作码实现from module import *
  • EXEC_STMT ( ** )

    • 实现exec TOS2,TOS1,TOS。编译器使用None填充缺少的可选参数。
  • POP_BLOCK ( ** )

    • 从块堆栈中删除一个块。每帧都有一堆块,表示嵌套循环,try 语句等。
  • END_FINALLY ( ** )

    • 终止finally子句。解释器回想是否必须重新引发该异常,或者该函数是否返回,并 continue 执行外部下一个块。
  • BUILD_CLASS ( ** )

    • 创建一个新的类对象。 TOS 是方法字典,TOS1 是 Base Class 名称的 Tuples,TOS2 是类名称。
  • SETUP_WITH(* delta *)

    • 此操作码在 with 块开始之前执行若干操作。首先,它从上下文 Management 器中加载exit()并将其推入堆栈,以供WITH_CLEANUP以后使用。然后,调用enter(),并压入指向* delta *的 finally 块。最后,调用 enter 方法的结果被压入堆栈。下一个操作码将忽略它(POP_TOP),或将其存储在一个或多个变量中(STORE_FASTSTORE_NAMEUNPACK_SEQUENCE)。
  • WITH_CLEANUP ( ** )

    • with语句块退出时清理堆栈。堆栈顶部是 1-3 个值,指示如何/为什么 Importingfinally 子句:
  • TOP = None

  • (TOP,SECOND)=(WHY_{RETURN,CONTINUE}),检索

  • TOP = WHY_*;在它下面没有撤回

  • (TOP,SECOND,THIRD)= exc_info()

它们下面是 EXIT,即上下文 Management 器的exit()绑定方法。

在最后一种情况下,将调用EXIT(TOP, SECOND, THIRD),否则将调用EXIT(None, None, None)

将 EXIT 从堆栈中删除,使其上方的值保持相同的 Sequences。另外,如果堆栈表示一个异常,并且该函数调用返回一个“ true”值,则此信息将“转换”,以防止END_FINALLY引发该异常。 (但仍应恢复 nonlocal 操作.)

以下所有操作码都需要参数。一个参数是两个字节,最后一个有效字节。

  • STORE_NAME(* namei *)

    • 实现name = TOS。 * namei name *在代码对象的属性co_names中的索引。如果可能,编译器将try使用STORE_FASTSTORE_GLOBAL
  • DELETE_NAME(* namei *)

    • 实现del name,其中* namei *是代码对象的co_names属性的索引。
  • UNPACK_SEQUENCE(* count *)

    • 将 TOS 打包成* count *个单独的值,这些值从右到左放入堆栈中。
  • DUP_TOPX(* count *)

    • 重复* count 个项目,使其 Sequences 保持一致。由于实施限制, count *应该在 1 到 5 之间(含 1 和 5)。
  • STORE_ATTR(* namei *)

    • 实现TOS.name = TOS1,其中* namei *是co_names中 name 的索引。
  • DELETE_ATTR(* namei *)

    • 使用* namei *作为co_names的索引来实现del TOS.name
  • STORE_GLOBAL(* namei *)

    • 用作STORE_NAME,但将名称存储为全局名称。
  • DELETE_GLOBAL(* namei *)

    • 用作DELETE_NAME,但删除全局名称。
  • LOAD_CONST(* consti *)

    • co_consts[consti]压入堆栈。
  • LOAD_NAME(* namei *)

    • 将与co_names[namei]关联的值压入堆栈。
  • BUILD_TUPLE(* count *)

    • 创建一个从堆栈中消耗* count *个项目的 Tuples,并将生成的 Tuples 推入堆栈。
  • BUILD_LIST(* count *)

    • 用作BUILD_TUPLE,但创建一个列表。
  • BUILD_SET(* count *)

    • 用作BUILD_TUPLE,但创建一个集合。

2.7 版的新Function。

  • BUILD_MAP(* count *)

    • 将新的字典对象压入堆栈。字典已预先设置大小,可以容纳* count *个条目。
  • LOAD_ATTR(* namei *)

    • getattr(TOS, co_names[namei])代替 TOS。
  • COMPARE_OP(* opname *)

    • 执行布尔运算。可以在cmp_op[opname]中找到操作名称。
  • IMPORT_NAME(* namei *)

    • 导入模块co_names[namei]。弹出 TOS 和 TOS1 并提供import()的* fromlist level *参数。模块对象被压入堆栈。当前的名称空间不受影响:对于正确的 import 语句,随后的STORE_FAST指令将修改该名称空间。
  • IMPORT_FROM(* namei *)

    • 从 TOS 中找到的模块中加载属性co_names[namei]。结果对象被压入堆栈,随后由STORE_FAST指令存储。
  • JUMP_FORWARD(* delta *)

    • 将字节码计数器增加* delta *。
  • POP_JUMP_IF_TRUE(* target *)

    • 如果 TOS 为 true,则将字节码计数器设置为* target *。 TOS 弹出。
  • POP_JUMP_IF_FALSE(* target *)

    • 如果 TOS 为 false,则将字节码计数器设置为* target *。 TOS 弹出。
  • JUMP_IF_TRUE_OR_POP(* target *)

    • 如果 TOS 为 true,则将字节码计数器设置为* target *并将 TOS 留在堆栈中。否则(TOS 为假),将弹出 TOS。
  • JUMP_IF_FALSE_OR_POP(* target *)

    • 如果 TOS 为 false,则将字节码计数器设置为* target *并将 TOS 留在堆栈中。否则(TOS 为 true),将弹出 TOS。
  • JUMP_ABSOLUTE(* target *)

    • 将字节码计数器设置为* target *。
  • FOR_ITER(* delta *)

    • TOSiterator。调用其next()方法。如果产生一个新值,则将其压入堆栈(将迭代器保留在其下方)。如果迭代器指示已用尽,则弹出TOS,并且字节码计数器增加* delta *。
  • LOAD_GLOBAL(* namei *)

    • 将名为co_names[namei]的全局变量加载到堆栈中。
  • SETUP_LOOP(* delta *)

    • 将要循环的块压入块堆栈。该块跨越当前指令,大小为* delta *字节。
  • SETUP_EXCEPT(* delta *)

    • 将 try 块从 try-except 子句推入块堆栈。 * delta *指向第一个除外块。
  • SETUP_FINALLY(* delta *)

    • 将 try 块从 try-except 子句推入块堆栈。 * delta *指向 finally 块。
  • STORE_MAP ( ** )

    • 将键和值对存储在字典中。弹出键和值,同时将字典保留在堆栈中。
  • LOAD_FAST(* var_num *)

    • 将对本地co_varnames[var_num]的引用压入堆栈。
  • STORE_FAST(* var_num *)

    • 将 TOS 存储到本地co_varnames[var_num]
  • DELETE_FAST(* var_num *)

    • 删除本地co_varnames[var_num]
  • LOAD_CLOSURE(* i *)

    • 将引用推入单元格的插槽* i 和可用变量存储中包含的单元格。如果 i 小于 co_cellvars *的长度,则变量的名称为co_cellvars[i]。否则为co_freevars[i - len(co_cellvars)]
  • LOAD_DEREF(* i *)

    • 加载包含在单元的插槽* i *和可用变量存储中的单元。将引用推送到单元格包含在堆栈中的对象。
  • STORE_DEREF(* i *)

    • 将 TOS 存储到单元的插槽* i *中包含的单元中,并释放变量存储。
  • SET_LINENO(* lineno *)

    • 该操作码已过时。
  • RAISE_VARARGS(* argc *)

    • 引发异常。 * argc *指示引发语句的参数数,范围为 0 到 3.处理程序将发现回溯为 TOS2,参数为 TOS1,异常为 TOS。
  • CALL_FUNCTION(* argc *)

    • 调用可调用对象。 * argc *的低字节指示位置参数的数量,高字节指示关键字参数的数量。堆栈在顶部包含关键字参数(如果有),在其下方包含位置参数(如果有),然后在其下方包含要调用的可调用对象。每个关键字参数在堆栈上都用两个值表示:参数的名称及其值,参数的值在堆栈上的名称上方。位置参数按传递到可调用对象的 Sequences 推送,最右边的位置参数在顶部。 CALL_FUNCTION将所有参数和可调用对象弹出堆栈,使用这些参数调用可调用对象,然后推入可调用对象返回的返回值。
  • MAKE_FUNCTION(* argc *)

    • 将新的Function对象压入堆栈。 TOS 是与Function关联的代码。函数对象定义为具有* argc *默认参数,可在 TOS 下找到。
  • MAKE_CLOSURE(* argc *)

    • 创建一个新的函数对象,设置其* func_closure 插槽,并将其压入堆栈。 TOS 是与函数关联的代码,TOS1 是包含用于闭包的自由变量的单元格的 Tuples。该函数还具有 argc *默认参数,可在单元格下面找到。
  • BUILD_SLICE(* argc *)

    • 将切片对象压入堆栈。 * argc *必须为 2 或 3.如果为 2,则推入slice(TOS1, TOS);如果为 3,则推slice(TOS2, TOS1, TOS)。有关更多信息,请参见slice()内置函数。
  • EXTENDED_ARG(* ext *)

    • 在操作码前加一个太大的参数以至于不能容纳默认的两个字节的前缀。 * ext 包含两个额外的字节,这些字节与随后的操作码的参数一起组成一个四个字节的参数, ext *是两个最高有效字节。
  • CALL_FUNCTION_VAR(* argc *)

    • 调用可调用对象,类似于CALL_FUNCTION。 * argc *表示关键字和位置参数的数量,与CALL_FUNCTION相同。堆栈的顶部包含一个可迭代的对象,该对象包含其他位置参数。与CALL_FUNCTION相同的是关键字参数(如果有的话),位置参数(如果有的话)和可调用对象(在其下)。在调用可调用对象之前,将可迭代对象“解包”,并将其内容附加到传入的位置参数中。在计算argc的值时,将忽略可迭代对象。
  • CALL_FUNCTION_KW(* argc *)

    • 调用可调用对象,类似于CALL_FUNCTION。 * argc *表示关键字和位置参数的数量,与CALL_FUNCTION相同。堆栈的顶部包含一个包含其他关键字参数的 Map 对象。与CALL_FUNCTION相同的是关键字参数(如果有的话),位置参数(如果有的话)和可调用对象(在其下)。在调用 callable 之前,将“解压缩”堆栈顶部的 Map 对象,并将其内容附加到传入的关键字参数中。计算argc的值时,将忽略堆栈顶部的 Map 对象。
  • CALL_FUNCTION_VAR_KW(* argc *)

    • 调用可调用对象,类似于CALL_FUNCTION_VARCALL_FUNCTION_KW。 * argc *表示关键字和位置参数的数量,与CALL_FUNCTION相同。根据CALL_FUNCTION_KW,堆栈的顶部包含一个 Map 对象。根据CALL_FUNCTION_VAR,下面是一个可迭代的对象。与CALL_FUNCTION相同的是关键字参数(如果有的话),位置参数(如果有的话)和可调用对象。在调用 callable 之前,分别将 Map 对象和可迭代对象“解包”,并将它们的内容分别作为关键字和位置参数传递,与CALL_FUNCTION_VARCALL_FUNCTION_KW相同。计算argc的值时,将忽略 Map 对象和可迭代对象。
  • HAVE_ARGUMENT ( ** )

    • 这不是 true 的操作码。它标识不带参数< HAVE_ARGUMENT的操作码和带参数>= HAVE_ARGUMENT的操作码之间的分界线。