PVM字节码

​ 在前面的章节我们提到过,python的运行是将pvm可以识别的字节码进行运行,那么什么是pvm和字节码呢?

PVM

​ PVM就像一个专门设计用来运行字节码的特殊引擎(有java逆向基础的师傅们可以参考JVM)。它逐条读取字节码指令并执行,从而使你的Python程序运行起来。你可以理解为他是一个为了解决迁移问题的小虚拟机。

字节码

​ 如果把PVM比作虚拟机的话,那么字节码就是汇编了,简而言之,编译过程将你可读的代码转换为PVM能够理解和更高效执行的形式。

查看字节码

​ Python 提供了一个强大的工具,称为 dis 模块,该模块允许您反汇编 Python 函数或甚至整个脚本,转化为字节码。

我们写这样一段代码

def dDostalker():
  print('written by dDostalker')

要查看此函数的字节码,我们使用 dis.dis() 函数:

import dis
dis.dis(dDostalker)

之后便打印出我们想要的字节码了

 3           0 LOAD_GLOBAL              0 (print)
              2 LOAD_CONST               1 ('written by dDostalker')
              4 CALL_FUNCTION            1
              6 POP_TOP
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE

常见字节码指令

  • LOAD_CONST:将一个常量值(如数字、字符串或 None)加载到栈顶。例如,LOAD_CONST 1 ('Hello, ') 将字符串“Hello, ”加载到栈中。
  • LOAD_FAST:将局部变量的值加载到栈中。示例:LOAD_FAST 0 (x) 将局部变量 x 的值加载到栈中。
  • STORE_FAST:将栈顶的值取出并存储到局部变量中。例如,STORE_FAST 1 (y) 将栈顶的值存储到变量 y 中。
  • BINARY_ADD:从栈中取出顶部的两个值,将它们相加,并将结果推回栈中。例如,在指令序列 LOAD_FAST 0 (x)LOAD_CONST 1 (5)BINARY_ADD 中,x 和 5 的值被相加,结果被放置在栈上。
  • POP_TOP:从栈中移除顶部的值,有效地丢弃它。
  • RETURN_VALUE:返回栈顶的值,有效地结束函数的执行。
  • JUMP_IF_FALSE_OR_POP:如果栈顶的值为假,则此指令跳转到指定的指令。否则,它从栈中弹出该值。
  • JUMP_ABSOLUTE:无条件跳转到指定的指令。

基本 Python 结构的字节码示例

让我们看看这些指令在基本 Python 结构中的使用:

条件语句(If-Else)

def check_positive(x):
    if x > 0:
        return "Positive"
    else:
        return "Non-positive"

字节码:

2           0 LOAD_FAST                0 (x)
            2 LOAD_CONST               1 (0)
            4 COMPARE_OP               4 (>)
            6 POP_JUMP_IF_FALSE       14
3           8 LOAD_CONST               2 ('Positive')
           10 RETURN_VALUE
5     >>   12 LOAD_CONST               3 ('Non-positive')
           14 RETURN_VALUE

在上面的字节码中:

  • LOAD_FAST 0 (x):将变量 x 加载到栈上。
  • LOAD_CONST 1 (0):将常量 0 加载到栈上。
  • COMPARE_OP 4 (>):比较栈顶的两个值(x > 0)。
  • POP_JUMP_IF_FALSE 14:如果比较结果为假,则跳转到指令 14。
  • LOAD_CONST 2 ('Positive'):如果 x > 0,则将字符串 'Positive' 加载到栈上。
  • RETURN_VALUE:返回栈上的值。
  • LOAD_CONST 3 ('Non-positive'):如果 x <= 0,则将字符串 'Non-positive' 加载到栈上。

循环(For 循环)

def sum_list(numbers):
    total = 0
    for num in numbers:
        total += num
    return total

字节码:

2           0 LOAD_CONST               1 (0)
            2 STORE_FAST               1 (total)
3           4 LOAD_FAST                0 (numbers)
            6 GET_ITER
>>   8 FOR_ITER                12 (到 22)
           10 STORE_FAST               2 (num)
4          12 LOAD_FAST                1 (total)
           14 LOAD_FAST                2 (num)
           16 INPLACE_ADD
           18 STORE_FAST               1 (total)
           20 JUMP_ABSOLUTE            8
        >>  22 LOAD_FAST                1 (total)
           24 RETURN_VALUE

现在,让我们来探讨一下字节码中发生了什么:

  1. LOAD_CONST 1 (0):将常量 0 加载到栈上,以初始化 total
  2. STORE_FAST 1 (total):将 0 存储在变量 total 中。
  3. LOAD_FAST 0 (numbers):将变量 numbers 加载到栈上。
  4. GET_ITER:获取 numbers 的迭代器。
  5. FOR_ITER 12 (to 22):遍历 numbers,完成后跳转到指令 22。
  6. STORE_FAST 2 (num):将当前项存储在变量 num 中。
  7. LOAD_FAST 1 (total):将 total 加载到栈上。
  8. LOAD_FAST 2 (num):将 num 加载到栈上。
  9. INPLACE_ADD:将 totalnum 相加(就地操作)。
  10. STORE_FAST 1 (total):将结果存储回 total 中。
  11. JUMP_ABSOLUTE 8:跳回循环的开始处。
  12. LOAD_FAST 1 (total):将 total 加载到栈上。
  13. RETURN_VALUE:返回 total

理解这些常见指令及其在不同 Python 结构中的使用,可以显著增强您分析字节码的能力,并深入了解 Python 的内部工作原理。

引用

Python 字节码:初学者指南 – QPython+