浮生半日:探究Python字节码

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浮生半日:探究Python字节码相关的知识,希望对你有一定的参考价值。

 好吧!“人生苦短,请用Python”,作为python爱好者以及安全从业者,而且最近也碰到了一些这方面的问题,懂点python字节码还是很有必要的。

 Python是一门解释性语言,它的具体工作流程如下:

    1:编译,形成.pyc或.pyo后缀的语言

    2:放入解释器,解释器执行字节流(opecode)

 和java字节码一样,他们都是基于栈进行解释的。首先,先来看对pyc文件进行一个直观的理解:

一:直面pyc文件

  pyc文件的生成一般用于加快Python的解释速度,运行时,如果pyc的编译时间晚于py的修改时间,会直接运行pyc文件。所以一般需要以module形式加载时,才会直接生成,生成pyc文件脚本如下:

import imp
import sys
def generate_pyc(name):
    fp, pathname, description = imp.find_module(name)
    try:
        imp.load_module(name, fp, pathname, description)    
    finally:
        if fp:
            fp.close()
if __name__ == __main__:
    t = raw_input()
    generate_pyc(t)

  通过load_module形式进行加载,来间接获取pyc文件。

二:分析pyc文件

  pyc文件的格式分为两种

  3.4以前前四个字节为magic,这个和python版本相关,然后四个字节为编译时间,后面为code_type(PycodeObject)。而在3.4及以后则在编译时间之后添加一个filesize。下面是CPython中对code_type数据结构的描述


#define OFF(x) offsetof(PyCodeObject, x)
static PyMemberDef code_memberlist[] = {
    {"co_argcount",     T_INT,          OFF(co_argcount),       READONLY},
    {"co_nlocals",      T_INT,          OFF(co_nlocals),        READONLY},
    {"co_stacksize",T_INT,              OFF(co_stacksize),      READONLY},
    {"co_flags",        T_INT,          OFF(co_flags),          READONLY},
    {"co_code",         T_OBJECT,       OFF(co_code),           READONLY},
    {"co_consts",       T_OBJECT,       OFF(co_consts),         READONLY},
    {"co_names",        T_OBJECT,       OFF(co_names),          READONLY},
    {"co_varnames",     T_OBJECT,       OFF(co_varnames),       READONLY},
    {"co_freevars",     T_OBJECT,       OFF(co_freevars),       READONLY},
    {"co_cellvars",     T_OBJECT,       OFF(co_cellvars),       READONLY},
    {"co_filename",     T_OBJECT,       OFF(co_filename),       READONLY},
    {"co_name",         T_OBJECT,       OFF(co_name),           READONLY},
    {"co_firstlineno", T_INT,           OFF(co_firstlineno),    READONLY},
    {"co_lnotab",       T_OBJECT,       OFF(co_lnotab),         READONLY},
    {NULL}      /* Sentinel */
};

  下面是一个基于python2.7的pyc文件例子的部分截图

                      技术分享

  解析pyc文件可以得到,如下:

magic 03f30d0a
moddate aa813e59 (Mon Jun 12 19:57:30 2017)
code
   argcount 0
   nlocals 0
   stacksize 1
   flags 0040
   code 640000474864010053      
   consts
      hello world!
      None
   names ()
   varnames ()
   freevars ()
   cellvars ()
   filename C:\\\\Users\\\\Administrator\\\\Desktop\\\\test3.py
   name <module>
   firstlineno 1
   lnotab 

 

  本文着重讲解的是co_code,也就是opcode。

三:解读opcode

  opcode代码在理解上还是很简单的,想要得到某个函数或者module的话可以直接使用dis.dis()或者dis.disassemble()函数,这里先使用dis.dis()函数,直接观察函数的opcode。下面是一个简单的python脚本:

import dis
def test1():
    a = "hello"
    b = " "
    c = "world"
    d = a +b+c
    print d
    
print dis.dis(test1)

 

  可以得到test1函数的opcode代码

  3           0 LOAD_CONST               1 (hello)      //‘hello‘压栈
              3 STORE_FAST               0 (a)             //‘hell0‘出栈,同时local[‘a‘] = ‘hello‘

  4           6 LOAD_CONST               2 ( )
              9 STORE_FAST               1 (b)

  5          12 LOAD_CONST               3 (world)
             15 STORE_FAST               2 (c)

  6          18 LOAD_FAST                0 (a)           //将local[‘a‘]压栈
             21 LOAD_FAST                1 (b)           //将local[‘b‘]压栈
             24 BINARY_ADD                               //栈中a,b相加,结果压栈
             25 LOAD_FAST                2 (c)           
             28 BINARY_ADD          
             29 STORE_FAST               3 (d)

  7          32 LOAD_FAST                3 (d)
             35 PRINT_ITEM          
             36 PRINT_NEWLINE       
             37 LOAD_CONST               0 (None)
             40 RETURN_VALUE        
None

  源码和opcode对照并结合注释,理解起来还是很方便的。第一列是源代码的行数,第二列是字节相对于第一个字节的偏移,第三个则是命令,第四个是命令参数。opcode的格式如下:

                                技术分享

   想要彻底理解上面的代码,必须先理解python基于栈的运行机制,python的运行是单纯模拟cpu运行的机制,看一下它的堆结构

typedef struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;    /* 调用者的帧 */
    PyCodeObject *f_code;     /* 帧对应的字节码对象 */
    PyObject *f_builtins;     /* 内置名字空间 */
    PyObject *f_globals;      /* 全局名字空间 */
    PyObject *f_locals;       /* 本地名字空间 */
    PyObject **f_valuestack;  /* 运行时栈底 */
    PyObject **f_stacktop;    /* 运行时栈顶 */
    …….

 

  可以利用sys.getFrame来得到运行时的堆栈状态

{a: hello, c: world, frame: <frame object at 0x0000000002DEA3A8>, b:  , d: hello }
{test1: <function test1 at 0x00000000032B9908>, __builtins__: <module __builtin__ (built-in)>, __file__: C:\\\\Users\\\\Administrator\\\\Desktop\\\\test3.py, __package__: None, sys: <module sys (built-in)>, __name__: __main__, __doc__: None, dis: <module dis from C:\\Python27\\lib\\dis.pyc>}
{test1: <function test1 at 0x00000000032B9908>, __builtins__: <module __builtin__ (built-in)>, __file__: C:\\\\Users\\\\Administrator\\\\Desktop\\\\test3.py, __package__: None, sys:

 

四:常见语句以及对应opcode

  1.判断语句

def test2(t):if t > 3:
        print "OK!"

 

  对应

  6           0 LOAD_FAST                0 (t)
              3 LOAD_CONST               1 (3)
              6 COMPARE_OP               4 (>)
              9 POP_JUMP_IF_FALSE       20

  7          12 LOAD_CONST               2 (OK!)
             15 PRINT_ITEM          
             16 PRINT_NEWLINE       
             17 JUMP_FORWARD             0 (to 20)
        >>   20 LOAD_CONST               0 (None)
             23 RETURN_VALUE        
None

   2.循环语句

def test3():
    i = 0
    while t < 10:
        t += i
        i+=1

  对应

  5           0 LOAD_CONST               1 (0)
              3 STORE_FAST               0 (i)

  6           6 SETUP_LOOP              36 (to 45)
        >>    9 LOAD_FAST                1 (t)
             12 LOAD_CONST               2 (10)
             15 COMPARE_OP               0 (<)
             18 POP_JUMP_IF_FALSE       44

  7          21 LOAD_FAST                1 (t)
             24 LOAD_FAST                0 (i)
             27 INPLACE_ADD         
             28 STORE_FAST               1 (t)

  8          31 LOAD_FAST                0 (i)
             34 LOAD_CONST               3 (1)
             37 INPLACE_ADD         
             38 STORE_FAST               0 (i)
             41 JUMP_ABSOLUTE            9
        >>   44 POP_BLOCK           
        >>   45 LOAD_CONST               0 (None)
             48 RETURN_VALUE     

  3.调用操作

def test4(a,b):
    print a+b

def test3():
    test4(3,4)

  对应

 7           0 LOAD_GLOBAL              0 (test4)
              3 LOAD_CONST               1 (3)
              6 LOAD_CONST               2 (4)
              9 CALL_FUNCTION            2
             12 POP_TOP             
             13 LOAD_CONST               0 (None)
             16 RETURN_VALUE    

 


以上是关于浮生半日:探究Python字节码的主要内容,如果未能解决你的问题,请参考以下文章

探究Web常用编码

字节码:分析 Python 执行的终极利器

python字节码(转)

如何下架app

JVM进阶之路十二:字节码指令

实例教程,用python实现字节码编译器和解释器实例教程,用python实现字节码编译器和解释器