跳转到主要内容

Python跨版本字节码解释器

项目描述

TravisCI CircleCI PyPI Installs Latest Version Supported Python Versions

x-python

这是一个用Python编写的CPython字节码解释器。

您可以使用它来

  • 了解CPython内部工作原理,因为它模拟了这些

  • 尝试额外的操作码,或改变运行时环境的方法

  • 作为沙箱环境尝试执行片段

  • 有一个Python程序可以运行多个版本的Python字节码。

  • 在动态模糊测试或协同执行中用于分析

能够从Python 3.10或Python 2.7解释器(假设没有新运行时的高级异步功能)运行到2.4的简单Python字节码,我认为这很酷。

此外,我还发现调试器中的沙箱环境很有趣。(注意:目前环境并没有很好地沙箱化,但我正在努力实现这一点,这有点具有挑战性。)

由于在调试器内部有一个单独的执行和跟踪栈,您可以在调试会话中间尝试一些事情,而不会影响真实执行。另一方面,如果一系列执行结果成功,则可能在某些情况下将其(复制)返回到CPython的执行栈。

我已经将 trepan3k 集成到这个解释器中,因此您有一个类似于pdb/gdb的调试器,并且可以单步执行字节码指令。

为了实验更快地支持跟踪回调,例如调试器中使用的回调,增加了一条指令以支持快速断点和在特定指令上设置断点,该指令不是在行边界上。我相信这可以也应该被移植回CPython,并且会带来好处。(Python 3.8支持保存额外的帧信息,原始操作码就存储在这里。只需要< cite>BRKPT操作码)

尽管这还遥不可及,但假设你想添加一个竞态检测器?在这里用Python原型化它可能更容易。(这假设解释器很好地支持线程,我怀疑它不支持)

上述内容暗示了另一个未探索的途径,即混合解释和直接CPython执行。事实上,现在确实存在这样的错误,但它们将被转化为特性。有些函数或类你可能不想在慢速解释器下运行,而有些你确实想运行。

示例

想知道当你编写一些简单代码时运行了哪些指令?试试这个

$ xpython -vc "x, y = 2, 3; x **= y"
INFO:xpython.vm:L. 1   @  0: LOAD_CONST (2, 3)
INFO:xpython.vm:       @  2: UNPACK_SEQUENCE 2
INFO:xpython.vm:       @  4: STORE_NAME (2) x
INFO:xpython.vm:       @  6: STORE_NAME (3) y
INFO:xpython.vm:L. 1   @  8: LOAD_NAME x
INFO:xpython.vm:       @ 10: LOAD_NAME y
INFO:xpython.vm:       @ 12: INPLACE_POWER (2, 3)
INFO:xpython.vm:       @ 14: STORE_NAME (8) x
INFO:xpython.vm:       @ 16: LOAD_CONST None
INFO:xpython.vm:       @ 18: RETURN_VALUE (None)

选项-c与Python的标志(作为字符串传递的程序)相同,而-v也是Python标志的类似物。在这里,它显示了运行的字节码指令。

注意,上述动态跟踪中的反汇编比Python的dis模块中的静态反汇编器显示的要多一些。特别是,STORE_NAME指令显示了存储的,例如指令偏移量4处的“2”,存储在名称x中。同样,INPLACE_POWER显示了操作数2和3,这是如何得出下一个指令的操作数,即STORE_NAME的值8。

还想了解更多,比如执行堆栈和块堆栈?添加另一个v

$ xpython -vvc "x, y = 2, 3; x **= y"

DEBUG:xpython.vm:make_frame: code=<code object <module> at 0x7f8018507db0, file "<string x, y = 2, 3; x **= y>", line 1>, callargs={}, f_globals=(<class 'dict'>, 140188140947488), f_locals=(<class 'NoneType'>, 93856967704000)
DEBUG:xpython.vm:<Frame at 0x7f80184c1e50: '<string x, y = 2, 3; x **= y>':1 @-1>
DEBUG:xpython.vm:  frame.stack: []
DEBUG:xpython.vm:  blocks     : []
INFO:xpython.vm:L. 1   @  0: LOAD_CONST (2, 3) <module> in <string x, y = 2, 3; x **= y>:1
DEBUG:xpython.vm:  frame.stack: [(2, 3)]
DEBUG:xpython.vm:  blocks     : []
INFO:xpython.vm:       @  2: UNPACK_SEQUENCE 2 <module> in <string x, y = 2, 3; x **= y>:1
DEBUG:xpython.vm:  frame.stack: [3, 2]
DEBUG:xpython.vm:  blocks     : []
INFO:xpython.vm:       @  4: STORE_NAME (2) x <module> in <string x, y = 2, 3; x **= y>:1
DEBUG:xpython.vm:  frame.stack: [3]
DEBUG:xpython.vm:  blocks     : []
INFO:xpython.vm:       @  6: STORE_NAME (3) y <module> in <string x, y = 2, 3; x **= y>:1
DEBUG:xpython.vm:  frame.stack: []
DEBUG:xpython.vm:  blocks     : []
INFO:xpython.vm:L. 1   @  8: LOAD_NAME x <module> in <string x, y = 2, 3; x **= y>:1
DEBUG:xpython.vm:  frame.stack: [2]
DEBUG:xpython.vm:  blocks     : []
INFO:xpython.vm:       @ 10: LOAD_NAME y <module> in <string x, y = 2, 3; x **= y>:1
DEBUG:xpython.vm:  frame.stack: [2, 3]
DEBUG:xpython.vm:  blocks     : []
INFO:xpython.vm:       @ 12: INPLACE_POWER (2, 3)  <module> in <string x, y = 2, 3; x **= y>:1
DEBUG:xpython.vm:  frame.stack: [8]
DEBUG:xpython.vm:  blocks     : []
INFO:xpython.vm:       @ 14: STORE_NAME (8) x <module> in <string x, y = 2, 3; x **= y>:1
DEBUG:xpython.vm:  frame.stack: []
DEBUG:xpython.vm:  blocks     : []
INFO:xpython.vm:       @ 16: LOAD_CONST None <module> in <string x, y = 2, 3; x **= y>:1
DEBUG:xpython.vm:  frame.stack: [None]
DEBUG:xpython.vm:  blocks     : []
INFO:xpython.vm:       @ 18: RETURN_VALUE (None)  <module> in <string x, y = 2, 3; x **= y>:1

想在终端中看到这种颜色化?使用这个trepan-xpy -xtrepan-xpy-example

假设你有Python 2.4字节码(或其他字节码),但你在运行Python 3.7?

$ xpython -v test/examples/assign-2.4.pyc
INFO:xpython.vm:L. 1   @  0: LOAD_CONST (2, 3)
INFO:xpython.vm:       @  3: UNPACK_SEQUENCE 2
INFO:xpython.vm:       @  6: STORE_NAME (2) x
INFO:xpython.vm:       @  9: STORE_NAME (3) y
INFO:xpython.vm:L. 2   @ 12: LOAD_NAME x
INFO:xpython.vm:       @ 15: LOAD_NAME y
INFO:xpython.vm:       @ 18: INPLACE_POWER (2, 3)
INFO:xpython.vm:       @ 19: STORE_NAME (8) x
INFO:xpython.vm:       @ 22: LOAD_CONST None
INFO:xpython.vm:       @ 25: RETURN_VALUE (None)

这里没有太大的变化,只是从3.6之后指令是2字节而不是1-或3字节指令。

上述示例显示了直线代码,所以你看到了所有指令。但不要将此与来自xdispydisasm之类的反汇编器混淆。下面的示例,带有条件分支示例,使这一点更清楚

$ xpython -vc "x = 6 if __name__ != '__main__' else 10"
INFO:xpython.vm:L. 1   @  0: LOAD_NAME __name__
INFO:xpython.vm:       @  2: LOAD_CONST __main__
INFO:xpython.vm:       @  4: COMPARE_OP ('__main__', '__main__') !=
INFO:xpython.vm:       @  6: POP_JUMP_IF_FALSE 12
                                               ^^ Note jump below
INFO:xpython.vm:       @ 12: LOAD_CONST 10
INFO:xpython.vm:       @ 14: STORE_NAME (10) x
INFO:xpython.vm:       @ 16: LOAD_CONST None
INFO:xpython.vm:       @ 18: RETURN_VALUE (None)

想了解更多状态和控制?请参阅trepan-xpy

状态

目前支持Python版本3.10到3.2以及2.7到2.4的字节码。Python的最新版本并没有实现所有操作码。这只是我的许多兴趣之一,因此支持可能很糟糕。我使用资金来帮助我指导我在修复问题上的注意力,这些问题在这个项目中非常多。

Byterun,这是基于这个的,非常棒。但它以微妙的方式作弊。

想用CPython编写一个非常小的解释器吗?

# get code somehow
exec(code)

这种作弊方式有点低俗,但这种作弊方式在 Byterun 中以更微妙的方式进行。例如,上面提到的例子依赖于内置函数 exec 来完成所有工作,而 Byterun 则依赖于各种类似类型的内置函数来支持指令码的解释。实际上,如果你正在 解释 的代码是上述代码,那么 Byterun 将使用其内置函数在 exec 函数调用内部运行代码,因此代码内部的所有字节码都不会被用于解释。

此外,像 exec 这样的内置函数以及其他内置模块会影响解释器命名空间。因此,这两个命名空间就会混合在一起。

其中一个例子是关于 import 的。参见 https://github.com/nedbat/byterun/issues/26。但还有其他情况。虽然我们还没有解决第26号问题中提到的 import 问题,但我们已经解决了类似的问题。

一些内置函数和 inpsect 模块需要内置类型,如 cell、traceback 或 frame 对象,并且它们不能使用相应的解释器类。在 Byterun 中,这是这样一个例子:类 __init__ 函数不会被追踪,因为依赖于内置函数 __build_class__。而 __build_class__ 需要一个本地函数,而不是一个可由解释器追踪的函数。参见 https://github.com/nedbat/byterun/pull/20

此外,Byterun 在接受字节码指令码时比较宽松,这可能对于某些Python版本无效,但对于另一个版本可能是有效的。我想这是可以的,因为你不期望无效的指令码出现在有效的字节码中。然而,当提取过程不准确时,可能会意外或错误地出现通过某种提取过程获得的代码。

相比之下,x-python 对接受的指令码更为严格。

Byterun 需要进行彻底的改造,以便能够扩展到支持更多 Python 版本的字节码,并能够在不同的 Python 版本之间运行字节码。具体来说,如果你期望运行的不是解释器正在运行的字节码,或者在新“wordcode”字节码上运行“字节”取向的字节码,或者相反,那么你就不能依赖 Python 的 dis 模块。

相比之下,在 x-python 中,正在解释的版本和正在运行的 Python 版本之间存在明确的区别。对指令码有更严格的控制,并且为每个 Python 版本保留指令码的实现。因此,当有无效内容时,我们会提前警告。你可以使用 Python 3.10(在很大程度上)运行回 Python 2.4 的字节码,这很令人惊讶,因为 3.10 的本地字节码每条指令是2个字节,而 2.4 的每条指令是1个或3个字节。

“在很大程度上”是指,正如上面提到的,解释器始终使用 Python 内置和库,而且大部分没有太大变化。通常,由于许多底层内置函数是相同的,解释器可以使用解释器内部。例如,内置函数如 range() 就是这种方式支持的。

因此,从比 Python 解释器使用的 Python 版本更新的版本解释字节码也是可能的。尽管 Python 2.7 不支持关键字参数或格式字符串,但它仍然可以解释使用这些构造创建的字节码。

这是可能的,因为这些特定功能更多的是语法糖,而不是运行时的扩展。例如,格式字符串基本上映射到使用 2.7 上可用的 format() 函数。

但像异步 I/O 和并发原语这样的新功能在旧版本中并不存在。因此,需要模拟这些功能,如果有兴趣或支持,这也是可能的。

您可以运行许多Python用于自身测试的测试,我也这么做!而且其中大多数都有效。目前,这个程序在Python 3.4或更早版本上运行最佳,那时的Python生活要简单得多。它在Python自己的测试套件中运行超过300个测试而没有任何问题。对于Python 3.6,这个数字下降到大约237;Python 3.9的情况更糟。

历史

这是Byterun的一个分支,而Byterun是一个纯Python实现的Python字节码执行虚拟机。Ned Batchelder开始这个项目(基于Paul Swartz的工作),以便更好地理解字节码,从而修复coverage.py中的分支覆盖率错误。

项目详情


下载文件

下载适合您平台文件的文件。如果您不确定要选择哪个,请了解有关安装包的更多信息。

源分布

x-python-1.5.1.tar.gz (493.7 kB 查看哈希)

上传时间

构建分布

x_python-1.5.1-py310-none-any.whl (124.5 kB 查看哈希)

上传时间 Python 3.10

x_python-1.5.1-py38-none-any.whl (124.5 kB 查看哈希)

上传时间 Python 3.8

x_python-1.5.1-py37-none-any.whl (124.5 kB 查看哈希)

上传时间 Python 3.7

x_python-1.5.1-py3-none-any.whl (124.5 kB 查看哈希)

上传时间 Python 3

x_python-1.5.1-py2-none-any.whl (124.6 kB 查看哈希)

上传时间 Python 2

由以下支持