Python pycフォーマット解析
7608 ワード
この文章はpython pycファイルフォーマットを純粋に分析するだけで,主にpycのファイルにおける記憶方式について解析した.pycはpythonバイトコードをファイルに格納する方式であり、仮想マシン実行時環境ではPyCodeObjectオブジェクトに対応する.PyFrameObjectやPyFunctionObjectなどのランタイム構造については,引き続き学習を徹底して分析できるようにしたい.
1.サンプルファイル
ソースファイルpy
得られるpyc後、
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コマンドで内容を見てみましょう.
やはり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の結果を見てみましょう.
次は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源コード剖析》(内容が多く、ゆっくり研究する価値のある良い本がある)
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.参考資料