1. 程式人生 > >如何編譯和除錯Python核心原始碼?

如何編譯和除錯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