python - 修改python字节码

标签 python bytecode

我想知道如何修改字节码,然后重新编译该代码,以便我可以在 python 中将其作为函数使用?我一直在尝试:

a = """
def fact():
    a = 8
    a = 0
"""
c = compile(a, '<string>', 'exec')
w = c.co_consts[0].co_code
dis(w)

反编译为:

      0 LOAD_CONST          1 (1)
      3 STORE_FAST          1 (1)
      6 LOAD_CONST          2 (2)
      9 STORE_FAST          1 (1)
     12 LOAD_CONST          0 (0)
     15 RETURN_VALUE   

假设我想去掉第 0 行和第 3 行,我调用:

x = c.co_consts[0].co_code[6:16]
dis(x)

结果是:

      0 LOAD_CONST          2 (2)
      3 STORE_FAST          1 (1)
      6 LOAD_CONST          0 (0)
      9 RETURN_VALUE   

我的问题是如何处理 x,如果我尝试 exec x,我会得到一个“没有空字节的预期字符串”,对于 exec w,我也会得到同样的结果, 尝试编译 x 结果:compile() expected string without null bytes.

我不确定继续进行的最佳方式是什么,除了我可能需要创建某种代码对象,但我不确定如何创建,但我假设它必须是 可能又名 byteplay、python 汇编器等

我使用的是 python 2.7.10,但如果可能的话,我希望它能在未来兼容(例如 python 3)。

最佳答案

更新:出于各种原因,我已经开始编写跨 Python 版本的汇编程序。参见 https://github.com/rocky/python-xasm .它仍处于早期测试阶段。另见 bytecode .

据我所知,没有其他 当前维护 的 Python 汇编器。 PEAK's Bytecode Disassembler是为 Python 2.6 开发的,后来经过修改以支持早期的 Python 2.7。

documentation 来看很酷.但它依赖于其他可能存在问题的 PEAK 库。

我将通过整个示例让您了解您必须做什么。它不是很漂亮,但你应该预料到这一点。

基本上修改字节码后,需要新建一个types.CodeType目的。您需要一个新的,因为代码类型中的许多对象,出于充分的理由,您无法更改。例如,解释器可能缓存了其中一些对象值。

创建代码后,您可以在使用可在 execeval 中使用的代码类型的函数中使用它。

或者您可以将其写入字节码文件。代码格式在 Python 版本 1.3、1、5、2.0、3.0、3.8 和 3.10 之间发生了变化。顺便说一下,优化和字节码也是如此。事实上,在 Python 3.6 中,它们将是单词代码而不是字节码。

所以这里是你必须为你的例子做的:

a = """
def fact():
    a = 8
    a = 0
    return a
"""
c = compile(a, '<string>', 'exec')
fn_code = c.co_consts[0] # Pick up the function code from the main code
from dis import dis
dis(fn_code)
print("=" * 30)

x = fn_code.co_code[6:16] # modify bytecode

import types
opt_fn_code = types.CodeType(fn_code.co_argcount,
                             # c.co_kwonlyargcount,  Add this in Python3
                             # c.co_posonlyargcount, Add this in Python 3.8+
                             fn_code.co_nlocals,
                             fn_code.co_stacksize,
                             fn_code.co_flags,
                             x,  # fn_code.co_code: this you changed
                             fn_code.co_consts,
                             fn_code.co_names,
                             fn_code.co_varnames,
                             fn_code.co_filename,
                             fn_code.co_name,
                             fn_code.co_firstlineno,
                             fn_code.co_lnotab,   # In general, You should adjust this
                             fn_code.co_freevars,
                             fn_code.co_cellvars)
dis(opt_fn_code)
print("=" * 30)
print("Result is", eval(opt_fn_code))

# Now let's change the value of what's returned
co_consts = list(opt_fn_code.co_consts)
co_consts[-1] = 10

opt_fn_code = types.CodeType(fn_code.co_argcount,
                             # c.co_kwonlyargcount,  Add this in Python3
                             # c.co_posonlyargcount, Add this in Python 3.8+
                             fn_code.co_nlocals,
                             fn_code.co_stacksize,
                             fn_code.co_flags,
                             x,  # fn_code.co_code: this you changed
                             tuple(co_consts), # this is now changed too
                             fn_code.co_names,
                             fn_code.co_varnames,
                             fn_code.co_filename,
                             fn_code.co_name,
                             fn_code.co_firstlineno,
                             fn_code.co_lnotab,   # In general, You should adjust this
                             fn_code.co_freevars,
                             fn_code.co_cellvars)

dis(opt_fn_code)
print("=" * 30)
print("Result is now", eval(opt_fn_code))

当我在这里运行时,我得到的是:

  3           0 LOAD_CONST               1 (8)
              3 STORE_FAST               0 (a)

  4           6 LOAD_CONST               2 (0)
              9 STORE_FAST               0 (a)

  5          12 LOAD_FAST                0 (a)
             15 RETURN_VALUE
==============================
  3           0 LOAD_CONST               2 (0)
              3 STORE_FAST               0 (a)

  4           6 LOAD_FAST                0 (a)
              9 RETURN_VALUE
==============================
('Result is', 0)
  3           0 LOAD_CONST               2 (10)
              3 STORE_FAST               0 (a)

  4           6 LOAD_FAST                0 (a)
              9 RETURN_VALUE
==============================
('Result is now', 10)

请注意,即使我在代码中删除了几行,行号也没有改变。那是因为我没有更新 fn_code.co_lnotab

如果你现在想从这里写一个 Python 字节码文件。这是你要做的:

co_consts = list(c.co_consts)
co_consts[0] = opt_fn_code
c1 = types.CodeType(c.co_argcount,
                    # c.co_posonlyargcount, Add this in Python 3.8+
                    # c.co_kwonlyargcount,  Add this in Python3
                    c.co_nlocals,
                    c.co_stacksize,
                    c.co_flags,
                    c.co_code,
                    tuple(co_consts),
                    c.co_names,
                    c.co_varnames,
                    c.co_filename,
                    c.co_name,
                    c.co_firstlineno,
                    c.co_lnotab,   # In general, You should adjust this
                    c.co_freevars,
                    c.co_cellvars)

from struct import pack
with open('/tmp/testing.pyc', 'w') as fp:
        fp.write(pack('Hcc', 62211, '\r', '\n')) # Python 2.7 magic number
        import time
        fp.write(pack('I', int(time.time())))
        # In Python 3.7+ you need to PEP 552 bits 
        # In Python 3 you need to write out the size mod 2**32 here
        import marshal
        fp.write(marshal.dumps(c1))

为了简化上面样板字节码的编写,我在 xasm 中添加了一个例程称为 write_pycfile()

现在检查结果:

$ uncompyle6 /tmp/testing.pyc
# uncompyle6 version 2.9.2
# Python bytecode 2.7 (62211)
# Disassembled from: Python 2.7.12 (default, Jul 26 2016, 22:53:31)
# [GCC 5.4.0 20160609]
# Embedded file name: <string>
# Compiled at: 2016-10-18 05:52:13


def fact():
    a = 0
# okay decompiling /tmp/testing.pyc
$ pydisasm /tmp/testing.pyc
# pydisasm version 3.1.0
# Python bytecode 2.7 (62211) disassembled from Python 2.7
# Timestamp in code: 2016-10-18 05:52:13
# Method Name:       <module>
# Filename:          <string>
# Argument count:    0
# Number of locals:  0
# Stack size:        1
# Flags:             0x00000040 (NOFREE)
# Constants:
#    0: <code object fact at 0x7f815843e4b0, file "<string>", line 2>
#    1: None
# Names:
#    0: fact
  2           0 LOAD_CONST               0 (<code object fact at 0x7f815843e4b0, file "<string>", line 2>)
              3 MAKE_FUNCTION            0
              6 STORE_NAME               0 (fact)
              9 LOAD_CONST               1 (None)
             12 RETURN_VALUE


# Method Name:       fact
# Filename:          <string>
# Argument count:    0
# Number of locals:  1
# Stack size:        1
# Flags:             0x00000043 (NOFREE | NEWLOCALS | OPTIMIZED)
# Constants:
#    0: None
#    1: 8
#    2: 10
# Local variables:
#    0: a
  3           0 LOAD_CONST               2 (10)
              3 STORE_FAST               0 (a)

  4           6 LOAD_CONST               0 (None)
              9 RETURN_VALUE
$

另一种优化方法是在 Abstract Syntax Tree 处进行优化水平(AST)。 compileevalexec 函数可以从 AST 开始,或者您可以转储 AST。您还可以使用 Python 模块 astor 将其作为 Python 源代码写回

但是请注意,某些类型的优化(如尾递归消除)可能会使字节码处于无法以真正忠实于源代码的方式转换的形式。参见 my pycon2018 Columbia Lightning Talk对于我制作的视频,它消除了字节码中的尾递归,以了解我在这里谈论的内容。

如果你希望能够调试和单步执行字节码指令。看我的bytecode interpreter及其 bytecode debugger .

关于python - 修改python字节码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33348067/

相关文章:

python - 为什么我的 urls.py 不起作用?

java - Java中字节码的目的是什么?

java - 经过Byte Buddy改造后,如何在不加载或保存类的情况下获取类信息?

Java-第 0 个局部变量何时不是 'this'?

python - centos6.2 upgrade python2.7 _hashlib and _ssl build failed and make [Modules/_ssl.o] 错误1

python - 使用Python 3从MySQL数据库中一张一张地检索多个图像

python - 给定一个 python .pyc 文件,是否有一个工具可以让我查看字节码?

compiler-construction - 字节码解析指令和机器语言之间的区别?

python - 如何在OpenERP中使用下拉字段?

python - 动态生成的元素 -NoSuchElementException : Message: no such element: Unable to locate element?