我最近在參與Python字節碼相關的工作,想與大家分享一些這方面的經驗。更準確的說,我正在參與2.6到2.7版本的CPython解釋器字節碼的工作。
Python是一門動態語言,在命令行工具下運行時,本質上執行了下面的步驟:
當第一次執行到一段代碼時,這段代碼會被編譯(如,作為一個模塊加載,或者直接執行)。根據操作系統的不同,這一步生成后綴名是pyc或者pyo的二進制文件。 解釋器讀取二進制文件,并依次執行指令(opcodes)。Python解釋器是基于棧的。要理解數據流向,我們需要知道每條指令的棧效應(如,操作碼和參數)。
探索Python二進制文件
得到一個二進制文件字節碼的最簡單方式,是對CodeType結構進行解碼:
import marshalfd = open('path/to/my.pyc', 'rb')magic = fd.read(4) # 魔術數,與python版本相關date = fd.read(4) # 編譯日期code_object = marshal.load(fd)fd.close()code_object包含了一個CodeType對象,它代表被加載文件的整個模塊。為了查看這個模塊的類定義、方法等的所有嵌套編碼對象(編碼對象,原文為code object),我們需要遞歸地檢查CodeType的常量池。就像下面的代碼:
import types def inspect_code_object(co_obj, indent=''):print indent, "%s(lineno:%d)" % (co_obj.co_name, co_obj.co_firstlineno)for c in co_obj.co_consts:if isinstance(c, types.CodeType):inspect_code_object(c, indent + ' ') inspect_code_object(code_object) # 從第一個對象開始
這個案例中,我們打印出一顆編碼對象樹,每個編碼對象是其父對象的子節點。對下面的代碼:
class A:def __init__(self):passdef __repr__(self):return 'A()'a = A()print a
我們得到的樹形結果是:
<module>(lineno:2) A(lineno:2) __init__(lineno:3) __repr__(lineno:5)
為了測試,我們可以通過compile指令,編譯一個包含Python源碼的字符串,從而能夠得到一個編碼對象:
co_obj = compile(python_source_code, '<string>', 'exec')
要獲取更多關于編碼對象的信息,我們可以查閱Python文檔的co_* fields 部分。
初見字節碼
一旦我們得到了編碼對象,我們就可以開始對它進行拆解了(在co_code字段)。從字節碼中解析出它的含義:
? 解釋操作碼的含義 ? 提取任意參數dis模塊的disassemble函數展示了是如何做到的。對我們前面例子,它輸出的結果是:
2 0 LOAD_CONST 0 ('A') 3 LOAD_CONST 3 (()) 6 LOAD_CONST 1 (<code object A at 0x42424242, file "<string>", line 2>) 9 MAKE_FUNCTION 0 12 CALL_FUNCTION 0 15 BUILD_CLASS 16 STORE_NAME 0 (A) 8 19 LOAD_NAME 0 (A) 22 CALL_FUNCTION 0 25 STORE_NAME 1 (a) 9 28 LOAD_NAME 1 (a) 31 PRINT_ITEM 32 PRINT_NEWLINE 33 LOAD_CONST 2 (None) 36 RETURN_VALUE
新聞熱點
疑難解答