如何編譯和除錯Python核心原始碼?
目錄
- 寫在前面
- 獲取原始碼
- 原始碼的組織
- windows下編譯CPython
- 除錯CPython
- 小結
- 參考
部落格:blog.shinelee.me | 部落格園 | CSDN
寫在前面
如果對Python原始碼感興趣,那“窺探”其實現的最佳方式就是調教它,不,除錯它。
獲取原始碼
Python的官方預設實現為CPython,即C語言實現(主要指直譯器的實現,其他實現見Other Interpreter Implementations)。CPython的原始碼可以從官網pyhton.org或者github.com/python/cpython獲取,目前最新的穩定版本為3.8.0,於2019.10.14釋出。這裡,從官網 https://www.python.org/downloads/release/python-380/ 下載原始碼壓縮包,如下圖所示,
原始碼的組織
解壓後,目錄結構如下
{ Python-3.8.0 } » tree -d -L 1 . . ├── Doc # rst(reStructuredText)格式官方文件,用其生成https://docs.python.org/ ├── Grammar # Python的EBNF(Extended Backus–Naur form)語法定義檔案 ├── Include # .h 標頭檔案 ├── Lib # .py 純Python實現的標準庫 ├── m4 # ? ├── Mac # Mac-specific code,支援MacOS ├── Misc # Things that do not belong elsewhere. ├── Modules # C實現的標準庫,內含.c .asm .macros .h ├── Objects # 內建資料型別實現 ├── Parser # Python語法分析器原始碼 ├── PC # Windows-specific code,支援Windows ├── PCbuild # Windows生成檔案,for MSVC ├── Programs # main函式檔案,用於生成可執行檔案,如python.exe的入口檔案 ├── Python # CPython直譯器原始碼 └── Tools # 獨立工具程式碼,used to maintain Python
CPython的原始碼組織結構如下,摘抄自CPython Source Code Layout,
原始碼檔案分門別類存放,而且,無論是py實現的標準庫、c實現的標準庫、內建資料型別還是內建函式,在Lib/test/
和Doc/library/
目錄下都有與之對應的test_x.py測試檔案和rst文件檔案(對於內建資料型別和函式,其文件集中儲存在stdtypes.rst和functions.rst)。比如,內建型別int
位於Objects/longobject.c
檔案中。
下面正式開始編譯CPython。
windows下編譯CPython
據Compile and build on Windows,Python3.6及之後的版本可以使用VS2017編譯,安裝VS2017時,記得勾選 Python development 和 Python native development tools,有備無患。
安裝好VS2017後,雙擊PCbuild/pcbuild.sln
,開啟解決方案。因為我們的關注點僅在Python核心和直譯器部分,所以僅編譯python和pythoncore,其他模組暫時忽略,具體地,
- 切換到debug win32
- 右鍵解決方案→屬性→配置屬性
- 僅勾選專案python和pythoncore
- 確定
此時再“生成解決方案”,生成目錄為PCbuild/win32
,內容如下,含直譯器python_d.exe和核心python38_d.dll,
接下來,將專案python設為啟動專案(預設狀態即是啟動專案),點選除錯,執行得到如下控制檯,可以像平時使用python一樣,與之互動。
如果想生成全部模組,需要執行PCbuild\get_externals.bat
下載依賴,再編譯,具體可參見Build CPython on Windows。
除錯CPython
只要程式能執行起來,一切就好辦了。憑藉“宇宙最強IDE”,我們可以任性地設斷點除錯甚至修改程式碼。
F5
重新啟動除錯,彈出控制檯。在上面我們知道int
型別位於Objects/longobject.c
檔案,開啟檔案,簡單瀏覽後在函式PyObject * PyLong_FromLong(long ival)
入口處打個斷點。然後,在彈出的控制檯中輸入a = 1
來建立int
物件,回車,程式停在了斷點處,檢視變數ival
的值為1——恰為我們輸入的數值,這個函式會跟根據輸入的C long int建立一個int
物件,返回物件指標。
再來看看函式呼叫堆疊,如下圖所示,
呼叫順序從下至上,從中可以推斷出,
- 從python_d.exe的入口main執行起來後,進入python38_d.dll
- 從標準輸入stdin中讀取鍵入的字串
- 解析字串,建立了語法樹AST(abstract syntax tree)
- 解析語法樹中的節點,判斷字元為number,將字串轉化為C long int
- 由C long int建立Python的
int
物件
繼續執行,彈出的控制檯中游標前出現<<<
,等待輸入。這時如果我們點選除錯中的停止按鈕(全部中斷),會發現程式停在Parser/myreadline.c
檔案_PyOS_WindowsConsoleReadline
函式中的ReadConsoleW
一行,
if (!ReadConsoleW(hStdIn, &wbuf[total_read], wbuflen - total_read, &n_read, NULL)) {
err = GetLastError();
goto exit;
}
ReadConsoleW
為WINAPI,詳見ReadConsole function,其等待並讀取控制檯的輸入,讀取的字元儲存在wbuf
中。如果有輸入,則進入上面的流程,解析→建立語法樹→……
小結
至此,我們揭開了Python面紗的一角——不過是一個可執行、可除錯的程式而已(微笑)。
參考
- Directory structure
- reStructuredText
- Extended Backus–Naur form
- Exploring CPython’s Internals
- Compile and build on Windows