我想知道如何修改字节码,然后重新编译该代码,以便我可以在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
我得到一个没有nullbytes的预期字符串,我对exec w
得到相同的结果,
尝试编译x
会导致:compile()期望的字符串没有空字节。
我不确定最好的方法是什么,除了我可能需要创建某种代码对象,但我不确定如何,但我认为它必须是 可能也称为byteplay,python汇编程序等
我使用的是python 2.7.10,但如果可能的话,我希望将来兼容(例如python 3)。
答案 0 :(得分:9)
更新:出于各种原因,我已经开始编写一个跨Python版本的汇编程序。请参阅https://github.com/rocky/python-xasm目前仍处于测试阶段。
据我所知,目前没有当前维护的 Python汇编程序。 PEAK's Bytecode Disassembler是为Python 2.6开发的,后来经过修改以支持早期的Python 2.7。
documentation非常酷。但它依赖于其他可能存在问题的PEAK库。
我将通过整个示例来了解您必须做的事情。它不漂亮,但你应该期待。
基本上在修改字节码后,您需要创建一个新的types.CodeType
对象。您需要一个新的,因为代码类型中的许多对象,有充分理由,您无法更改。例如,解释器可能会缓存其中一些对象值。
创建代码后,您可以在使用可在exec
或eval
中使用的代码类型的函数中使用此代码。
或者您可以将其写入字节码文件。唉,Python 2和Python 3之间的代码格式发生了变化。顺便提一下,优化和字节码也是如此。实际上,在Python 3.6中,它们将是 word 代码而不是字节码。
所以这就是你必须为你的榜样做的事情:
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
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
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_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 you need to write out the size mod 2**32 here
import marshal
fp.write(marshal.dumps(c1))
为了简化上面的样板字节码的编写,我在xdis添加了一个名为 write_python_file()的例程。
现在检查结果:
$ 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)进行优化。我不知道你是如何从AST生成字节码文件的。所以我想如果可能的话,你把它写成Python源代码。
但请注意,某些类型的优化(如尾递归消除)可能会将字节码保留为无法以真实忠实的方式转换为源代码的形式。请参阅my pycon2018 columbia lightning talk,查看我制作的视频,它会在字节码中消除尾部递归,以便了解我在这里谈论的内容。