浮生半日:探究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    
时间: 2024-08-01 10:20:23

浮生半日:探究Python字节码的相关文章

Python字节码与解释器学习

参考:http://blog.jobbole.com/55327/ http://blog.jobbole.com/56300/ http://blog.jobbole.com/56761/ 1. 在交互式命令行中执行命令的内部过程 当你敲下return键的时候,python完成了以下四步:词法分析.句法分析.编译.解释.词法分析的工作就是将你刚才输入的那行代码分解为一些符号token(译者注:包括标示符,关键字,数字, 操作符等).句法分析程序再接收这些符号,并用一种结构来展现它们之间的关系(

python字节码(转)

了解 Python 字节码是什么,Python 如何使用它来执行你的代码,以及知道它是如何帮到你的. 如果你曾经编写过 Python,或者只是使用过 Python,你或许经常会看到 Python 源代码文件--它们的名字以 .py 结尾.你可能还看到过其它类型的文件,比如以 .pyc 结尾的文件,或许你可能听说过它们就是 Python 的 "字节码bytecode" 文件.(在 Python 3 上这些可能不容易看到 -- 因为它们与你的 .py 文件不在同一个目录下,它们在一个叫 _

Python 字节码bytecode

字节码bytecode dis模块是Python字节码反汇编器.通过反汇编支持Cpython的字节码分析. 前置知识 在看字节码之前,先要了解一下code object和frame object,它们在datamodel.html中有介绍 例子: >>> import dis >>> def hello(): ... print('Hello World!') ... >>> hello.__code__ <code object hello a

使用uncompyle2直接反编译python字节码文件pyo/pyc

update:在Mac OS X版的September 10, 2014版(5.0.9-1)中发现安装目录中的src.zip已更换位置至WingIDE.app/Contents/Resources/bin/2.7目录下,其它的Crack操作仍然不变. 实际上只需要把之前的abstract.pyo拿出来,放到新的src.zip中即可,即无需再次发编译. 这里主要介绍一下,python生成的pyo.pyc字节码文件的反编译工具uncompyle2.过程中将结合WingIDE最新版(WingIDE 5

Python源码剖析笔记3-Python执行原理初探

Python源码剖析笔记3-Python执行原理初探 本文简书地址:http://www.jianshu.com/p/03af86845c95 之前写了几篇源码剖析笔记,然而慢慢觉得没有从一个宏观的角度理解python执行原理的话,从底向上分析未免太容易让人疑惑,不如先从宏观上对python执行原理有了一个基本了解,再慢慢探究细节,这样也许会好很多.这也是最近这么久没有更新了笔记了,一直在看源码剖析书籍和源码,希望能够从一个宏观层面理清python执行原理.人说读书从薄读厚,再从厚读薄方是理解了

003dayPython学习初始模块和字节码

一.注释: 1.单行注释 # 被注释的内容 2.多行注释 """ 被注释的内容 """ 二.模块 我们在编程的时候,往往是一个主.py文件,多个功能文件,当用到某个功能的时候将其导入到主文件中 这些以.py结尾的功能文件,我们可以将其称之为模块 模块分为系统模块和自定义模块 系统模块是系统中本身就有的,像sys.time等模块 而自定义模块就是我们自己写的.py文件 导入模块用 import+模块名 即可,例: import sys 运行时,im

《python源码剖析》笔记 python环境初始化

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.线程环境初始化 Py_InitializeEx,Python会首先调用 PyInterpreterState_New创建一个崭新的PyInterpreterState对象. 创建了PyInterpreterState(进程状态)对象之后,Python会调用PyThreadState_New创建PyThreadState(线程状态)对象 全局变量_PyThreadState_Curren

Python源码剖析之准备工作

一个Python程序开发者, 也是C语言爱好者, 为了加强自己对Python语言实现的理解, 最近选择阅读下陈儒老师的书, 对Python3.5.1源码进行阅读, 再次记录下读书笔记.  一.Python的总体结构 Python总体分为三部分: 文件系统(File Groups), 解释器(Interpreter), 运行时环境(Runtim Environement): 文件系统用于存储Python脚本文件(.py), 主要分为内置模块.第三方模块.用户自定义模块: 解释器用于完成Python

[转帖]虚拟内存探究 -- 第二篇:Python 字节

虚拟内存探究 -- 第二篇:Python 字节 http://blog.coderhuo.tech/2017/10/15/Virtual_Memory_python_bytes/ 是真看不懂哦 翻译 虚拟内存  翻译 这是虚拟内存系列文章的第二篇.这次我们要做的事情和<虚拟内存探究 – 第一篇:C strings & /proc>类似,不同的是我们将访问Python 3 脚本的虚拟内存.这会比较费劲, 所以我们需要了解Pyhton3 内部的一些机制. 一.预备知识 本文基于上一篇文章&