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

2024-04-25 18:25:03 发布

您现在位置:Python中文网/ 问答频道 /正文

我这里有一个函数,分解后它看起来是这样的:

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,但不是很直接。有人知道这方面的信息吗?或者有人能告诉我我能做些什么吗?在


Tags: hellosysexitloadfunctioncallpopglobal
1条回答
网友
1楼 · 发布于 2024-04-25 18:25:03

修改函数字节码的最佳方法是使用第三方库。目前,^{}似乎是最好的一个,但是对于Python的旧版本,您可能希望byteplay-用于3.4(您似乎正在使用),特别是Seprex's version of the 3.x port。在

但你可以手工做任何事情。{至少要学一次图书馆的知识。在

^{}文档中可以看到,函数基本上是一个包含额外内容的__code__对象的包装器(闭包单元格、默认值和诸如名称和类型注释之类的反射内容),而代码对象则是一个co_code字节码字节环的包装器,它充满了字节码和一大堆额外的东西。在

所以,你会认为删掉一些字节码只是一个问题:

del func.__code__.co_code[12:22]

但遗憾的是,字节码以偏移量的形式执行所有操作,从跳转指令到用于生成回溯的行号表。你可以把一切都搞定,但这很痛苦。因此,您可以用^{}替换要终止的指令。(在幕后,编译器和窥视孔优化器在各处放置nop,然后在最后进行一次大的修复。但是用于修复的代码不会公开给Python)

另外,字节码存储在不可变的bytes中,而不是可变的bytearray,并且{}对象本身是不可变的(试图在解释器背后通过C API黑客修改它们是一个非常糟糕的主意)。因此,您必须围绕修改后的字节码构建一个新的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的函数。所以,交叉手指,然后:

noprange(game_on, 12, 22)

…会做你想做的事。或者它会修改你的函数,在你试图调用它时引发一个RuntimeError或者崩溃,但是segfaults是学习的一部分,对吗?不管怎样,如果dis.dis(noprange)你应该看到第10行的四条指令被一个NOP行的字符串所取代,然后函数的其余部分没有变化,所以在调用它之前请尝试一下。在


一旦您确信这一点可以正常工作,如果您想从一个源代码行中删除所有指令而不必dis该函数并手动读取它们,则可以使用^{}以编程方式执行:

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版本之间改变,但是行号的计数方式显然不会

相关问题 更多 >