Python pyc格式解析

這篇文章只是純粹分析python pyc文件格式胜茧,主要是關(guān)于pyc在文件中的存儲(chǔ)方式進(jìn)行了解析脑题。pyc是python字節(jié)碼在文件中存儲(chǔ)的方式摇展,而在虛擬機(jī)運(yùn)行時(shí)環(huán)境中對(duì)應(yīng)PyCodeObject對(duì)象洋闽。關(guān)于PyFrameObject以及PyFunctionObject等運(yùn)行時(shí)結(jié)構(gòu)阐污,后續(xù)希望學(xué)習(xí)透徹了能夠一并分析缰猴。

1.示例文件

源文件test.py

s = "hello"                                                                                                                                                    

def func():
  a = 3 
  print s

func()

通過執(zhí)行python pyc_generator.py test 可以生成編譯好的pyc文件。

##pyc_generator.py
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__":
  generate_pyc(sys.argv[1])

得到test.pyc后疤剑,執(zhí)行hexdump -C test.pyc可以得到如下二進(jìn)制字符流滑绒。

00000000  03 f3 0d 0a f6 e9 38 55  63 00 00 00 00 00 00 00  |......8Uc.......|
00000010  00 01 00 00 00 40 00 00  00 73 1a 00 00 00 64 00  |.....@...s....d.|
00000020  00 5a 00 00 64 01 00 84  00 00 5a 01 00 65 01 00  |.Z..d.....Z..e..|
00000030  83 00 00 01 64 02 00 53  28 03 00 00 00 74 05 00  |....d..S(....t..|
00000040  00 00 68 65 6c 6c 6f 63  00 00 00 00 01 00 00 00  |..helloc........|
00000050  01 00 00 00 43 00 00 00  73 0f 00 00 00 64 01 00  |....C...s....d..|
00000060  7d 00 00 74 00 00 47 48  64 00 00 53 28 02 00 00  |}..t..GHd..S(...|
00000070  00 4e 69 03 00 00 00 28  01 00 00 00 74 01 00 00  |.Ni....(....t...|
00000080  00 73 28 01 00 00 00 74  01 00 00 00 61 28 00 00  |.s(....t....a(..|
00000090  00 00 28 00 00 00 00 73  1e 00 00 00 2f 55 73 65  |..(....s..../Use|
000000a0  72 73 2f 73 73 6a 2f 50  72 6f 67 2f 70 79 74 68  |rs/ssj/Prog/pyth|
000000b0  6f 6e 2f 74 65 73 74 2e  70 79 74 04 00 00 00 66  |on/test.pyt....f|
000000c0  75 6e 63 03 00 00 00 73  04 00 00 00 00 01 06 01  |unc....s........|
000000d0  4e 28 02 00 00 00 52 01  00 00 00 52 03 00 00 00  |N(....R....R....|
000000e0  28 00 00 00 00 28 00 00  00 00 28 00 00 00 00 73  |(....(....(....s|
000000f0  1e 00 00 00 2f 55 73 65  72 73 2f 73 73 6a 2f 50  |..../Users/ssj/P|
00000100  72 6f 67 2f 70 79 74 68  6f 6e 2f 74 65 73 74 2e  |rog/python/test.|
00000110  70 79 74 08 00 00 00 3c  6d 6f 64 75 6c 65 3e 01  |pyt....<module>.|
00000120  00 00 00 73 04 00 00 00  06 02 09 04              |...s........|
0000012c

2.PyCodeObject結(jié)構(gòu)

PyCodeObject格式如下:

這個(gè)圖片轉(zhuǎn)自UC技術(shù)博客,參見參考資料1隘膘。當(dāng)然這個(gè)圖片還有些字段沒有寫出來疑故,比如co_names, co_varnames, co_freevars, co_cellvars,co_filename, co_name, co_firstlineno, co_lnotab。

3.Pyc格式解析

首先4個(gè)字節(jié)是magic number弯菊,03f30d0a 其中0d0a就是\r\n了接下來4個(gè)字節(jié)是時(shí)間纵势,這里是d2e73855,注意到是小端模式,所以實(shí)際是0x5538e7d2,可以發(fā)現(xiàn)是我開始編譯的時(shí)間钦铁。然后就是PyCodeObject對(duì)象了软舌。首先是對(duì)象標(biāo)識(shí)TYPE_CODE,也就是字符c牛曹,值為99佛点,即0x63.然后4個(gè)字節(jié)是全局code block的位置參數(shù)個(gè)數(shù)co_argument,這里是0.再接著4個(gè)字節(jié)是全局code block中的局部變量個(gè)數(shù)co_nlocals黎比,這里是0.接著4個(gè)字節(jié)是code block需要的棾空間co_stacksize,這里值為1.然后4個(gè)字節(jié)是co_flags阅虫,這里是64.

接下來從0x73開始就是code block的字節(jié)碼序列co_code演闭。注意到它是PyStringObject形式存在,因此颓帝,開始寫PyStringObject米碰,注意到首先寫1個(gè)字節(jié)的類型標(biāo)識(shí)TYPE_STRING, 即s购城,對(duì)應(yīng)0x73见间。然后4個(gè)字節(jié)標(biāo)識(shí)長(zhǎng)度為1a,也就是26個(gè)字節(jié)工猜。從0x64開始就是co_code內(nèi)容了杆兵。通過dis命令來看一下內(nèi)容:

In [39]: source = open("test.py").read()
In [40]: co = compile(source, 'test.py', 'exec')

In [41]: co.co_consts
Out[41]: ('hello', <code object func at 0x1075710a8, file "test.py", line 3>, None)

In [42]: co.co_names
Out[42]: ('s', 'func')

In [38]: dis.dis(co)
  1           0 LOAD_CONST               0 ('hello') #將co.co_consts[0]即'hello'壓棧
              3 STORE_NAME               0 (s) #以co.co_names[0]作為key屋彪,將'hello'出棧,然后設(shè)置f->f_locas['s'] = 'hello' 

  3           6 LOAD_CONST               1 (<code object func at 0x1075710a8, file "test.py", line 3>) ##將co.co_consts[1]即func的字節(jié)碼對(duì)象壓棧
              9 MAKE_FUNCTION            0 ##創(chuàng)建函數(shù)對(duì)象并壓棧
             12 STORE_NAME               1 (func) #f->f_locals['func']=函數(shù)對(duì)象

  7          15 LOAD_NAME                1 (func) #將f->f_locals['func']即函數(shù)對(duì)象壓棧
             18 CALL_FUNCTION            0 #調(diào)用函數(shù),在新棧幀執(zhí)行
             21 POP_TOP                   ##函數(shù)返回值出棧
             22 LOAD_CONST               2 (None) ##None壓棧
             25 RETURN_VALUE        ##返回None

果然是正好26個(gè)字節(jié)跌造,其中內(nèi)容分別對(duì)應(yīng)這些指令担钮,其中第一列是在源碼中的行數(shù)刀脏,第二列是該指令在co_code中的偏移止吐,第三列是opcode,分為有操作數(shù)和無操作數(shù)兩種箭昵,是一個(gè)字節(jié)的整數(shù)税朴。第四列是操作數(shù),占兩個(gè)字節(jié)家制。
那么這些指令對(duì)應(yīng)的就是我們看到的pyc文件中的內(nèi)容了正林,具體意義參見代碼中的注釋。LOAD_CONST指令為0x64颤殴,然后兩個(gè)字節(jié)操作數(shù)是0.接下來是STORE_NAME指令0x5a觅廓,操作數(shù)是0.其他以此類推,frame相關(guān)內(nèi)容后面再解析涵但。

接下來從0x28開始是co.co_consts內(nèi)容杈绸,我們知道這是一個(gè)PyTupleObject對(duì)象帖蔓,保存著code block的常量,如在前面看到的那樣瞳脓,我們知道它有3個(gè)元素塑娇,分別是字符串hello,code object對(duì)象func以及None劫侧。那么PyTupleObject跟PyListObject類似埋酬,首先是記錄類型標(biāo)示TYPE_TUPLE,即'(',也就是0x28了板辽。接下來4個(gè)字節(jié)是長(zhǎng)度奇瘦,這里是3表示有3個(gè)元素棘催。然后是元素內(nèi)容劲弦,第一個(gè)是'hello‘,它是PyStringObject對(duì)象,因此醇坝,先寫入標(biāo)記TYPE_INTERNED,即't'邑跪,也就是上面的0x74了,然后呢呼猪,是寫入4個(gè)字節(jié)的長(zhǎng)度画畅,共5個(gè)字節(jié),所以這是5宋距,接著就是hello這5個(gè)字節(jié)轴踱。

第二個(gè)是code object,好吧谚赎,這個(gè)就相當(dāng)于跟之前的流程再來一遍了淫僻。
0x63跟之前的一樣是TYPE_CODE的標(biāo)示'c',然后就是code object的各個(gè)字段了壶唤。還是來一遍雳灵,分別如下

First Header Second Header
co_argcount 0
co_nlocals 1
co_stacksize 1
co_flags 67
co_code 標(biāo)示0x73,即TYPE_STRING闸盔。長(zhǎng)度0x0f悯辙,即15個(gè)字節(jié)長(zhǎng)度。然后從0x64開始就是co_code內(nèi)容迎吵。

下面分析下func的co_code躲撰,首先看下dis的結(jié)果:


In [63]: func.co_nlocals
Out[63]: 1

In [64]: func.co_consts
Out[64]: (None, 3)

In [65]: func.co_names
Out[65]: ('s',)

In [66]: func.co_varnames
Out[66]: ('a',)

In [62]: dis.dis(func)
  4           0 LOAD_CONST               1 (3)  #將func.co_consts[1]即3壓棧
              3 STORE_FAST               0 (a)  #存儲(chǔ)3在變量a中

  5           6 LOAD_GLOBAL              0 (s) #壓入全局變量s
              9 PRINT_ITEM               ##打印s
             10 PRINT_NEWLINE            ##打印換行
             11 LOAD_CONST               0 (None) #None壓棧
             14 RETURN_VALUE     ##函數(shù)返回None

接下來就是func的co_consts字段了,同樣是PyTupleObject對(duì)象击费,先是類型標(biāo)示0x28茴肥,然后4個(gè)字節(jié)為長(zhǎng)度2.接著第一個(gè)元素是None(N),即0x4e荡灾,然后是第二個(gè)元素3瓤狐,類型標(biāo)示是TYPE_INT(i)瞬铸,即0x69.后面4個(gè)字節(jié)是整數(shù)3.

再接著就是co_names,同樣是PyTupleObject對(duì)象础锐,顯示標(biāo)示0x28嗓节,然后4個(gè)字節(jié)為長(zhǎng)度1,然后字符s是TYPE_INTERNED類型皆警,于是接著是標(biāo)示't'拦宣,即0x74,然后是字符內(nèi)容s(0x73)信姓。

接下來是co_varnames鸵隧,同樣是PyTupleObject,類型是0x28意推,然后4個(gè)字節(jié)為長(zhǎng)度1豆瘫,然后是字符a。

再后面是閉包相關(guān)的東西co_freevars菊值,為空的PyTupleObject外驱,類型0x28后面4個(gè)字節(jié)長(zhǎng)度為0.

然后是code block內(nèi)部嵌套函數(shù)引用的局部變量名集合co_cellvars,同樣是空的PyTupleObject對(duì)象腻窒。

接著0x73開始就是co_filename了昵宇,這是PyStringObject對(duì)象,先是對(duì)象標(biāo)示s儿子,然后是長(zhǎng)度30.后面是對(duì)應(yīng)的文件的完整路徑"/Users/ssj/Prog/python/test.py"瓦哎。

接著是co_name,即函數(shù)名或者類名柔逼,這里就是func了蒋譬,首先也是對(duì)象標(biāo)示't'(0x74),后面跟著長(zhǎng)度4卒落,然后是’func‘這四個(gè)字節(jié)羡铲。

然后是co_firstlineno,這里直接寫的整數(shù)3.

然后是字節(jié)碼指令與源文件行號(hào)對(duì)應(yīng)關(guān)系co_lnotab儡毕,以PyStringObject對(duì)象存儲(chǔ)也切。先是標(biāo)示's'(0x73),然后是長(zhǎng)度4個(gè)字節(jié)腰湾,然后是內(nèi)容0x00010601.

好吧雷恃,至此,func這個(gè)code object分析完成费坊。我們回到全局的code object倒槐。
全局code object從co_consts[2]開始,這是None,如前面一樣附井,標(biāo)示為0x4e讨越。接著就是co_names两残,co_varnames等,分析跟前面func的類似把跨,不再贅述人弓。注意的是這里的co_names對(duì)應(yīng)的's'和’func'類型不再是TYPE_INTERNED,而是TYPE_STRINGREF('R'),值是0x52.還有就是co_lnotab是0x06020904着逐。

4.參考資料

  • Python程序的執(zhí)行原理(好文崔赌,精簡(jiǎn)到位,抓住了重點(diǎn))
  • 陳儒《Python源碼剖析》(內(nèi)容很多耸别,有時(shí)間值得慢慢研究的好書)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末健芭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子秀姐,更是在濱河造成了極大的恐慌慈迈,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件囊扳,死亡現(xiàn)場(chǎng)離奇詭異吩翻,居然都是意外死亡兜看,警方通過查閱死者的電腦和手機(jī)锥咸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來细移,“玉大人搏予,你說我怎么就攤上這事』≡” “怎么了雪侥?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)精绎。 經(jīng)常有香客問我速缨,道長(zhǎng),這世上最難降的妖魔是什么代乃? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任旬牲,我火速辦了婚禮,結(jié)果婚禮上搁吓,老公的妹妹穿的比我還像新娘原茅。我一直安慰自己,他們只是感情好堕仔,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布擂橘。 她就那樣靜靜地躺著,像睡著了一般摩骨。 火紅的嫁衣襯著肌膚如雪通贞。 梳的紋絲不亂的頭發(fā)上朗若,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音昌罩,去河邊找鬼捡偏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛峡迷,可吹牛的內(nèi)容都是我干的银伟。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼绘搞,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼彤避!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起夯辖,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤琉预,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蒿褂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體圆米,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年啄栓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了娄帖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡昙楚,死狀恐怖近速,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情堪旧,我是刑警寧澤削葱,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站淳梦,受9級(jí)特大地震影響析砸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜爆袍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一首繁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧螃宙,春花似錦蛮瞄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至堂湖,卻和暖如春闲先,著一層夾襖步出監(jiān)牢的瞬間状土,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工伺糠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蒙谓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓训桶,卻偏偏與公主長(zhǎng)得像累驮,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子舵揭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理谤专,服務(wù)發(fā)現(xiàn),斷路器午绳,智...
    卡卡羅2017閱讀 134,599評(píng)論 18 139
  • 1. 簡(jiǎn)單的例子 先從一個(gè)簡(jiǎn)單的例子說起置侍,包含了兩個(gè)文件 foo.py 和 demo.py 執(zhí)行這個(gè)程序pytho...
    jiangmo閱讀 1,664評(píng)論 0 5
  • Python源碼剖析筆記3-Python執(zhí)行原理初探 之前寫了幾篇源碼剖析筆記,然而慢慢覺得沒有從一個(gè)宏觀的角度理...
    __七把刀__閱讀 73,174評(píng)論 3 30
  • 這一胎是女兒還是兒子拦焚?若是女兒蜡坊,我想我是肯定高興不起來的。因?yàn)槲乙呀?jīng)有一個(gè)女兒了阿赎败。再帶一個(gè)女兒秕衙,我會(huì)覺得好失落!...
    莎莎大小姐閱讀 548評(píng)論 0 50
  • 過去都是假的峡钓,回憶是一條沒有歸途的路妓笙,以往的一切春天都無法復(fù)原,即使最狂熱最堅(jiān)貞的愛情能岩,歸根結(jié)底也不過是一種瞬息即...
    蔡駿閱讀 8,909評(píng)論 76 358