0x1.前言
> Python 代码先被编译为字节码后,再由Python虚拟机来执行字节码, Python的字节码是一种类似汇编指令的中间语言, 一个Python语句会对应若干字节码指令,虚拟机一条一条执行字节码指令, 从而完成程序执行。Python dis 模块支持对Python代码进行反汇编, 生成字节码指令。dis.dis()
将CPython字节码转为可读的伪代码(类似于汇编代码)。结构如下:
7 0 LOAD_CONST 1 (0)
3 STORE_FAST 1 (local1)
8 6 LOAD_CONST 2 (101)
9 STORE_GLOBAL 0 (global1)
9 12 LOAD_FAST 1 (local1)
15 PRINT_ITEM
16 LOAD_FAST 0 (arg1)
19 PRINT_ITEM
20 LOAD_GLOBAL 0 (global1)
23 PRINT_ITEM
24 PRINT_NEWLINE
25 LOAD_CONST 0 (None)
28 RETURN_VALUE
其实就是这样的结构:
源码行号 | 指令在函数中的偏移 | 指令符号 | 指令参数 | 实际参数值
0x2.变量
1.const
LOAD_CONST
加载const
变量,比如数值、字符串等等,一般用于传给函数的参数
55 12 LOAD_GLOBAL 1 (test)
15 LOAD_FAST 0 (2) #读取2
18 LOAD_CONST 1 ('output')
21 CALL_FUNCTION 2
转为python代码就是:
test(2, 'output')
2.局部变量
LOAD_FAST
一般加载局部变量的值,也就是读取值,用于计算或者函数调用传参等。STORE_FAST
一般用于保存值到局部变量。
61 77 LOAD_FAST 0 (n)
80 LOAD_FAST 3 (p)
83 INPLACE_DIVIDE
84 STORE_FAST 0 (n)
这段bytecode转为python就是:
n = n / p
函数的形参也是局部变量,如何区分出是函数形参还是其他局部变量呢?形参没有初始化,也就是从函数开始到LOAD_FAST
该变量的位置,如果没有看到STORE_FAST
,那么该变量就是函数形参。而其他局部变量在使用之前肯定会使用STORE_FAST
进行初始化。具体看下面的实例:
4 0 LOAD_CONST 1 (0)
3 STORE_FAST 1 (local1)
5 6 LOAD_FAST 1 (local1)
9 PRINT_ITEM
10 LOAD_FAST 0 (arg1)
13 PRINT_ITEM
14 PRINT_NEWLINE
15 LOAD_CONST 0 (None)
18 RETURN_VALUE
对应的python代码如下,对比一下就一目了然。
def test(arg1):
local1 = 0
print local1, arg1
3.全局变量
LOAD_GLOBAL
用来加载全局变量,包括指定函数名,类名,模块名等全局符号。STORE_GLOBAL
用来给全局变量赋值。
8 6 LOAD_CONST 2 (101)
9 STORE_GLOBAL 0 (global1)
20 LOAD_GLOBAL 0 (global1)
23 PRINT_ITEM
对应的python代码
def test():
global global1
global1 = 101
print global
0x3.常用数据类型
1.list
BUILD_LIST
用于创建一个list结构。
13 0 LOAD_CONST 1 (1)
3 LOAD_CONST 2 (2)
6 BUILD_LIST 2
9 STORE_FAST 0 (k)
对应python代码是:
k = [1, 2]
另外再看看一种常见的创建list的方式如下:
[x for x in xlist if x!=0 ]
一个实例bytecode如下:
22 235 BUILD_LIST 0 //创建list,为赋值给某变量,这种时候一般都是语法糖结构了
238 LOAD_FAST 3 (sieve)
241 GET_ITER
>> 242 FOR_ITER 24 (to 269)
245 STORE_FAST 4 (x)
248 LOAD_FAST 4 (x)
251 LOAD_CONST 2 (0)
254 COMPARE_OP 3 (!=)
257 POP_JUMP_IF_FALSE 242 //不满足条件contine
260 LOAD_FAST 4 (x)//读取满足条件的x
263 LIST_APPEND 2 //把每个满足条件的x存入list
266 JUMP_ABSOLUTE 242
>> 269 RETURN_VALUE
转为python代码是:
[for x in sieve if x != 0]
2.dict
BUILD_MAP
用于创建一个空的dict。STORE_MAP
用于初始化dict的内容。
13 0 BUILD_MAP 1
3 LOAD_CONST 1 (1)
6 LOAD_CONST 2 ('a')
9 STORE_MAP
10 STORE_FAST 0 (k)
对应的python代码是:
k = {'a': 1}
再看看修改dict的bytecode:
14 13 LOAD_CONST 3 (2)
16 LOAD_FAST 0 (k)
19 LOAD_CONST 4 ('b')
22 STORE_SUBSCR
对应的python代码是:
k['b'] = 2
3.slice
BUILD_SLICE
用于创建slice。对于list、元组、字符串都可以使用slice的方式进行访问。但是要注意BUILD_SLICE
用于[x:y:z]这种类型的slice,结合BINARY_SUBSCR
读取slice的值,结合STORE_SUBSCR
用于修改slice的值。另外SLICE+n
用于[a:b]类型的访问,STORE_SLICE+n
用于[a:b]类型的修改,其中n
表示如下:
SLICE+0()
Implements TOS = TOS[:].
SLICE+1()
Implements TOS = TOS1[TOS:].
SLICE+2()
Implements TOS = TOS1[:TOS].
SLICE+3()
Implements TOS = TOS2[TOS1:TOS].
下面看具体实例:
13 0 LOAD_CONST 1 (1)
3 LOAD_CONST 2 (2)
6 LOAD_CONST 3 (3)
9 BUILD_LIST 3
12 STORE_FAST 0 (k1) //k1 = [1, 2, 3]
14 15 LOAD_CONST 4 (10)
18 BUILD_LIST 1
21 LOAD_FAST 0 (k1)
24 LOAD_CONST 5 (0)
27 LOAD_CONST 1 (1)
30 LOAD_CONST 1 (1)
33 BUILD_SLICE 3
36 STORE_SUBSCR //k1[0:1:1] = [10]
15 37 LOAD_CONST 6 (11)
40 BUILD_LIST 1
43 LOAD_FAST 0 (k1)
46 LOAD_CONST 1 (1)
49 LOAD_CONST 2 (2)
52 STORE_SLICE+3 //k1[1:2] = [11]
16 53 LOAD_FAST 0 (k1)
56 LOAD_CONST 1 (1)
59 LOAD_CONST 2 (2)
62 SLICE+3
63 STORE_FAST 1 (a) //a = k1[1:2]
17 66 LOAD_FAST 0 (k1)
69 LOAD_CONST 5 (0)
72 LOAD_CONST 1 (1)
75 LOAD_CONST 1 (1)
78 BUILD_SLICE 3
81 BINARY_SUBSCR
82 STORE_FAST 2 (b) //b = k1[0:1:1]
0x4.循环
SETUP_LOOP
用于开始一个循环。SETUP_LOOP 26 (to 35)
中35
表示循环退出点。
while循环
23 0 LOAD_CONST 1 (0)
3 STORE_FAST 0 (i) // i=0
24 6 SETUP_LOOP 26 (to 35)
>> 9 LOAD_FAST 0 (i) //循环起点
12 LOAD_CONST 2 (10)
15 COMPARE_OP 0 (<)
18 POP_JUMP_IF_FALSE 34 //while i < 10:
25 21 LOAD_FAST 0 (i)
24 LOAD_CONST 3 (1)
27 INPLACE_ADD
28 STORE_FAST 0 (i) // i += 1
31 JUMP_ABSOLUTE 9 // 回到循环起点
>> 34 POP_BLOCK
>> 35 LOAD_CONST 0 (None)
对应python代码是:
i = 0
while i < 10:
i += 1
for in结构
238 LOAD_FAST 3 (sieve)#sieve是个list
241 GET_ITER //开始迭代sieve
>> 242 FOR_ITER 24 (to 269) //继续iter下一个x
245 STORE_FAST 4 (x)
...
266 JUMP_ABSOLUTE 242 //循环
这是典型的for+in结构,转为python代码就是:
for x in sieve:
0x5.if
POP_JUMP_IF_FALSE
和JUMP_FORWARD
一般用于分支判断跳转。POP_JUMP_IF_FALSE
表示条件结果为FALSE
就跳转到目标偏移指令。JUMP_FORWARD
直接跳转到目标偏移指令。
23 0 LOAD_CONST 1 (0)
3 STORE_FAST 0 (i) //i=0
24 6 LOAD_FAST 0 (i)
9 LOAD_CONST 2 (5)
12 COMPARE_OP 0 (<)
15 POP_JUMP_IF_FALSE 26
25 18 LOAD_CONST 3 ('i < 5')
21 PRINT_ITEM
22 PRINT_NEWLINE
23 JUMP_FORWARD 25 (to 51)
26 >> 26 LOAD_FAST 0 (i)
29 LOAD_CONST 2 (5)
32 COMPARE_OP 4 (>)
35 POP_JUMP_IF_FALSE 46
27 38 LOAD_CONST 4 ('i > 5')
41 PRINT_ITEM
42 PRINT_NEWLINE
43 JUMP_FORWARD 5 (to 51)
29 >> 46 LOAD_CONST 5 ('i = 5')
49 PRINT_ITEM
50 PRINT_NEWLINE
>> 51 LOAD_CONST 0 (None)
转为python代码是:
i = 0
if i < 5:
print 'i < 5'
elif i > 5:
print 'i > 5'
else:
print 'i = 5'
0x6.分辨函数
1.函数范围
前面介绍第二列表示指令在函数中的偏移地址,所以看到0就是函数开始,下一个0前一条指令就是函数结束位置,当然也可以通过RETURN_VALUE
来确定函数结尾
54 0 LOAD_FAST 1 (plist) //函数开始
3 LOAD_CONST 0 (None)
6 COMPARE_OP 2 (==)
9 POP_JUMP_IF_FALSE 33
55 ...
67 >> 139 LOAD_FAST 2 (fs)
142 RETURN_VALUE
70 0 LOAD_CONST 1 ('FLAG') //另一个函数开始
3 STORE_FAST 0 (flag)
2.函数调用
函数调用类似于push+call
的汇编结构,压栈参数从左到右依次压入(当然不是push
,而是读取指令LOAD_xxxx
来指定参数)。函数名一般通过LOAD_GLOBAL
指令指定,如果是模块函数或者类成员函数通过LOAD_GLOBAL
+LOAD_ATTR
来指定。先指定要调用的函数,然后压参数,最后通过CALL_FUNCTION
调用。CALL_FUNCTION
后面的值表示有几个参数。支持嵌套调用:
6 0 LOAD_GLOBAL 0 (int) //int函数
3 LOAD_GLOBAL 1 (math)//math模块
6 LOAD_ATTR 2 (sqrt)//sqrt函数
9 LOAD_FAST 0 (n) //参数
12 CALL_FUNCTION 1
15 CALL_FUNCTION 1
18 STORE_FAST 2 (nroot)
这段bytecode
转换成python
代码就是
nroot = int(math.sqrt(n)) //其中n是一个局部变量或者函数参数,具体看上下文
0x7.其他指令
其他常见指令,一看就明白,就不具体分析了,更多详细内容请看官方文档。
INPLACE_POWER()
Implements in-place TOS = TOS1 ** TOS.
INPLACE_MULTIPLY()
Implements in-place TOS = TOS1 * TOS.
INPLACE_DIVIDE()
Implements in-place TOS = TOS1 / TOS when from __future__ import division is not in effect.
INPLACE_FLOOR_DIVIDE()
Implements in-place TOS = TOS1 // TOS.
INPLACE_TRUE_DIVIDE()
Implements in-place TOS = TOS1 / TOS when from __future__ import division is in effect.
INPLACE_MODULO()
Implements in-place TOS = TOS1 % TOS.
INPLACE_ADD()
Implements in-place TOS = TOS1 + TOS.
INPLACE_SUBTRACT()
Implements in-place TOS = TOS1 - TOS.
INPLACE_LSHIFT()
Implements in-place TOS = TOS1 << TOS.
INPLACE_RSHIFT()
Implements in-place TOS = TOS1 >> TOS.
INPLACE_AND()
Implements in-place TOS = TOS1 & TOS.
INPLACE_XOR()
Implements in-place TOS = TOS1 ^ TOS.
INPLACE_OR()
Implements in-place TOS = TOS1 | TOS.
基础运算还有一套对应的BINARY_xxxx
指令,两者区别很简单。
i += 1 //使用INPLACE_xxx
i = i + 1 //使用BINARY_xxxx