如何編譯和調試Python內核源碼?

寫在前面

如果對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內核源碼?

源代碼的組織

解壓後,目錄結構如下

{ 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,

如何編譯和調試Python內核源碼?

源碼文件分門別類存放,而且,無論是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 developmentPython native development tools,有備無患。

安裝好VS2017後,雙擊PCbuild/pcbuild.sln,打開解決方案。因為我們的關注點僅在Python內核和解釋器部分,所以僅編譯python和pythoncore,其他模塊暫時忽略,具體地,

  • 切換到debug win32
  • 右鍵解決方案→屬性→配置屬性
  • 僅勾選項目python和pythoncore
  • 確定
如何編譯和調試Python內核源碼?

此時再“生成解決方案”,生成目錄為PCbuild/win32,內容如下,含解釋器python_d.exe和內核python38_d.dll,

如何編譯和調試Python內核源碼?

接下來,將項目python設為啟動項目(默認狀態即是啟動項目),點擊調試,運行得到如下控制檯,可以像平時使用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內核源碼?

再來看看函數調用堆棧,如下圖所示,

如何編譯和調試Python內核源碼?

調用順序從下至上,從中可以推斷出,

  • 從python_d.exe的入口main運行起來後,進入python38_d.dll
  • 從標準輸入stdin中讀取鍵入的字符串
  • 解析字符串,建立了語法樹AST
    (abstract syntax tree)
  • 解析語法樹中的節點,判斷字符為number,將字符串轉化為C long int
  • 由C long int創建Python的int對象

繼續運行,彈出的控制檯中光標前出現<<

if (!ReadConsoleW(hStdIn, &wbuf[total_read], wbuflen - total_read, &n_read, NULL)) {
err = GetLastError();
goto exit;
}

ReadConsoleW為WINAPI,詳見ReadConsole function,其等待並讀取控制檯的輸入,讀取的字符保存在wbuf中。如果有輸入,則進入上面的流程,解析→建立語法樹→……

小結

至此,我們揭開了Python面紗的一角——不過是一個可運行、可調試的程序而已(微笑)。


分享到:


相關文章: