吉吉于

解读dis反编译的输出结构

昨晚看了一点python cookbook,对里边的list assignment部分有点疑惑,于是想起用dis看下代码背后的逻辑。

翻阅了一些资料,梳理一下dis输出的含义。

关于dis:

doc:http://docs.python.org/2/library/dis.html

关于.pyc

.pyc文件是.py文件经过编译后的二进制文件,是一种跨平台,由虚拟机执行的字节码(byte code)。

不通版本的python编译后.pyc的内容是不同的。

doc:http://docs.python.org/2/tutorial/modules.html#compiled-python-files

示例代码:

dis_list_slice.py

a = [1, 2, 3]

b = a

c = a[1:]

d = [y for x in a]

a[:] = [x for x in a]

运行:

 python -m dis dis_list_slice.py

输出:

1 0 LOAD_CONST 0 (1)

  3 LOAD_CONST 1 (2)

  6 LOAD_CONST 2 (3)

  9 BUILD_LIST 3

  12 STORE_NAME 0 (a)

2 15 LOAD_NAME 0 (a)

  18 STORE_NAME 1 (b)

3 21 LOAD_NAME 0 (a)

  24 LOAD_CONST 0 (1)

  27 SLICE+1

  28 STORE_NAME 2 (c)

4 31 BUILD_LIST 0

  34 LOAD_NAME 0 (a)

  37 GET_ITER

  >> 38 FOR_ITER 12 (to 53)

  41 STORE_NAME 3 (x)

  44 LOAD_NAME 4 (y)

  47 LIST_APPEND 2

  50 JUMP_ABSOLUTE 38

  >> 53 STORE_NAME 5 (d)

5 56 BUILD_LIST 0

  59 LOAD_NAME 0 (a)

  62 GET_ITER

  >> 63 FOR_ITER 12 (to 78)

  66 STORE_NAME 3 (x)

  69 LOAD_NAME 3 (x)

  72 LIST_APPEND 2

  75 JUMP_ABSOLUTE 63

  >> 78 LOAD_NAME 0 (a)

  81 STORE_SLICE+0

  82 LOAD_CONST 3 (None)

  85 RETURN_VALUE

编译:

我们来看看a = [1,2,3] 的真实 byte code

>>> co = compile(‘a=[1,2,3]‘, ‘’, ‘exec’) </p>

>>> co.co_code.encode(‘hex’)

'6400006401006402006703005a00006500005a010064030053'   (一个十六进制串)

解读:

第一行:

1 0 LOAD_CONST 0 (1)

第一列1代表源代码第一行语句执行所编译的字节码,也就是执行a = [1, 2, 3]:

  0 LOAD_CONST 0 (1)

  3 LOAD_CONST 1 (2)

  6 LOAD_CONST 2 (3)

  9 BUILD_LIST 3

  12 STORE_NAME 0 (a)

左边的第一个0代表指令的偏移量(offset),从byte code里边读取 offset = 0 到前两位是64

操作码(opcode)为LOAD_CONST

这里的64 和 LOAD_CONST 是一一对应的,

>>> import opcode

>>> opcode.opname[0x64]

‘LOAD_CONST’

从这里想到了什么?

没错我们可以通过修改opcode来加密代码.

操作指令集合:

http://docs.python.org/2/library/dis.html#python-bytecode-instructions

LOAD_CONST 右边的这个0代表oparg,从byte code 里读取就是:0000

接下来从offset = 3的地方读取两位即:64,opcode为LOAD_CONST,oparg为0100,即为:1

继续,从offset = 4的地方读取两位即:64,opcode为LOAD_CONST,oparg为0200,即为:2

这里的oparg是小端序(little-endian)。

关于大端序以及小端序:

http://zh.wikipedia.org/wiki/%E5%AD%97%E8%8A%82%E5%BA%8F

注意:oparg的含义取决于opcode

例如:对于LOAD_NAME以及LOAD_ATTR来说,oparg代表co_names中的下标。

引用dis.dis 输出源代码第二行的byte code

2 15 LOAD_NAME 0 (a)

  18 STORE_NAME 1 (b)

>>> co = compile(‘a=[1,2,3]; b=a’, ‘’, ‘exec’) </p>

>>> co.co_names

(‘a’, ‘b’)

>>> co.co_code.encode('hex’)

’6400006401006402006703005a00006500005a010064030053′

>>> opcode.opname[0x65]

'LOAD_NAME’

oparg 为0000,即为0,代表在co_names中下标为0的数据,即为a.

====================================================

如有不对的地方,请指出。

转载请注明:[于哲的博客][1] » [解读dis反编译的输出结构][2] [1]: http://lazynight.me [2]: http://lazynight.me/3397.html