我这里有一个函数,分解后它看起来是这样的:
def game_on():
def other_function():
print('Statement within a another function')
print("Hello World")
sys.exit()
print("Statement after sys.exit")
8 0 LOAD_CONST 1 (<code object easter_egg at 0x0000000005609C90, file "filename", line 8>)
3 LOAD_CONST 2 ('game_on.<locals>.other_function')
6 MAKE_FUNCTION 0
9 STORE_FAST 0 (other_function)
10 12 LOAD_GLOBAL 0 (print)
15 LOAD_CONST 3 ('Hello World')
18 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
21 POP_TOP
11 22 LOAD_GLOBAL 1 (sys)
25 LOAD_ATTR 2 (exit)
28 CALL_FUNCTION 0 (0 positional, 0 keyword pair)
31 POP_TOP
12 32 LOAD_GLOBAL 0 (print)
35 LOAD_CONST 4 ('second print statement')
38 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
41 POP_TOP
42 LOAD_CONST 5 (None)
45 RETURN_VALUE
有没有办法修改字节码,使其不打印“Hello world”。就像我想跳过第10行继续到第11行一样。在
有很多材料像检查员和settrace
,但不是很直接。有人知道这方面的信息吗?或者有人能告诉我我能做些什么吗?在
修改函数字节码的最佳方法是使用第三方库。目前,^{} 似乎是最好的一个,但是对于Python的旧版本,您可能希望
byteplay
-用于3.4(您似乎正在使用),特别是Seprex's version of the 3.x port。在但你可以手工做任何事情。{至少要学一次图书馆的知识。在
从^{} 文档中可以看到,函数基本上是一个包含额外内容的
__code__
对象的包装器(闭包单元格、默认值和诸如名称和类型注释之类的反射内容),而代码对象则是一个co_code
字节码字节环的包装器,它充满了字节码和一大堆额外的东西。在所以,你会认为删掉一些字节码只是一个问题:
但遗憾的是,字节码以偏移量的形式执行所有操作,从跳转指令到用于生成回溯的行号表。你可以把一切都搞定,但这很痛苦。因此,您可以用^{} 替换要终止的指令。(在幕后,编译器和窥视孔优化器在各处放置nop,然后在最后进行一次大的修复。但是用于修复的代码不会公开给Python)
另外,字节码存储在不可变的}对象本身是不可变的(试图在解释器背后通过C API黑客修改它们是一个非常糟糕的主意)。因此,您必须围绕修改后的字节码构建一个新的
bytes
中,而不是可变的bytearray
,并且{code
对象。但是函数是可变的,所以可以对函数进行修改以指向新的代码对象。在因此,这里有一个函数可以按偏移量NOP出一系列指令:
^{pr2}$如果您想知道版本检查:在Python2.x和3.0-3.5中,根据是否需要任何参数,每个指令的长度是1或3个字节,所以NOP是1个字节;在3.6+版本中,每个指令的长度是2个字节,包括NOP。在
不管怎样,我实际上只测试了3.6,而不是3.4或3.5,所以希望我没有弄错这一部分。希望我没有在3.4之后添加任何添加到
dis
的函数。所以,交叉手指,然后:…会做你想做的事。或者它会修改你的函数,在你试图调用它时引发一个
RuntimeError
或者崩溃,但是segfaults是学习的一部分,对吗?不管怎样,如果dis.dis(noprange)
你应该看到第10行的四条指令被一个NOP
行的字符串所取代,然后函数的其余部分没有变化,所以在调用它之前请尝试一下。在一旦您确信这一点可以正常工作,如果您想从一个源代码行中删除所有指令而不必} 以编程方式执行:
dis
该函数并手动读取它们,则可以使用^{现在只是:
这有一个很好的优势,你可以在代码中使用它,在3.4和3.8中是一样的,因为偏移量可能在Python版本之间改变,但是行号的计数方式显然不会
相关问题 更多 >
编程相关推荐