gpt4 book ai didi

python - 使用 Python 中的反汇编程序停止打印函数?

转载 作者:行者123 更新时间:2023-11-28 17:08:08 25 4
gpt4 key购买 nike

我这里有这个功能,反汇编后是这样的:

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之类的资料很多,但不是很直接。有没有人对此有任何信息,或者有人可以指出我可以做什么?

最佳答案

修改函数字节码的最佳方法(好吧,首先假设任何东西都可以称为好方法……)是使用第三方库。目前bytecode似乎是最好的,但是对于旧版本的 Python,您可能需要 byteplay—for 3.4(您似乎正在使用),特别是 Seprex's version of the 3.x port .

但是您可以手动完成所有操作。至少这样做一次是值得的,只是为了确保您理解所有内容(并了解为什么 bytecode 是一个如此酷的库)。

inspect可以看出在文档中,函数基本上是 __code__ 对象的包装器,带有额外的东西(闭包单元、默认值和反射东西,如名称和类型注释),而代码对象是 对象的包装器code>co_code bytestring 充满字节码和一大堆额外的东西。

因此,您认为删除一些字节码只是一个问题:

del func.__code__.co_code[12:22]

但遗憾的是,字节码在偏移量方面完成了所有工作,从跳转指令到用于生成回溯的行号表。您可以修复所有问题,但这很痛苦。所以你可以用 NOP 代替你想杀死的指令. (在幕后,编译器和窥视孔优化器在各处放置 NOP,然后在最后进行一次大修复。但是执行该修复的代码并未向 Python 公开。)

此外,字节码存储在不可变的 bytes 中,而不是可变的 bytearray,并且 code 对象本身是不可变的(并试图改变它们通过 C API hacks 在解释器的背后是一个非常糟糕的主意)。因此,您必须围绕修改后的字节码构建一个新的 code 对象。但是函数是可变的,因此您可以修改函数以指向新的代码对象。


所以,这里有一个函数可以通过偏移量来 NOP 出一系列指令:

import dis
import sys
import types

NOP = bytes([dis.opmap['NOP']])

def noprange(func, start, end):
c = func.__code__
cc = c.co_code
if sys.version_info >= (3,6):
if (end - start) % 2:
raise ValueError('Cannot nop out partial wordcodes')
nops = (NOP + b'\0') * ((end-start)//2)
else:
nops = NOP * (end-start)
newcc = cc[:start] + nops + cc[end:]
newc = types.CodeType(
c.co_argcount, c.co_kwonlyargcount, c.co_nlocals, c.co_stacksize,
c.co_flags, newcc, c.co_consts, c.co_names, c.co_varnames,
c.co_filename, c.co_name, c.co_firstlineno, c.co_lnotab,
c.co_freevars, c.co_cellvars)
func.__code__ = newc

如果您想了解版本检查:在 Python 2.x 和 3.0-3.5 中,每条指令的长度为 1 或 3 个字节,具体取决于它是否需要任何参数,因此 NOP 为 1 个字节;在 3.6+ 中,每条指令长度为 2 个字节,包括 NOP。

无论如何,我实际上只在 3.6 上测试过,而不是 3.4 或 3.5,所以希望我没有弄错那部分。希望在 3.4 之后我没有添加任何添加到 dis 的功能。那么,祈祷吧,然后:

noprange(game_on, 12, 22)

... 将完全按照您的意愿行事。或者它会修改您的函数以引发 RuntimeError 或在您尝试调用它时崩溃,但段错误是学习的一部分,对吧?无论如何,如果您使用 dis.dis(noprange),您应该会看到第 10 行中的四个指令被一串 NOP 行替换,然后函数的其余部分保持不变,所以在调用它之前尝试一下。


一旦你确信你已经让这个正常工作,如果你想从一个源代码行中删除所有指令,而不必dis函数并手动阅读它们,你可以使用 findlinestarts以编程方式进行:

def nopline(func, line):
linestarts = dis.findlinestarts(func.__code__)
for offset, lineno in linestarts:
if lineno > line:
raise ValueError('No code found for line')
if lineno == line:
try:
nextoffset, _ = next(linestarts)
except StopIteration:
raise ValueError('Do not nop out the last return')
noprange(func, offset, nextoffset)
return
raise ValueError('No line found')

现在只是:

nopline(game_on, 10)

这有一个很好的优势,您可以在代码中使用它,在 3.4 和 3.8 中将以相同的方式工作(或崩溃),因为 Python 版本之间的偏移量可能会发生变化,但行号的计数方式显然不会。

关于python - 使用 Python 中的反汇编程序停止打印函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49702797/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com