【转载】死磕python字节码-手工还原python源码
0x1.前言
Python 代码先被编译为字节码后,再由Python虚拟机来执行字节码, Python的字节码是一种类似汇编指令的中间语言, 一个Python语句会对应若干字节码指令,虚拟机一条一条执行字节码指令, 从而完成程序执行。
Python dis 模块支持对Python代码进行反汇编, 生成字节码指令。
dis.dis()
将CPython字节码转为可读的伪代码(类似于汇编代码)。结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 |
其实就是这样的结构:
1 | 源码行号 | 指令在函数中的偏移 | 指令符号 | 指令参数 | 实际参数值 |
0x2.变量
1.const
LOAD_CONST
加载const
变量,比如数值、字符串等等,一般用于传给函数的参数
1 2 3 4 | 55 12 LOAD_GLOBAL 1 (test) 15 LOAD_FAST 0 ( 2 ) #读取2 18 LOAD_CONST 1 ( 'output' ) 21 CALL_FUNCTION 2 |
转为python代码就是:
1 | test( 2 , 'output' ) |
2.局部变量
LOAD_FAST
一般加载局部变量的值,也就是读取值,用于计算或者函数调用传参等。STORE_FAST
一般用于保存值到局部变量。
1 2 3 4 | 61 77 LOAD_FAST 0 (n) 80 LOAD_FAST 3 (p) 83 INPLACE_DIVIDE 84 STORE_FAST 0 (n) |
这段bytecode转为python就是:
1 | n = n / p |
函数的形参也是局部变量,如何区分出是函数形参还是其他局部变量呢?
形参没有初始化,也就是从函数开始到LOAD_FAST
该变量的位置,如果没有看到STORE_FAST
,那么该变量就是函数形参。
而其他局部变量在使用之前肯定会使用STORE_FAST
进行初始化。
具体看下面的实例:
1 2 3 4 5 6 7 8 9 10 | 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代码如下,对比一下就一目了然。
1 2 3 | def test(arg1): local1 = 0 print local1, arg1 |
3.全局变量
LOAD_GLOBAL
用来加载全局变量,包括指定函数名,类名,模块名等全局符号。
STORE_GLOBAL
用来给全局变量赋值。
1 2 3 4 | 8 6 LOAD_CONST 2 ( 101 ) 9 STORE_GLOBAL 0 (global1) 20 LOAD_GLOBAL 0 (global1) 23 PRINT_ITEM |
对应的python代码
1 2 3 4 | def test(): global global1 global1 = 101 print global |
0x3.常用数据类型
1.list
BUILD_LIST
用于创建一个list结构。
1 2 3 4 | 13 0 LOAD_CONST 1 ( 1 ) 3 LOAD_CONST 2 ( 2 ) 6 BUILD_LIST 2 9 STORE_FAST 0 (k) |
对应python代码是:
1 | k = [ 1 , 2 ] |
另外再看看一种常见的创建list的方式如下:
1 | [x for x in xlist if x! = 0 ] |
一个实例bytecode如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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代码是:
1 | [ for x in sieve if x ! = 0 ] |
2.dict
BUILD_MAP
用于创建一个空的dict。STORE_MAP
用于初始化dict的内容。
1 2 3 4 5 | 13 0 BUILD_MAP 1 3 LOAD_CONST 1 ( 1 ) 6 LOAD_CONST 2 ( 'a' ) 9 STORE_MAP 10 STORE_FAST 0 (k) |
对应的python代码是:
1 | k = { 'a' : 1 } |
再看看修改dict的bytecode:
1 2 3 4 | 14 13 LOAD_CONST 3 ( 2 ) 16 LOAD_FAST 0 (k) 19 LOAD_CONST 4 ( 'b' ) 22 STORE_SUBSCR |
对应的python代码是:
1 | 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
表示如下:
1 2 3 4 5 6 7 8 9 10 11 | SLICE + 0 () Implements TOS = TOS[:]. SLICE + 1 () Implements TOS = TOS1[TOS:]. SLICE + 2 () Implements TOS = TOS1[:TOS]. SLICE + 3 () Implements TOS = TOS2[TOS1:TOS]. |
下面看具体实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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代码是:
1 2 3 | i = 0 while i < 10 : i + = 1 |
for in结构
1 2 3 4 5 6 | 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代码就是:
1 | for x in sieve: |
0x5.if
POP_JUMP_IF_FALSE
和JUMP_FORWARD
一般用于分支判断跳转。POP_JUMP_IF_FALSE
表示条件结果为FALSE
就跳转到目标偏移指令。JUMP_FORWARD
直接跳转到目标偏移指令。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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代码是:
1 2 3 4 5 6 7 | i = 0 if i < 5 : print 'i < 5' elif i > 5 : print 'i > 5' else : print 'i = 5' |
0x6.分辨函数
1.函数范围
前面介绍第二列表示指令在函数中的偏移地址,所以看到0就是函数开始,下一个0前一条指令就是函数结束位置,当然也可以通过RETURN_VALUE
来确定函数结尾
1 2 3 4 5 6 7 8 9 10 11 | 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
后面的值表示有几个参数。
支持嵌套调用:
1 2 3 4 5 6 7 | 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
代码就是
1 | nroot = int (math.sqrt(n)) / / 其中n是一个局部变量或者函数参数,具体看上下文 |
0x7.其他指令
其他常见指令,一看就明白,就不具体分析了,更多详细内容请看官方文档。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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
指令,两者区别很简单。
1 2 | i + = 1 / / 使用INPLACE_xxx i = i + 1 / / 使用BINARY_xxxx |