一、前言
上一節我們知道了pyc文件是python在編譯過程中出現的主要中間過程文件。pyc文件是二進制的,可以由python虛擬機直接執行的程序。分析pyc文件的文件結構對於實現python編譯與反編譯就顯得十分重要。
Python代碼的編譯結果就是PyCodeObject對象。PyCodeObject對象可以由虛擬機加載后直接運行,而pyc文件就是PyCodeObject對象在硬盤上的保存形式。因此我們先分析PyCodeObject對象的結構,隨后再涉及pyc文件的二進制結構。
二、PyCodeObject對象結構分析
typedef struct {
PyObject_HEAD
int co_argcount; /* 位置參數個數 */
int co_nlocals; /* 局部變量個數 */
int co_stacksize; /* 棧大小 */
int co_flags;
PyObject *co_code; /* 字節碼指令序列 */
PyObject *co_consts; /* 所有常量集合 */
PyObject *co_names; /* 所有符號名稱集合 */
PyObject *co_varnames; /* 局部變量名稱集合 */
PyObject *co_freevars; /* 閉包用的的變量名集合 */
PyObject *co_cellvars; /* 內部嵌套函數引用的變量名集合 */
/* The rest doesn’t count for hash/cmp */
PyObject *co_filename; /* 代碼所在文件名 */
PyObject *co_name; /* 模塊名|函數名|類名 */
int co_firstlineno; /* 代碼塊在文件中的起始行號 */
PyObject *co_lnotab; /* 字節碼指令和行號的對應關系 */
void *co_zombieframe; /* for optimization only (see frameobject.c) */
} PyCodeObject;
上面就是PyCodeObject對象一般情況下包含的屬性名稱及數據類型,每個屬性在虛擬機執行pyc文件時都有其作用,隨后在編譯與反編譯的過程中我們會對上述出現的屬性一一分析。
三、pyc文件生成
python中使用marshal.dump的方法將PyCodeObject對象轉化為對應的二進制文件結構。每個字段在二進制文件中的結構如下圖:
byte表示占用1個字節,long表示占用4個字節,bytes表示該字段可能占用1到多個字節。需要說明的是,PyCodeObject對象中的每一個屬性及值都會按照一定的順序表示在二進制文件里。
pyc文件結構主要包括兩部分:pyc文件頭部表示和PyCodeObject對象部分。上面對PyCodeObject對象的二進制部分已經有了了解,pyc文件頭部比較簡單,在python2中只占用4個字節包含兩個字段magic和mtime,完整的pyc文件結構見下圖:
四、實例分析
上面我們對pyc文件結構已經有了理論上的了解,接下來通過一個實例對實際的二進制文件進行分析。
源文件test.py
s = "hello"
def func():
a = 3
print s
func()
通過執行python2 -m py_compile test.py 可以生成編譯好的pyc文件test.pyc。
我們使用二進制編輯器打開test.pyc
- pyc文件頭部:
- 前4個字節:03f3 0d0a,表示python版本
- 5-8個字節:0e6b 905d,表示pyc文件修改時間
- PyCodeObject對象二進制編譯結果:
- 第9字節:63,TYPE_CODE字段,也就是字符c,值為99,即0x63,表示接下為是一個PyCodeObject對象
- PyCodeObject對象----全局參數:
- 然后4個字節是0x00 0000 00,code block的位置參數個數co_argument,這里是0;
- 再接着4個字節是0x00 0000 00, code block中的局部變量個數co_nlocals,這里是0;
- 再接着4個字節是0x01 0000 00, code block需要的棧空間co_stacksize,這里是1;
- 再接着4個字節是0x40 0000 00, co_flags,這里是64;
- PyCodeObject對象----code block:
- 1個字節0x73為TYPE_CODE字段, 表示該字段為string格式;
- 4個字節0x1a00 0000表示code block段的數據部分占用0x1a個字節,即長度為26;
- 接下來26個字節6400 ...... 6402 0053為該TYPE_CODE字段(數據類型string)部分,也就是pyc文件中包含的字節碼指令
- 再往下的逐個TYPE_CODE字段都是重復結構的,用來表示PyCodeObject對象中的一些其他參數