好吧!“人生苦短,請用Python”,作為python愛好者以及安全從業者,而且最近也碰到了一些這方面的問題,懂點python字節碼還是很有必要的。
Python是一門解釋性語言,它的具體工作流程如下:
1:編譯,形成.pyc或.pyo后綴的語言
2:放入解釋器,解釋器執行字節流(opecode)
和java字節碼一樣,他們都是基於棧進行解釋的。首先,先來看對pyc文件進行一個直觀的理解:
一:直面pyc文件
pyc文件的生成一般用於加快Python的解釋速度,運行時,如果pyc的編譯時間晚於py的修改時間,會直接運行pyc文件。所以一般需要以module形式加載時,才會直接生成,生成pyc文件腳本如下:
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__': t = raw_input() generate_pyc(t)
通過load_module形式進行加載,來間接獲取pyc文件。
二:分析pyc文件
pyc文件的格式分為兩種
3.4以前前四個字節為magic,這個和python版本相關,然后四個字節為編譯時間,后面為code_type(PycodeObject)。而在3.4及以后則在編譯時間之后添加一個filesize。下面是CPython中對code_type數據結構的描述
#define OFF(x) offsetof(PyCodeObject, x)
static PyMemberDef code_memberlist[] = { {"co_argcount", T_INT, OFF(co_argcount), READONLY}, {"co_nlocals", T_INT, OFF(co_nlocals), READONLY}, {"co_stacksize",T_INT, OFF(co_stacksize), READONLY}, {"co_flags", T_INT, OFF(co_flags), READONLY}, {"co_code", T_OBJECT, OFF(co_code), READONLY}, {"co_consts", T_OBJECT, OFF(co_consts), READONLY}, {"co_names", T_OBJECT, OFF(co_names), READONLY}, {"co_varnames", T_OBJECT, OFF(co_varnames), READONLY}, {"co_freevars", T_OBJECT, OFF(co_freevars), READONLY}, {"co_cellvars", T_OBJECT, OFF(co_cellvars), READONLY}, {"co_filename", T_OBJECT, OFF(co_filename), READONLY}, {"co_name", T_OBJECT, OFF(co_name), READONLY}, {"co_firstlineno", T_INT, OFF(co_firstlineno), READONLY}, {"co_lnotab", T_OBJECT, OFF(co_lnotab), READONLY}, {NULL} /* Sentinel */ };
下面是一個基於python2.7的pyc文件例子的部分截圖
解析pyc文件可以得到,如下:
magic 03f30d0a moddate aa813e59 (Mon Jun 12 19:57:30 2017) code argcount 0 nlocals 0 stacksize 1 flags 0040 code 640000474864010053 consts 'hello world!' None names () varnames () freevars () cellvars () filename 'C:\\Users\\Administrator\\Desktop\\test3.py' name '<module>' firstlineno 1 lnotab
本文着重講解的是co_code,也就是opcode。
三:解讀opcode
opcode代碼在理解上還是很簡單的,想要得到某個函數或者module的話可以直接使用dis.dis()或者dis.disassemble()函數,這里先使用dis.dis()函數,直接觀察函數的opcode。下面是一個簡單的python腳本:
import dis def test1(): a = "hello" b = " " c = "world" d = a +b+c print d print dis.dis(test1)
可以得到test1函數的opcode代碼
3 0 LOAD_CONST 1 ('hello') //'hello'壓棧 3 STORE_FAST 0 (a) //'hell0'出棧,同時local['a'] = 'hello' 4 6 LOAD_CONST 2 (' ') 9 STORE_FAST 1 (b) 5 12 LOAD_CONST 3 ('world') 15 STORE_FAST 2 (c) 6 18 LOAD_FAST 0 (a) //將local['a']壓棧 21 LOAD_FAST 1 (b) //將local['b']壓棧 24 BINARY_ADD //棧中a,b相加,結果壓棧 25 LOAD_FAST 2 (c) 28 BINARY_ADD 29 STORE_FAST 3 (d) 7 32 LOAD_FAST 3 (d) 35 PRINT_ITEM 36 PRINT_NEWLINE 37 LOAD_CONST 0 (None) 40 RETURN_VALUE None
源碼和opcode對照並結合注釋,理解起來還是很方便的。第一列是源代碼的行數,第二列是字節相對於第一個字節的偏移,第三個則是命令,第四個是命令參數。opcode的格式如下:
想要徹底理解上面的代碼,必須先理解python基於棧的運行機制,python的運行是單純模擬cpu運行的機制,看一下它的堆結構
typedef struct _frame { PyObject_VAR_HEAD struct _frame *f_back; /* 調用者的幀 */ PyCodeObject *f_code; /* 幀對應的字節碼對象 */ PyObject *f_builtins; /* 內置名字空間 */ PyObject *f_globals; /* 全局名字空間 */ PyObject *f_locals; /* 本地名字空間 */ PyObject **f_valuestack; /* 運行時棧底 */ PyObject **f_stacktop; /* 運行時棧頂 */ …….
可以利用sys.getFrame來得到運行時的堆棧狀態
{'a': 'hello', 'c': 'world', 'frame': <frame object at 0x0000000002DEA3A8>, 'b': ' ', 'd': 'hello '} {'test1': <function test1 at 0x00000000032B9908>, '__builtins__': <module '__builtin__' (built-in)>, '__file__': 'C:\\Users\\Administrator\\Desktop\\test3.py', '__package__': None, 'sys': <module 'sys' (built-in)>, '__name__': '__main__', '__doc__': None, 'dis': <module 'dis' from 'C:\Python27\lib\dis.pyc'>} {'test1': <function test1 at 0x00000000032B9908>, '__builtins__': <module '__builtin__' (built-in)>, '__file__': 'C:\\Users\\Administrator\\Desktop\\test3.py', '__package__': None, 'sys':
四:常見語句以及對應opcode
1.判斷語句
def test2(t):if t > 3: print "OK!"
對應
6 0 LOAD_FAST 0 (t) 3 LOAD_CONST 1 (3) 6 COMPARE_OP 4 (>) 9 POP_JUMP_IF_FALSE 20 7 12 LOAD_CONST 2 ('OK!') 15 PRINT_ITEM 16 PRINT_NEWLINE 17 JUMP_FORWARD 0 (to 20) >> 20 LOAD_CONST 0 (None) 23 RETURN_VALUE None
2.循環語句
def test3(): i = 0 while t < 10: t += i i+=1
對應
5 0 LOAD_CONST 1 (0) 3 STORE_FAST 0 (i) 6 6 SETUP_LOOP 36 (to 45) >> 9 LOAD_FAST 1 (t) 12 LOAD_CONST 2 (10) 15 COMPARE_OP 0 (<) 18 POP_JUMP_IF_FALSE 44 7 21 LOAD_FAST 1 (t) 24 LOAD_FAST 0 (i) 27 INPLACE_ADD 28 STORE_FAST 1 (t) 8 31 LOAD_FAST 0 (i) 34 LOAD_CONST 3 (1) 37 INPLACE_ADD 38 STORE_FAST 0 (i) 41 JUMP_ABSOLUTE 9 >> 44 POP_BLOCK >> 45 LOAD_CONST 0 (None) 48 RETURN_VALUE
3.調用操作
def test4(a,b): print a+b def test3(): test4(3,4)
對應
7 0 LOAD_GLOBAL 0 (test4) 3 LOAD_CONST 1 (3) 6 LOAD_CONST 2 (4) 9 CALL_FUNCTION 2 12 POP_TOP 13 LOAD_CONST 0 (None) 16 RETURN_VALUE