从底层简析Python程序的执行过程

2019-01-05 09:49:53王冬梅

基于上面的例子,有人可能会想我们的 insert_op_debug 会在指定的偏移量增加一个"x00",这尼玛是个坑啊!我们第一个 DEBUG_OP 注入的例子中被注入的函数是没有任何的分支的,为了能够实现完美一个函数注入函数 insert_op_debug 我们需要考虑到存在分支操作码的情况。

Python 的分支一共有两种:

   (1) 绝对分支:看起来是类似这样子的 Instruction_Pointer = argument(instruction)

    (2)相对分支:看起来是类似这样子的 Instruction_Pointer += argument(instruction)

               相对分支总是向前的

我们希望这些分支在我们插入操作码之后仍然能够正常工作,为此我们需要修改一些指令参数。以下是其逻辑流程:

   (1) 对于每一个在插入偏移量之前的相对分支而言

        如果目标地址是严格大于我们的插入偏移量的话,将指令参数增加 1

        如果相等,则不需要增加 1 就能够在跳转操作和目标地址之间执行我们的操作码DEBUG_OP

        如果小于,插入我们的操作码的话并不会影响到跳转操作和目标地址之间的距离

   (2) 对于 code object 中的每一个绝对分支而言

        如果目标地址是严格大于我们的插入偏移量的话,将指令参数增加 1

        如果相等,那么不需要任何修改,理由和相对分支部分是一样的

        如果小于,插入我们的操作码的话并不会影响到跳转操作和目标地址之间的距离

下面是实现:

# Helper def bytecode_to_string(bytecode): if bytecode.arg is not None: return struct.pack("<Bh", bytecode.opcode, bytecode.arg) return struct.pack("<B", bytecode.opcode) # Dummy class for bytecode_to_string class DummyInstr: def __init__(self, opcode, arg): self.opcode = opcode self.arg = arg def insert_op_debug(code, offset): opcode_jump_rel = ['FOR_ITER', 'JUMP_FORWARD', 'SETUP_LOOP', 'SETUP_WITH', 'SETUP_EXCEPT', 'SETUP_FINALLY'] opcode_jump_abs = ['POP_JUMP_IF_TRUE', 'POP_JUMP_IF_FALSE', 'JUMP_ABSOLUTE'] res_codestring = b"" inserted = False for instr in dis.Bytecode(code): if instr.offset == offset: res_codestring += b"x00" inserted = True if instr.opname in opcode_jump_rel and not inserted: #relative jump are always forward if offset < instr.offset + 3 + instr.arg: # inserted beetwen jump and dest: add 1 to dest (3 for size) #If equal: jump on DEBUG_OP to get info before exec instr res_codestring += bytecode_to_string(DummyInstr(instr.opcode, instr.arg + 1)) continue if instr.opname in opcode_jump_abs: if instr.arg > offset: res_codestring += bytecode_to_string(DummyInstr(instr.opcode, instr.arg + 1)) continue res_codestring += bytecode_to_string(instr) # replace_bytecode just replaces the original code co_code return replace_bytecode(code, res_codestring)