死磕python位元組碼-手工還原python原始碼
0x1.前言
Python 程式碼先被編譯為位元組碼後,再由Python虛擬機器來執行位元組碼, Python的位元組碼是一種類似彙編指令的中間語言, 一個Python語句會對應若干位元組碼指令,虛擬機器一條一條執行位元組碼指令, 從而完成程式執行。
Python dis 模組支援對Python程式碼進行反彙編, 生成位元組碼指令。
dis.dis()將CPython位元組碼轉為可讀的虛擬碼(類似於彙編程式碼)。結構如下:
7 0LOAD_CONST 1(0) 3STORE_FAST 1(local1) 8 6LOAD_CONST 2(101) 9STORE_GLOBAL 0(global1) 9 12LOAD_FAST 1(local1) 15PRINT_ITEM 16LOAD_FAST 0(arg1) 19PRINT_ITEM 20LOAD_GLOBAL 0(global1) 23PRINT_ITEM 24PRINT_NEWLINE 25LOAD_CONST 0(None) 28RETURN_VALUE
其實就是這樣的結構:
原始碼行號 | 指令在函式中的偏移 | 指令符號 | 指令引數 | 實際引數值
0x2.變數
1.const
LOAD_CONST載入const變數,比如數值、字串等等,一般用於傳給函式的引數:
55 12LOAD_GLOBAL 1(test) 15LOAD_FAST 0(2)#讀取2 18LOAD_CONST 1('output') 21CALL_FUNCTION 2
轉為python程式碼就是:
test(2,'output')
2.區域性變數
LOAD_FAST一般載入區域性變數的值,也就是讀取值,用於計算或者函式呼叫傳參等。
STORE_FAST一般用於儲存值到區域性變數。
61 77LOAD_FAST 0(n) 80LOAD_FAST 3(p) 83INPLACE_DIVIDE 84STORE_FAST 0(n)
這段bytecode轉為python就是:
n=n/p
函式的形參也是區域性變數,如何區分出是函式形參還是其他區域性變數呢?
形參沒有初始化,也就是從函式開始到LOAD_FAST該變數的位置,如果沒有看到STORE_FAST,那麼該變數就是函式形參。
而其他區域性變數在使用之前肯定會使用STORE_FAST進行初始化。
具體看下面的例項:
4 0LOAD_CONST 1(0) 3STORE_FAST 1(local1) 5 6LOAD_FAST 1(local1) 9PRINT_ITEM 10LOAD_FAST 0(arg1) 13PRINT_ITEM 14PRINT_NEWLINE 15LOAD_CONST 0(None) 18RETURN_VALUE
對應的python程式碼如下,對比一下就一目瞭然。
deftest(arg1): local1=0 printlocal1, arg1
3.全域性變數
LOAD_GLOBAL用來載入全域性變數,包括指定函式名,類名,模組名等全域性符號。
STORE_GLOBAL用來給全域性變數賦值。
8 6LOAD_CONST 2(101) 9STORE_GLOBAL 0(global1) 20LOAD_GLOBAL 0(global1) 23PRINT_ITEM
對應的python程式碼
deftest(): globalglobal1 global1=101 printglobal
0x3.常用資料型別
1.list
BUILD_LIST用於建立一個list結構。
13 0LOAD_CONST 1(1) 3LOAD_CONST 2(2) 6BUILD_LIST 2 9STORE_FAST 0(k)
對應python程式碼是:
k=[1,2]
另外再看看一種常見的建立list的方式如下:
[xforxinxlistifx!=0]
一個例項bytecode如下:
22 235BUILD_LIST 0//建立list,為賦值給某變數,這種時候一般都是語法糖結構了 238LOAD_FAST 3(sieve) 241GET_ITER >> 242FOR_ITER 24(to269) 245STORE_FAST 4(x) 248LOAD_FAST 4(x) 251LOAD_CONST 2(0) 254COMPARE_OP 3(!=) 257POP_JUMP_IF_FALSE 242//不滿足條件contine 260LOAD_FAST 4(x)//讀取滿足條件的x 263LIST_APPEND 2//把每個滿足條件的x存入list 266JUMP_ABSOLUTE 242 >> 269RETURN_VALUE
轉為python程式碼是:
[forxinsieveifx !=0]
2.dict
BUILD_MAP用於建立一個空的dict。STORE_MAP用於初始化dict的內容。
13 0BUILD_MAP 1 3LOAD_CONST 1(1) 6LOAD_CONST 2('a') 9STORE_MAP 10STORE_FAST 0(k)
對應的python程式碼是:
k={'a':1}
再看看修改dict的bytecode:
14 13LOAD_CONST 3(2) 16LOAD_FAST 0(k) 19LOAD_CONST 4('b') 22STORE_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 0LOAD_CONST 1(0) 3STORE_FAST 0(i)//i=0 24 6SETUP_LOOP 26(to35) >> 9LOAD_FAST 0(i)//迴圈起點 12LOAD_CONST 2(10) 15COMPARE_OP 0(<) 18POP_JUMP_IF_FALSE 34 //whilei <10: 25 21LOAD_FAST 0(i) 24LOAD_CONST 3(1) 27INPLACE_ADD 28STORE_FAST 0(i)//i+=1 31JUMP_ABSOLUTE 9 //回到迴圈起點 >> 34POP_BLOCK >> 35LOAD_CONST 0(None)
對應python程式碼是:
i=0 whilei <10: i+=1
for in結構
238LOAD_FAST 3(sieve)#sieve是個list 241GET_ITER //開始迭代sieve >> 242FOR_ITER 24(to269)//繼續iter下一個x 245STORE_FAST 4(x) ... 266JUMP_ABSOLUTE 242//迴圈
這是典型的for+in結構,轉為python程式碼就是:
forxinsieve:
0x5.if
POP_JUMP_IF_FALSE和JUMP_FORWARD一般用於分支判斷跳轉。POP_JUMP_IF_FALSE表示條件結果為FALSE就跳轉到目標偏移指令。JUMP_FORWARD直接跳轉到目標偏移指令。
23 0LOAD_CONST 1(0) 3STORE_FAST 0(i)//i=0 24 6LOAD_FAST 0(i) 9LOAD_CONST 2(5) 12COMPARE_OP 0(<) 15POP_JUMP_IF_FALSE 26 25 18LOAD_CONST 3('i < 5') 21PRINT_ITEM 22PRINT_NEWLINE 23JUMP_FORWARD 25(to51) 26 >> 26LOAD_FAST 0(i) 29LOAD_CONST 2(5) 32COMPARE_OP 4(>) 35POP_JUMP_IF_FALSE 46 27 38LOAD_CONST 4('i > 5') 41PRINT_ITEM 42PRINT_NEWLINE 43JUMP_FORWARD 5(to51) 29 >> 46LOAD_CONST 5('i = 5') 49PRINT_ITEM 50PRINT_NEWLINE >> 51LOAD_CONST 0(None)
轉為python程式碼是:
i=0 ifi <5: print'i < 5' elifi >5: print'i > 5' else: print'i = 5'
0x6.分辨函式
1.函式範圍
前面介紹第二列表示指令在函式中的偏移地址,所以看到0就是函式開始,下一個0前一條指令就是函式結束位置,當然也可以通過RETURN_VALUE來確定函式結尾
54 0LOAD_FAST 1(plist)//函式開始 3LOAD_CONST 0(None) 6COMPARE_OP 2(==) 9POP_JUMP_IF_FALSE 33 55 ... 67 >> 139LOAD_FAST 2(fs) 142RETURN_VALUE 70 0LOAD_CONST 1('FLAG')//另一個函式開始 3STORE_FAST 0(flag)
2.函式呼叫
函式呼叫類似於push+call的彙編結構,壓棧引數從左到右依次壓入(當然不是push,而是讀取指令LOAD_xxxx來指定引數)。
函式名一般通過LOAD_GLOBAL指令指定,如果是模組函式或者類成員函式通過LOAD_GLOBAL+LOAD_ATTR來指定。
先指定要呼叫的函式,然後壓引數,最後通過CALL_FUNCTION呼叫。
CALL_FUNCTION後面的值表示有幾個引數。
支援巢狀呼叫:
6 0LOAD_GLOBAL 0(int)//int函式 3LOAD_GLOBAL 1(math)//math模組 6LOAD_ATTR 2(sqrt)//sqrt函式 9LOAD_FAST 0(n)//引數 12CALL_FUNCTION 1 15CALL_FUNCTION 1 18STORE_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
參考資料
- python dis官方文件
- google搜尋dis指令
- https://github.com/vstinner/bytecode
- https://blog.hakril.net/articles/2-understanding-python-execution-tracer.html
- A Python Interpreter Written in Python
- https://blog.csdn.net/qs9816/article/details/51661659
- https://github.com/Mysterie/uncompyle2
相關文章
- 死磕 jdk原始碼之HashMap原始碼分析2019-04-13JDK原始碼HashMap
- Python 位元組碼介紹2018-07-09Python
- 什麼是位元組碼?python位元組碼詳細介紹!2021-03-08Python
- pyc位元組碼文字轉python程式碼2024-05-15Python
- 死磕Spring原始碼-依賴注入2021-09-09Spring原始碼依賴注入
- python反編譯之位元組碼2019-05-19Python編譯
- 死磕 java集合之TreeSet原始碼分析2019-04-16Java原始碼
- 死磕 java集合之WeakHashMap原始碼分析2019-04-13JavaHashMap原始碼
- 死磕 java集合之LinkedList原始碼分析2019-05-03Java原始碼
- 死磕 java集合之ConcurrentLinkedQueue原始碼分析2019-04-27Java原始碼
- 死磕 java集合之PriorityQueue原始碼分析2019-04-20Java原始碼
- 死磕 java集合之ArrayList原始碼分析2019-04-01Java原始碼
- 死磕 java集合之HashMap原始碼分析2019-04-01JavaHashMap原始碼
- 死磕 java集合之CopyOnWriteArrayList原始碼分析2019-04-01Java原始碼
- 死磕 java集合之LinkedHashMap原始碼分析2019-04-01JavaHashMap原始碼
- 死磕以太坊原始碼分析之state2021-01-13原始碼
- 死磕以太坊原始碼分析之txpool2020-12-25原始碼
- 死磕 java集合之ConcurrentHashMap原始碼分析(一)2019-04-08JavaHashMap原始碼
- SpringBoot(一)啟動相關【死磕原始碼】2018-07-18Spring Boot原始碼
- 死磕以太坊原始碼分析之downloader同步2020-12-23原始碼
- 死磕以太坊原始碼分析之Fetcher同步2020-12-03原始碼
- 死磕hyperledger fabric原始碼|Order節點概述2021-02-27原始碼
- 死磕 java併發包之AtomicInteger原始碼分析2019-05-06Java原始碼
- 死磕 java併發包之LongAdder原始碼分析2019-05-12Java原始碼
- 死磕以太坊原始碼分析之MPT樹-上2021-01-04原始碼
- 死磕以太坊原始碼分析之rlpx協議2020-11-24原始碼協議
- 死磕以太坊原始碼分析之Kademlia演算法2020-11-22原始碼演算法
- 死磕以太坊原始碼分析之EVM指令集2021-02-21原始碼
- 死磕以太坊原始碼分析之挖礦流程分析2020-12-12原始碼
- 死磕 java集合之TreeMap原始碼分析(四)-內含彩蛋2019-04-04Java原始碼
- 死磕 java集合之ConcurrentSkipListMap原始碼分析——發現個bug2019-04-14Java原始碼
- 死磕原始碼分析,只有頭髮少的人才研究過2018-12-18原始碼
- 死磕Spring原始碼-MVC處理HTTP分發請求2021-09-09Spring原始碼MVCHTTP
- python2 traceback模組原始碼解析2019-03-30Python原始碼
- 位元組碼2018-07-02
- 死磕以太坊原始碼分析之EVM如何呼叫ABI編碼的外部方法2021-02-25原始碼
- 死磕 java集合之ConcurrentHashMap原始碼分析(二)——擴容全解析2019-04-13JavaHashMap原始碼
- 死磕 java集合之ConcurrentHashMap原始碼分析(一)——插入元素全解析2019-04-13JavaHashMap原始碼