Python 跨版本字节码解释器
项目描述
x-蟒蛇
这是一个用 Python 编写的 CPython 字节码解释器。
您可以使用它来:
了解 CPython 的内部是如何工作的,因为这个模型
尝试使用其他操作码或更改运行时环境的方法
用作尝试执行片段的沙盒环境
拥有一个运行多个 Python 字节码版本的 Python 程序。
用于动态 fuzzer 或 coholic 执行以进行分析
从 Python 3.10 或 Python 3.10 从 Python 2.7 解释器运行简单的 Python 字节码的能力(假设没有新运行时的花哨的异步功能)我觉得很整洁。
此外,我觉得调试器中沙盒环境的想法很有趣。(注意:目前环境还没有很好地沙盒化,但我正在朝着这个方向努力,这有点挑战性。)
由于在调试器中存在单独的执行和回溯堆栈,因此您可以在调试会话的中间进行尝试,而不会影响实际执行。另一方面,如果一系列执行成功,则可以(在某些情况下)将其复制回 CPython 的执行堆栈。
我已经将trepan3k连接 到这个解释器中,这样你就有了一个类似pdb/gdb 的调试器,它也能够单步执行字节码指令。
为了试验更快的方法来支持跟踪回调,例如在调试器中使用的那些,添加了一条指令,以支持快速断点和在不碰巧在行边界上的特定指令上设置断点。我相信这可以而且应该移植回 CPython 并且会有好处。(Python 3.8 支持保存附加帧信息的能力,这是存储原始操作码的位置。它只需要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 指令显示了存储的值,例如,在名称x中的指令偏移量 4 处的“2” 。类似地, INPLACE_POWER显示了操作数 2 和 3,这就是将值 8 派生为下一条指令STORE_NAME的操作数的方式。
还想要更多像执行堆栈堆栈和块堆栈吗?添加另一个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 -x使用它:
假设你有 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 之后的指令是两个字节而不是 1 或 3 字节指令的事实。
上面的示例显示了直线代码,因此您可以看到所有说明。但不要将其与xdis中的pydisasm 之类的反汇编程序混淆。下面的例子,带有条件分支的例子更清楚地说明了这一点:
$ 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 中提到的导入问题,但我们已经解决了类似的问题。
一些内置函数和inpsect模块需要像 cell、traceback 或 frame 对象这样的内置类型,它们不能使用相应的解释器类。这是 Byterun中的一个示例:类__init__函数不会被跟踪,因为依赖于内置函数__build_class__。并且 __build_class__需要一个本机函数,而不是解释器可跟踪的函数。请参阅 https://github.com/nedbat/byterun/pull/20。
Byterun在接受对特定 Python 无效但可能对其他 Python 有效的字节码操作码方面也很松散。我想这没关系,因为您不希望无效的操作码出现在有效的字节码中。然而,当提取过程不正确时,它可能会意外或错误地出现通过某种提取过程获得的代码。
与Byterun 相比,x-python对它接受的操作码更加严格。
Byterun 需要我们在这里进行的那种大修,以便能够扩展以支持更多 Python 的字节码,并能够跨不同版本的 Python 运行字节码。具体来说,如果您希望运行解释器正在运行的字节码以外的字节码,或者在面向“字节”的字节码上运行更新的“字码”字节码,则不能依赖 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 的一个分支。它是 Python 字节码执行虚拟机的纯 Python 实现。Ned Batchelder 启动它(基于 Paul Swartz 的工作)是为了更好地理解字节码,以便修复coverage.py 中的分支覆盖错误。
项目详情
下载文件
下载适用于您平台的文件。如果您不确定要选择哪个,请了解有关安装包的更多信息。