Python pycフォーマット解析

7608 ワード

この文章はpython pycファイルフォーマットを純粋に分析するだけで,主にpycのファイルにおける記憶方式について解析した.pycはpythonバイトコードをファイルに格納する方式であり、仮想マシン実行時環境ではPyCodeObjectオブジェクトに対応する.PyFrameObjectやPyFunctionObjectなどのランタイム構造については,引き続き学習を徹底して分析できるようにしたい.
1.サンプルファイル
ソースファイルpy
s = "hello"                                                                                                                                                    

def func():
  a = 3 
  print s

func()
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])

得られるpyc後、hexdump -C test.pycを実行すると、次のようなバイナリ文字ストリームが得られる.
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  |[email protected].|
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.....|
00000120  00 00 00 73 04 00 00 00  06 02 09 04              |...s........|
0000012c

2.PyCodeObject構造
PyCodeObject形式は以下の通りです.
この画像はUC技術ブログから転載され、参考資料1を参照してください.もちろん、この画像にはco_などのフィールドが書かれていません.names, co_varnames, co_freevars, co_cellvars,co_filename, co_name, co_firstlineno, co_lnotab.
3.Pyc形式解析
まず4バイトがmagic numberで、03 f 30 d 0 aのうち0 d 0 aがrです.次の4バイトが時間です.ここでd 2 e 73855です.小端モードであることに気づきましたので、実際には0 x 5538 e 7 d 2で、私がコンパイルを始めた時間であることがわかります.そしてPyCodeObjectオブジェクトです.まずはオブジェクトID TYPE_CODE、すなわち文字c、値99、すなわち0 x 63である.そして4バイトがグローバルコードブロックの位置パラメータ個数co_Argument,ここでは0.さらに4バイトはグローバルコードブロックのローカル変数個数co_nlocals、ここでは0.次の4バイトはcode blockに必要なスタック空間co_stacksize、ここで値は1.そして4バイトはco_flags、ここは64です.
次に0 x 73からcode blockのバイトコードシーケンスco_code.PyStringObject形式であることに気づき、PyStringObjectを書き始め、まず1バイトのタイプID TYPE_を書くことに気づくSTRING、すなわちsは、0 x 73に対応する.その後、4バイト識別長は1 a、すなわち26バイトである.0 x 64からco_コードの内容です.disコマンドで内容を見てみましょう.
In [39]: source = open("test.py").read()
In [40]: co = compile(source, 'test.py', 'exec')

In [41]: co.co_consts
Out[41]: ('hello', , 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'  ,    f->f_locas['s'] = 'hello' 

  3           6 LOAD_CONST               1 () ## co.co_consts[1] func        
              9 MAKE_FUNCTION            0 ##         
             12 STORE_NAME               1 (func) #f->f_locals['func']=    

  7          15 LOAD_NAME                1 (func) # f->f_locals['func']       
             18 CALL_FUNCTION            0 #    ,      
             21 POP_TOP                   ##       
             22 LOAD_CONST               2 (None) ##None  
             25 RETURN_VALUE        ##  None

やはり26バイトで、内容はそれぞれこれらの命令に対応しており、そのうち1列目はソースコードの行数であり、2列目はco_codeのオフセットは、3列目がopcodeで、オペランドとオペランドなしの2種類に分けられ、1バイトの整数です.4番目の列はオペランドで、2バイトを占めています.では、これらのコマンドは、コードのコメントを参照して、pycファイルの内容に対応しています.LOAD_CONST命令は0 x 64であり、2バイトのオペランドは0である.次はSTORE_NAME指令0 x 5 a、オペランド0.その他はこのように推定し,frame関連内容は後で解析する.
次は0 x 28からco.co_constsの内容は、PyTupleObjectオブジェクトであり、code blockの定数が保存されていることを知っています.前に見たように、文字列hello、code objectオブジェクトfunc、Noneの3つの要素があることを知っています.ではPyTupleObjectはPyListObjectと類似しており、まず記録タイプ表示TYPE_TUPLE,すなわち'(',すなわち0 x 28です.次の4バイトは長さで、ここでは3は3つの要素があることを示しています.次に要素の内容で、最初は'hello',PyStringObjectオブジェクトなので、TYPE_INTERNET,すなわち't',つまり上の0 x 74をマークして、それからね、4バイトの長さを5バイト書き込むので、これは5、次はhelloの5バイトです.
2つ目はcode objectです.はい、これは前の流れともう一度やり直すことに相当します.0 x 63は前と同じTYPE_CODEの表示「c」、そしてcode objectの各フィールドです.やはり1回来て、それぞれ次のようです
First Header
Second Header
co_argcount
0
co_nlocals
1
co_stacksize
1
co_flags
67
co_code
0 x 73、すなわちTYPE_STRING.長さ0 x 0 f、すなわち15バイト長.そして0 x 64からco_コードの内容.
次にfuncのco_を分析しますコード、まずdisの結果を見てみましょう.

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)  #  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     ##    None

次はfuncのco_constsフィールドは、同じPyTupleObjectオブジェクトで、まずタイプ表示0 x 28、それから4バイトが長さ2である.次に、第1の要素はNone(N)、すなわち0 x 4 e、次いで第2の要素3であり、タイプ表示はTYPE_INT(i)、すなわち0 x 69.後の4バイトは整数3である.
次はco_names、同じくPyTupleObjectオブジェクトで、0 x 28と表示され、4バイトが長さ1、文字sがTYPE_INTERNEDタイプは、「't',すなわち0 x 74」と表示され、文字内容s(0 x 73)となる.
次はco_varnamesは、同じPyTupleObjectで、タイプは0 x 28で、4バイトは長さ1、それから文字aです.
あとは閉鎖関連のものco_freevarsは、空のPyTupleObjectで、タイプ0 x 28の後ろの4バイトの長さは0.
次にcode block内部ネスト関数が参照する局所変数名の集合co_cellvarsは、同じく空のPyTupleObjectオブジェクトです.
続いて0 x 73からco_filenameです.これはPyStringObjectオブジェクトで、まずオブジェクト表示sで、それから長さ30です.次は、対応するファイルのフルパス「/Users/ssj/Prog/python/test.py」です.
続いてco_name、すなわち関数名またはクラス名、ここでfuncであり、まずオブジェクト表示't'(0 x 74)であり、後に長さ4、次に「func」の4バイトが続く.
そしてco_firstline no、ここに直接書いた整数3.
次にバイトコード命令とソースファイル行番号の対応関係co_lnotabは、PyStringObjectオブジェクトで格納されます.まず's'(0 x 73)と表示する、次に長さ4バイト、次にコンテンツ0 x 00010601とする.
では、funcというcode objectの分析を完了します.私たちはグローバルなcode objectに戻ります.グローバルcode objectはco_からconsts[2]から始まり、これはNoneで、前のように0 x 4 eと表示されます.続いてco_names,co_varnamesなどは,解析が前述のfuncと類似しており,これ以上述べない.ここのco_names対応の's'と'func'タイプはTYPEではありませんINTERNEDではなくTYPE_STRINGREF('R')、値は0 x 52である.あとはco_lnotabは0 x 06020904です.
4.参考資料
  • Pythonプログラムの実行原理(好文、簡素化、重点を捉えた)
  • 陳儒《Python源コード剖析》(内容が多く、ゆっくり研究する価値のある良い本がある)