【转载】死磕python字节码-手工还原python源码

2022/1/11 1:04:59

本文主要是介绍【转载】死磕python字节码-手工还原python源码,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

0x1.前言

Python 代码先被编译为字节码后,再由Python虚拟机来执行字节码, Python的字节码是一种类似汇编指令的中间语言, 一个Python语句会对应若干字节码指令,虚拟机一条一条执行字节码指令, 从而完成程序执行。
Python dis 模块支持对Python代码进行反汇编, 生成字节码指令。

 

dis.dis()将CPython字节码转为可读的伪代码(类似于汇编代码)。结构如下:

1234567891011121314157           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变量,比如数值、字符串等等,一般用于传给函数的参数

123455       12 LOAD_GLOBAL              1 (test)         15 LOAD_FAST                0 (2) #读取2         18 LOAD_CONST               1 ('output')          21 CALL_FUNCTION            2

转为python代码就是:

1test(2, 'output')

2.局部变量

LOAD_FAST一般加载局部变量的值,也就是读取值,用于计算或者函数调用传参等。
STORE_FAST一般用于保存值到局部变量。

123461          77 LOAD_FAST                0 (n)             80 LOAD_FAST                3 (p)             83 INPLACE_DIVIDE             84 STORE_FAST               0 (n)

这段bytecode转为python就是:

1n = n / p

函数的形参也是局部变量,如何区分出是函数形参还是其他局部变量呢?

 

形参没有初始化,也就是从函数开始到LOAD_FAST该变量的位置,如果没有看到STORE_FAST,那么该变量就是函数形参。

 

而其他局部变量在使用之前肯定会使用STORE_FAST进行初始化。

 

具体看下面的实例:

123456789104           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代码如下,对比一下就一目了然。

123def test(arg1):    local1 = 0    print local1, arg1

3.全局变量

LOAD_GLOBAL用来加载全局变量,包括指定函数名,类名,模块名等全局符号。

 

STORE_GLOBAL用来给全局变量赋值。

12348           6 LOAD_CONST               2 (101)            9 STORE_GLOBAL             0 (global1)            20 LOAD_GLOBAL              0 (global1)            23 PRINT_ITEM

对应的python代码

1234def test():    global global1    global1 = 101    print global

0x3.常用数据类型

1.list

BUILD_LIST用于创建一个list结构。

123413           0 LOAD_CONST               1 (1)             3 LOAD_CONST               2 (2)             6 BUILD_LIST               2             9 STORE_FAST               0 (k)

对应python代码是:

1k = [1, 2]

另外再看看一种常见的创建list的方式如下:

1[x for x in xlist if x!=0 ]

一个实例bytecode如下:

1234567891011121322         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的内容。

1234513           0 BUILD_MAP                1             3 LOAD_CONST               1 (1)             6 LOAD_CONST               2 ('a')             9 STORE_MAP            10 STORE_FAST               0 (k)

对应的python代码是:

1k = {'a': 1}

再看看修改dict的bytecode:

123414          13 LOAD_CONST               3 (2)             16 LOAD_FAST                0 (k)             19 LOAD_CONST               4 ('b')             22 STORE_SUBSCR

对应的python代码是:

1k['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表示如下:

1234567891011SLICE+0()Implements TOS = TOS[:]. SLICE+1()Implements TOS = TOS1[TOS:]. SLICE+2()Implements TOS = TOS1[:TOS]. SLICE+3()Implements TOS = TOS2[TOS1:TOS].

下面看具体实例:

123456789101112131415161718192021222324252627282930313233343513           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循环

1234567891011121314151623           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代码是:

123i = 0    while i < 10:        i += 1

for in结构

123456    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代码就是:

1for x in sieve:

0x5.if

POP_JUMP_IF_FALSEJUMP_FORWARD一般用于分支判断跳转。POP_JUMP_IF_FALSE表示条件结果为FALSE就跳转到目标偏移指令。JUMP_FORWARD直接跳转到目标偏移指令。

123456789101112131415161718192021222324252623           0 LOAD_CONST               1 (0)             3 STORE_FAST               0 (i) //i=024           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代码是:

1234567i = 0if i < 5:    print 'i < 5'elif i > 5:    print 'i > 5'else:    print 'i = 5'

0x6.分辨函数

1.函数范围

前面介绍第二列表示指令在函数中的偏移地址,所以看到0就是函数开始,下一个0前一条指令就是函数结束位置,当然也可以通过RETURN_VALUE来确定函数结尾

123456789101154         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_VALUE70         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后面的值表示有几个参数。

 

支持嵌套调用:

12345676           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代码就是

1nroot = int(math.sqrt(n)) //其中n是一个局部变量或者函数参数,具体看上下文

0x7.其他指令

其他常见指令,一看就明白,就不具体分析了,更多详细内容请看官方文档。

1234567891011121314151617181920212223242526272829303132333435363738INPLACE_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指令,两者区别很简单。

12i += 1 //使用INPLACE_xxxi = i + 1 //使用BINARY_xxxx

参考资料

  1. python dis官方文档
  2. google搜索dis指令
  3. https://github.com/vstinner/bytecode
  4. https://blog.hakril.net/articles/2-understanding-python-execution-tracer.html
  5. A Python Interpreter Written in Python
  6. https://blog.csdn.net/qs9816/article/details/51661659
  7. https://github.com/Mysterie/uncompyle2

本文转载来源:https://bbs.pediy.com/thread-246683.htm



这篇关于【转载】死磕python字节码-手工还原python源码的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程