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