小甲鱼 发表于 2024-6-23 15:50:08

dis — Python 字节码反汇编器

dis — Python 字节码反汇编器

快速入门

Python 提供了 dis 模块,该模块支持通过反汇编以分析 CPython 字节码。

该模块接收的 CPython 字节码被定义在文件 Include/opcode.h 中,并由编译器和解释器所使用。

CPython 实现细节:

字节码是 CPython 解释器的实现细节。

不能保证字节码在 Python 不同版本之间不会有添加、删除或更改。

因此,大家不应该认为该模块能跨 Python 虚拟机或 Python 版本而正常工作(讲人话就是不同环境及不同版本之间它是有区别的,这在本文档的注释中会有详细提示)。

版本 3.6 中的变更:每条指令使用 2 个字节,以前每条指令的字节数不同。

版本 3.10 中的变更:跳转、异常处理和循环指令的参数现在是指令偏移量而不是字节偏移量。

版本 3.11 中的变更:某些指令伴有一个或多个内联缓存条目,这些缓存条目以 CACHE 指令的形式存在。默认情况下,这些指令是隐藏的,但可以通过传递 show_caches=True 给任何 dis 工具来显示。此外,解释器现在会根据不同的运行时条件调整字节码。可以通过传递 adaptive=True 来显示自适应字节码。

版本 3.12 中的变更:跳转指令的参数是相对于跳转指令后立即出现的 CACHE 条目的目标指令的偏移量。因此,CACHE 指令的存在对于前向跳转是透明的,但在推理后向跳转时需要考虑。


示例

给定函数 myfunc() 如下:

def myfunc(alist):
    return len(alist)
我们可以使用以下命令,显示 myfunc() 的反汇编结果:

>>> dis.dis(myfunc)
2         0 RESUME                   0

3         2 LOAD_GLOBAL            1 (NULL + len)
             12 LOAD_FAST                0 (alist)
             14 CALL                     1
             22 RETURN_VALUE
(“2” 是行号)


命令行界面

我们可以通过命令行脚本调用 dis 模块:

python -m dis [-h]
接受以下选项:


[*]-h, --help:显示使用说明并退出。

如果指定了 infile,其反汇编代码将写入标准输出;否则,将对从标准输入接收的编译源代码进行反汇编。

字节码分析

版本 3.4 中新增。

字节码分析 API 允许将 Python 代码片段包装在 Bytecode 对象中,该对象提供对编译代码细节的便捷访问。

class dis.Bytecode(x, *, first_line=None, current_offset=None, show_caches=False, adaptive=False)

分析与函数、生成器、异步生成器、协程、方法、源代码字符串或代码对象(由 compile() 返回)对应的字节码。

这是许多列出的函数(特别是 get_instructions())的便捷包装,因为迭代 Bytecode 实例会生成字节码操作的 Instruction 实例。

如果 first_line 不为 None,它表示反汇编代码中第一条源代码行的行号。否则,源代码信息(如果有)直接取自反汇编代码对象。

如果 current_offset 不为 None,它指的是反汇编代码中的指令偏移量。设置此值意味着 dis() 将在指定的操作码旁显示一个 “当前指令” 标记。

如果 show_caches 为 True,则 dis() 将显示解释器用于优化字节码的内联缓存条目。

如果 adaptive 为 True,则 dis() 将显示可能与原始字节码不同的特化字节码。

classmethod from_traceback(tb, *, show_caches=False)

从给定的回溯构造一个 Bytecode 实例,并将 current_offset 设置为引发异常的指令。

codeobj

编译后的代码对象。

first_line

代码对象的第一条源代码行(如果有的话)。

dis()

返回格式化的字节码操作视图(与 dis.dis() 打印的相同,但返回多行字符串)。

info()

返回带有代码对象详细信息的格式化多行字符串,如 code_info()。

版本 3.7 中的变更:现在可以处理协程和异步生成器对象。

版本 3.11 中的变更:添加了 show_caches 和 adaptive 参数。

示例:

>>> bytecode = dis.Bytecode(myfunc)
>>> for instr in bytecode:
>>>   print(instr.opname)

RESUME
LOAD_GLOBAL
LOAD_FAST
CALL
RETURN_VALUE

分析函数

dis 模块还定义了以下分析函数,这些函数直接将输入转换为所需的输出。如果只执行单个操作,这些函数非常有用,因为中间的分析对象没有用处:

dis.code_info(x)

返回带有所提供的函数、生成器、异步生成器、协程、方法、源代码字符串或代码对象详细信息的格式化多行字符串。

注意:代码信息字符串的确切内容高度依赖于实现,可能会在不同的 Python 虚拟机或 Python 版本之间随意更改。

版本 3.2 中新增。

版本 3.7 中的变更:现在可以处理协程和异步生成器对象。


dis.show_code(x, *, file=None)

将所提供的函数、方法、源代码字符串或代码对象的详细代码信息打印到文件(如果未指定 file,则打印到 sys.stdout)。

这是 print(code_info(x), file=file) 的便捷代码,旨在在解释器提示符下进行交互式探索。

版本 3.2 中新增。

版本 3.4 中的变更:添加了 file 参数。


dis.dis(x=None, *, file=None, depth=None, show_caches=False, adaptive=False)

反汇编 x 对象。x 可以表示模块、类、方法、函数、生成器、异步生成器、协程、代码对象、源代码字符串或一串原始字节码。对于模块,它会反汇编所有函数。对于类,它会反汇编所有方法(包括类方法和静态方法)。对于代码对象或原始字节码序列,它会打印每条字节码指令一行。还会递归反汇编嵌套的代码对象。这些可以包括生成器表达式、嵌套函数、嵌套类的主体和用于注释范围的代码对象。字符串首先用内置函数 compile() 编译成代码对象,然后再进行反汇编。如果没有提供对象,该函数会反汇编上一个回溯。

如果提供了 file 参数,反汇编的内容将作为文本写入该文件,否则写入 sys.stdout。

递归的最大深度由 depth 限制,除非它为 None。depth=0 表示不进行递归。

如果 show_caches 为 True,此函数将显示解释器用于优化字节码的内联缓存条目。

如果 adaptive 为 True,此函数将显示可能与原始字节码不同的特化字节码。

版本 3.4 中的变更:添加了 file 参数。

版本 3.7 中的变更:实现了递归反汇编并添加了 depth 参数。

版本 3.7 中的变更:现在可以处理协程和异步生成器对象。

版本 3.11 中的变更:添加了 show_caches 和 adaptive 参数。


dis.distb(tb=None, *, file=None, show_caches=False, adaptive=False)

反汇编回溯的栈顶函数,如果未传递回溯,则使用上一个回溯。指示引发异常的指令。

如果提供了 file 参数,反汇编的内容将作为文本写入该文件,否则写入 sys.stdout。

版本 3.4 中的变更:添加了 file 参数。

版本 3.11 中的变更:添加了 show_caches 和 adaptive 参数。


dis.disassemble(code, lasti=-1, *, file=None, show_caches=False, adaptive=False)

dis.disco(code, lasti=-1, *, file=None, show_caches=False, adaptive=False)

反汇编一个代码对象,如果提供了 lasti,则指示最后一条指令。输出分为以下几列:


[*]行号(每行的第一条指令)
[*]当前指令(标记为 -->)
[*]标记的指令(标记为 >>)
[*]指令的地址
[*]操作码名称
[*]操作参数
[*]参数的解释(如局部和全局变量名、常量值、分支目标和比较操作符)

如果提供了 file 参数,反汇编的内容将作为文本写入该文件,否则写入 sys.stdout。

版本 3.4 中的变更:添加了 file 参数。

版本 3.11 中的变更:添加了 show_caches 和 adaptive 参数。


dis.get_instructions(x, *, first_line=None, show_caches=False, adaptive=False)

返回一个迭代器,用于迭代供应的函数、方法、源代码字符串或代码对象中的指令。

迭代器生成一系列 Instruction 命名元组,提供每个操作的详细信息。

如果 first_line 不为 None,它表示反汇编代码中第一条源代码行的行号。否则,源代码信息(如果有)直接取自反汇编代码对象。

show_caches 和 adaptive 参数的工作方式与 dis() 相同。

版本 3.4 中新增。

版本 3.11 中的变更:添加了 show_caches 和 adaptive 参数。


dis.findlinestarts(code)

该生成器函数使用代码对象的 co_lines() 方法查找源代码行的起始偏移量。生成 (offset, lineno) 对。

版本 3.6 中的变更:行号可以减少。之前它们总是增加。

版本 3.10 中的变更:使用 PEP 626 co_lines() 方法代替代码对象的 co_firstlineno 和 co_lnotab 属性。


dis.findlabels(code)

检测原始编译字节码字符串中的所有跳转目标偏移量,并返回这些偏移量的列表。


dis.stack_effect(opcode, oparg=None, *, jump=None)

计算带有参数 oparg 的操作码 opcode 的栈效果。

如果代码有跳转目标并且 jump 为 True,stack_effect() 将返回跳转的栈效果。如果 jump 为 False,它将返回不跳转的栈效果。如果 jump 为 None(默认值),它将返回两种情况下的最大栈效果。

版本 3.4 中新增。

版本 3.8 中的变更:添加了 jump 参数。


Python 字节码指令

get_instructions() 函数和 Bytecode 类提供字节码指令的详细信息,作为 Instruction 实例。

class dis.Instruction

提供字节码操作的详细信息。


[*]opcode:操作的数字代码,对应于下列的操作码值和 Opcode 集合中的字节码值。
[*]opname:操作的人类可读名称。
[*]arg:操作的数字参数(如果有),否则为 None。
[*]argval:解析的参数值(如果有),否则为 None。
[*]argrepr:操作参数的人类可读描述(如果有),否则为空字符串。
[*]offset:字节码序列中操作的起始索引。
[*]starts_line:此操作码开始的行(如果有),否则为 None。
[*]is_jump_target:如果其他代码跳转到此处,则为 True,否则为 False。
[*]positions:包含此指令覆盖的起始和结束位置的 dis.Positions 对象。

版本 3.4 中新增。

版本 3.11 中的变更:添加了 positions 字段。


class dis.Positions

如果信息不可用,某些字段可能为 None。


[*]lineno:行号。
[*]end_lineno:结束行号。
[*]col_offset:列偏移量。
[*]end_col_offset:结束列偏移量。

版本 3.11 中新增。


Python 编译器生成的字节码指令

在下文中,我们将把解释器栈称为 STACK,并描述它的操作方式就像它是一个 Python 列表。栈的顶部对应于 STACK[-1]。














页: [1]
查看完整版本: dis — Python 字节码反汇编器