`
RednaxelaFX
  • 浏览: 3052941 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

Python 2.6.2的.pyc文件格式

阅读更多
相关链接:
以Python为例讨论高级编程语言程序的wire format与校验
Python 2.6.2的字节码指令集一览

.pyc文件是什么?

Python源码编译的结果就是PyCodeObject(下面将PyCodeObject的实例简称为“代码对象”),每个作用域会编译出一个对应的代码对象,其中名为co_code的PyStringObject保存着代码对象的字节码。
一个Python源文件就是一个模块。每个模块顶层的代码对象通过marshal序列化之后就得到了.pyc文件。marshal以little-endian字节序来序列化数据。
那嵌套于顶层作用域里面的那些作用域,例如函数、类的定义,它们对应的代码对象在哪里?它们每一个都乖乖的躺在上一层作用域的代码对象的co_const(常量池)域里,所以其实顶层代码对象已经嵌套包含了底下其它作用域的代码对象。
PyCodeObject的结构和marshal的序列化逻辑请参考上一段的两个链接。

Python 2.6.2的.pyc文件的结构

============== <- 文件起始。下面是文件头信息
pyc_magic     (=0xD1 0xF2 0x0D 0x0A,4字节,简单校验.pyc的魔数)
mtime         (4字节,.pyc对应的源文件的修改时间)
============== <- 顶层PyCodeObject起始。下面都属于顶层代码对象
TYPE_CODE     (='c',1字节,PyCodeObject的类型标识)
co_argscount  (4字节,位置参数的个数)
co_nlocals    (4字节,局部变量(包括位置参数)的个数)
co_stacksize  (4字节,求值栈的最大深度)
co_flags      (4字节,意义不明)
co_code       (PyStringObject,字节码)
co_consts     (PyTupleObject,常量池)
co_names      (PyTupleObject,所有用到的符号的集合)
co_varnames   (PyTupleObject,局部变量名集合)
co_freevars   (PyTupleObject,自由变量的变量名集合)
co_cellvars   (PyTupleObject,被闭包捕获的局部变量的变量名集合)
co_filename   (PyStringObject,源文件名)
co_firstlineno(4字节,该代码对象中源码的首行对应行号)
co_lnotab     (PyStringObject,字节码偏移量与源码行号的对应关系)
============== <- 顶层PyCodeObject结束。文件结束

以上,整型的域都标出了长度,其它域则标出了类型。为了能清楚的看到文件头与后面的PyCodeObject的界线,用横线特别标注了出来。
因为关键的字节码是存在PyStringObject类型的对象里,该类型通过marshal序列化后的结构也有必要说明:
=========== <- PyStringObject起始
TYPE_STRING(='s',1字节,PyStringObject的类型标识)
length     (4字节,字符串内容的长度)
data       (byte数组,字符串内容)
=========== <- PyStringObject结束

了解这两个结构后,我们可以推出:在Python 2.6.2的.pyc文件中,地址在0x1A开始的4字节整型标识顶层代码对象的字节码的长度,假设长度为len;从0x1E开始,长度为len的数据就是顶层代码对象的字节码。

关于pyc_magic

pyc_magic是两个字节的整数,加上\r\n(0x0D 0x0A就是\r\n)。它是这样算出来的:
Python 2.6a1的Python/import.c
#define MAGIC (62161 | ((long)'\r'<<16) | ((long)'\n'<<24))

(注意marshal写出时保持little-endian字节序,所以\r\n就跑到后面去了。)
据说在magic里包含这样的数据是因为\r\n是换行符,如果.pyc文件被以文本模式打开并编辑,magic就会乱掉,那么Python解释器在试图加载这个损坏了的.pyc文件时就会发现有问题。
可是那个62161是怎么来的?通过阅读Include/code.h的注释,可以观察到每个主要的Python发行版本都有一个特别的数字,版本间各不相同。
于是pyc_magic的作用有三:
1、拒绝完全不可能是正常的.pyc的文件,例如普通文本,图片、音乐,或者别的二进制格式。检查文件的头4个字节已经能有效的筛掉许多无效文件。
2、拒绝不慎被文本编辑器编辑而破损的文件。
3、拒绝不对应的Python解释器生成的.pyc文件。由于不同Python版本的marshal算法可能不同,虚拟机采用的字节码指令集也可能不同,所以保守起见不同版本的Python解释器生成的.pyc文件被认为是不兼容的。

关于mtime

PyCodeObject被序列化为.pyc文件时,会连带对应源文件的修改时间一起记录下来。如果对应的源文件发生了修改,则其mtime会发生变化,以前生成的.pyc文件中的mtime就与源文件新的mtime不一致了。这样Python解释器就能够发现源文件有更新,并为其重新生成.pyc文件。

Python解释器生成.pyc文件一般是靠import机制来激活的,阅读Python/import.c可以看到import时对pyc_magic和mtime的检查和写入。

昨晚做了份Python 2.6.2的字节码指令集一览,有兴趣的同学可以看看~

实际解读一个.pyc文件的字节码
例如这样的一个Python源码:(随手写的,别介意内容)
demo.py
class A():
  x = 1

print(A.x) # 1

# increment A.x by 2
A.x += 2
print(A.x) # 3

# create an instance of A
a = A()
print(a.x) # 3

# increment A.x by 4
a.x += 4
print(a.x) # 7
print(A.x) # 3

让Python解释器将其编译为demo.pyc。只要随便在别的Python程序里import demo就足以得到这个文件了。文件内容是:
Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00000000   D1 F2 0D 0A B3 34 04 4A  63 00 00 00 00 00 00 00   羊..?.Jc.......
00000010   00 03 00 00 00 40 00 00  00 73 66 00 00 00 64 00   .....@...sf...d.
00000020   00 64 05 00 64 01 00 84  00 00 83 00 00 59 5A 00   .d..d..?.?.YZ.
00000030   00 65 00 00 69 01 00 47  48 65 00 00 04 69 01 00   .e..i..GHe...i..
00000040   64 02 00 37 02 5F 01 00  65 00 00 69 01 00 47 48   d..7._..e..i..GH
00000050   65 00 00 83 00 00 5A 02  00 65 02 00 69 01 00 47   e..?.Z..e..i..G
00000060   48 65 02 00 04 69 01 00  64 03 00 37 02 5F 01 00   He...i..d..7._..
00000070   65 02 00 69 01 00 47 48  65 00 00 69 01 00 47 48   e..i..GHe..i..GH
00000080   64 04 00 53 28 06 00 00  00 74 01 00 00 00 41 63   d..S(....t....Ac
00000090   00 00 00 00 00 00 00 00  01 00 00 00 42 00 00 00   ............B...
000000A0   73 0E 00 00 00 65 00 00  5A 01 00 64 00 00 5A 02   s....e..Z..d..Z.
000000B0   00 52 53 28 01 00 00 00  69 01 00 00 00 28 03 00   .RS(....i....(..
000000C0   00 00 74 08 00 00 00 5F  5F 6E 61 6D 65 5F 5F 74   ..t....__name__t
000000D0   0A 00 00 00 5F 5F 6D 6F  64 75 6C 65 5F 5F 74 01   ....__module__t.
000000E0   00 00 00 78 28 00 00 00  00 28 00 00 00 00 28 00   ...x(....(....(.
000000F0   00 00 00 73 07 00 00 00  64 65 6D 6F 2E 70 79 52   ...s....demo.pyR
00000100   00 00 00 00 01 00 00 00  73 02 00 00 00 06 01 69   ........s......i
00000110   02 00 00 00 69 04 00 00  00 4E 28 00 00 00 00 28   ....i....N(....(
00000120   03 00 00 00 52 00 00 00  00 52 03 00 00 00 74 01   ....R....R....t.
00000130   00 00 00 61 28 00 00 00  00 28 00 00 00 00 28 00   ...a(....(....(.
00000140   00 00 00 73 07 00 00 00  64 65 6D 6F 2E 70 79 74   ...s....demo.pyt
00000150   08 00 00 00 3C 6D 6F 64  75 6C 65 3E 01 00 00 00   ....<module>....
00000160   73 10 00 00 00 13 03 08  03 0F 01 08 03 09 01 08   s...............
00000170   03 0F 01 08 01                                     .....

根据本文前半部分的分析,顶层代码的字节码长度是0x66=102字节,位于0x1E-0x83这个范围内。有兴趣的同学可以参考Python指令集列表来试试自行解读字节码指令。

看到字节码的开头,首先是0x64,这是一个LOAD_CONST指令,有参数,所以接着看后面两个字节,0x00 0x00,也就是参数为0(注意little-endian字节序)。LOAD_CONST 0,也就是把常量池下标为0的对象压到求值栈上。
接下来又是0x64,连上参数的0x05 0x00,就是LOAD_CONST 5,也就是把常量池下标为5的对象压到求值栈上。
后面的字节码也可依法类推辨认出来。

事实上Python标准库里已经有提取字节码的库,dis模块。自己分析嫌麻烦的话让dis帮忙解决就行:
(下面代码中,dis.dis(code)的结果就是顶层代码对象中的字节码,
格式:行号  偏移量 指令名 参数(括号内为参数的注解))
E:\Python26>python
Python 2.6.2 (r262:71605, Apr 14 2009, 22:40:02) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import dis, marshal
>>> f = open('demo.pyc', 'rb')
>>> magic = f.read(4)
>>> mtime = f.read(4)
>>> code = marshal.load(f)
>>> dis.dis(code)
  1           0 LOAD_CONST               0 ('A')
              3 LOAD_CONST               5 (())
              6 LOAD_CONST               1 (<code object A at 01707EC0, file "demo.py", line 1>)
              9 MAKE_FUNCTION            0
             12 CALL_FUNCTION            0
             15 BUILD_CLASS
             16 STORE_NAME               0 (A)

  4          19 LOAD_NAME                0 (A)
             22 LOAD_ATTR                1 (x)
             25 PRINT_ITEM
             26 PRINT_NEWLINE

  7          27 LOAD_NAME                0 (A)
             30 DUP_TOP
             31 LOAD_ATTR                1 (x)
             34 LOAD_CONST               2 (2)
             37 INPLACE_ADD
             38 ROT_TWO
             39 STORE_ATTR               1 (x)

  8          42 LOAD_NAME                0 (A)
             45 LOAD_ATTR                1 (x)
             48 PRINT_ITEM
             49 PRINT_NEWLINE

 11          50 LOAD_NAME                0 (A)
             53 CALL_FUNCTION            0
             56 STORE_NAME               2 (a)

 12          59 LOAD_NAME                2 (a)
             62 LOAD_ATTR                1 (x)
             65 PRINT_ITEM
             66 PRINT_NEWLINE

 15          67 LOAD_NAME                2 (a)
             70 DUP_TOP
             71 LOAD_ATTR                1 (x)
             74 LOAD_CONST               3 (4)
             77 INPLACE_ADD
             78 ROT_TWO
             79 STORE_ATTR               1 (x)

 16          82 LOAD_NAME                2 (a)
             85 LOAD_ATTR                1 (x)
             88 PRINT_ITEM
             89 PRINT_NEWLINE

 17          90 LOAD_NAME                0 (A)
             93 LOAD_ATTR                1 (x)
             96 PRINT_ITEM
             97 PRINT_NEWLINE
             98 LOAD_CONST               4 (None)
            101 RETURN_VALUE
>>> quit()


P.S. 其实这文单独看挺火星的……我是为了让另外一帖更干净些才把分析.pyc文件格式的部分拆了出来。另外一帖是:以Python为例讨论高级编程语言程序的wire format与校验
分享到:
评论
1 楼 lwwin 2009-05-09  
强了……这也能够看……- -+

相关推荐

    python官方2.6.2.amd64版本msi安装包

    这个“python-2.6.2.amd64.msi”安装包是为Windows操作系统设计的,使用Microsoft Installer(MSI)技术进行分发,便于用户在Windows环境中安装Python。 首先,Python 2.6.2包含了Python解释器,这是一个能够执行...

    mobiscroll.custom-2.6.2.min.js

    mobiscroll.custom-2.6.2.min.js mobiscroll.custom-2.6.2.min.js mobiscroll.custom-2.6.2.min.js mobiscroll.custom-2.6.2.min.js mobiscroll.custom-2.6.2.min.js

    电子-python2.6.2.rar

    标题中的“电子-python2.6.2.rar”指的是一个关于Python编程语言的压缩文件,特别提到了版本号为2.6.2。这个版本在Python的发展历程中属于较早的版本,发布于2010年,对于学习历史版本的Python编程或者针对特定项目...

    python-2.6.2.amd64.msi

    Python是一种广泛使用的解释型、高级编程、通用型编程语言,由吉多·范罗苏姆创造,第一版发布于1991年。Python是ABC语言的后继者,也可以视之为一种使用传统中缀表达式的LISP方言。Python的设计哲学强调代码的...

    gson-2.6.2.jar包(com.google.code.gson:gson:2.6.2)

    在Android Studio中,通常会将外部依赖的库文件如gson-2.6.2.jar添加到项目的`libs`目录下。这样做是因为Google的官方仓库可能由于网络问题或地域限制无法直接访问,此时手动下载并添加库文件成为了必要的解决方案。...

    xerces-2.6.2.jar

    xerces-2.6.2.jar xerces-2.6.2.jar

    jedis-2.6.2.jar最新的jedis包

    jedis-2.6.2.jar最新的jedis包

    freetype-2.6.2.tar.gz

    使用`tar`命令来解压文件,命令格式通常是`tar -zxvf 文件名`,在这里就是`tar -zxvf freetype-2.6.2.tar.gz`。这将在当前目录下创建一个名为`freetype-2.6.2`的文件夹,包含所有源代码和其他相关文件。 2. **进入...

    Python-2.6.2c1.tar.bz2Python-2.6.2c1.tar.bz2Python-2.6.2c1.tar.bz2Python-2.6.2c1.tar.bz2Python-2.6.2c1.tar.bz2Python-2.6.2c1.tar.bz2Python-2.6.2c1.tar.bz2

    Python-2.6.2c1.tar.bz2Python-2.6.2c1.tar.bz2Python-2.6.2c1.tar.bz2Python-2.6.2c1.tar.bz2Python-2.6.2c1.tar.bz2Python-2.6.2c1.tar.bz2Python-2.6.2c1.tar.bz2

    modernizr-2.6.2.min.js

    此资源从wall的外面获取的,modernizr是一个开源的javascript库,利用它的富特性检测工功能,可以对HTML5文档进行更好的控制. 简单的说,有了它你就可以知道浏览器...更多的内容可以参考《javascript dom编程艺术》.

    TraceParts.v2.6.2.SP2 Lz0

    《TraceParts.v2.6.2.SP2 Lz0:三维零件库的高效管理与应用》 TraceParts是一款全球知名的三维零件库软件,版本v2.6.2.SP2是其重要的更新版本,通过Lz0压缩技术进行封装,旨在为用户提供更便捷、高效的零部件数据...

    hadoop-2.6.2.tar.gz

    这个"hadoop-2.6.2.tar.gz"文件是一个包含Hadoop 2.6.2版本源代码的压缩包,适合开发者深入理解Hadoop的工作原理并进行二次开发。 Hadoop的核心组件包括HDFS(Hadoop Distributed File System)和MapReduce。HDFS是...

    python官方2.6.2版本msi安装包

    Python 2.6.2.msi文件将帮助用户在他们的系统上安装Python解释器、标准库和其他必需的组件,以便开始编写和运行Python代码。 然而,需要注意的是,Python 2.6已经于2020年1月1日停止了官方支持,不再接收安全更新和...

    Gson-2.6.2.jar

    Gson-2.6.2.jar

    xmlunit-core-2.6.2.jar

    xmlunit-core-2.6.2.jar 将maven所在路径下conf/settings.xml中标签中的镜像从aliyun暂时改回默认(注掉即可),再用命令行跑mvn springboot:run

    spring-boot-2.6.2.jar

    spring-boot-2.6.2.jar

    bochs-2.6.2 .tar.gz

    在解压"bochs-2.6.2"后,通常会得到一系列文件和目录,如源代码、构建脚本、配置文件、文档等。用户需要按照提供的编译指示来编译和安装Bochs,以便在本地系统上使用。这通常涉及到配置文件的编辑,选择要模拟的硬件...

    apache-cxf-2.6.2.zip

    1. **解压文件**:将"apache-cxf-2.6.2.zip"解压到本地文件系统。 2. **配置环境**:根据你的项目需求,将解压后的库添加到项目的类路径中。 3. **创建服务**:使用CXF提供的工具或者API创建Web服务接口和实现。 4. ...

    xerces-2.6.2.jar读取大数据execl用类

    在这种情况下,使用像"xerces-2.6.2.jar"这样的XML解析库可以帮助我们优化读取过程,将其转换为更高效的XML格式。Xerces是Apache软件基金会开发的一个开源XML解析器,它提供了对XML 1.0和1.1规范的支持,以及DOM、...

    mariadb-java-client-2.6.2.jar

    MariaDB数据库驱动包

Global site tag (gtag.js) - Google Analytics