我想知道如何修改字节码,然后重新编译该代码,以便我可以在 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
目的。您需要一个新的,因为代码类型中的许多对象,出于充分的理由,您无法更改。例如,解释器可能缓存了其中一些对象值。
创建代码后,您可以在使用可在 exec
或 eval
中使用的代码类型的函数中使用它。
或者您可以将其写入字节码文件。代码格式在 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)。 compile
、eval
和 exec
函数可以从 AST 开始,或者您可以转储 AST。您还可以使用 Python 模块 astor 将其作为 Python 源代码写回
但是请注意,某些类型的优化(如尾递归消除)可能会使字节码处于无法以真正忠实于源代码的方式转换的形式。参见 my pycon2018 Columbia Lightning Talk对于我制作的视频,它消除了字节码中的尾递归,以了解我在这里谈论的内容。
如果你希望能够调试和单步执行字节码指令。看我的bytecode interpreter及其 bytecode debugger .
关于python - 修改python字节码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33348067/