不知各位有木有遇到這樣的情況,生產環境下的Python進程突然卡死了,所有其他線程都無法調度,如果我們kill掉重啟,通常會喪失掉當前報錯的上下文信息,失去這些信息,對後續報錯定位不太友好,能不能在不關停Python程序的情況下,看看程序發生了什麼問題?
首先,你可以嘗試一下,此前我提過的py-spy(漫畫:如何分析運行中的 Python 程序?),py-spy可以打印出簡單的調用信息,但很多時候不夠用,這裡我們通過gdb來調試Python程序,同時打印c棧和py棧的信息,調試起來,一目了然。
gdb主要用於調試c/c++程序的,因為Python是使用c寫的(cpython,當然還有其他語言實現的),對gdb而言,將python當成普通的c程序調試則可。
當我們使用gdb調試python進程時,我們會獲得解釋器級別的調試信息和內存狀態,而不是應用程序級別的(我們使用pdb便是應用程序基本的),這樣我們可以看到Python完整的執行流程,包括解釋器上函數與變量的信息。
我的服務器是Ubuntu 20.04,所以本文的操作都在Ubuntu 20.04上進行。
首先,需要安裝一下gdb。
sudo apt-get install gdb
光安裝gdb,在調試python時,雖然可以用,但不太友好,為了更好的浏覽調試信息,我們為cpython安裝gdb debugging Symbols。
gdb中的debugging Symbols的主要作用是將程序編譯後的二進制指令映射到源代碼相應的變量、函數和行中,這樣在調試時可以很好的浏覽調試信息。
在Ubuntu中,安裝python-dbg則可。
sudo apt-get install python-dbg
python-dbg提供了gdb Debugging Symbols和一些調試python的命令,如py-bt、py-list,本文後續會使用。
python-dbg之所以可以發揮作用,是因為它將python gdb相關的內容自動復制到gdb auto-load目錄下了,這樣gdb在啟動時,會自動加載這些內容。
你可以在進入gdb交互命令後,通過info auto-load來查看相關信息。
安裝好gdb調試環境後,准備一段代碼,簡單使用一下gdb。
我在當前用戶目錄下,創建了play_gdb目錄並在其中創建了虛擬環境,然後在目錄中創建了play_gdb.py,裡面就是一段簡單的計算斐波那契數列的代碼。
在日常開發中,我們經常使用python虛擬環境,所以這裡想測試一下,如果我們使用python虛擬環境運行程序時,gdb是否可以正常調試。
在使用python venv前,先通過系統的python來試試,不進入虛擬環境,直接運行。
從上圖可知,我們使用系統python運行程序,開啟新窗口,通過gdb調試一下,如下圖:
上圖命令為:
ps -x | grep python
sudo gdb -p 1199469
先找到pid,然後再讓gdb直接附加到運行中的python進程上。
通過bt命令,查一下當前程序的調用棧。
從上圖可以看出,有很多python解釋器級別的打印,我們看到程序目前在python的timemodule.c的pysleep方法中,最終調用了linux系統的select.c(即通過I/O復用相關的邏輯來實現python進程中主線程的sleep)。
gdb的bt命令可以將c調用棧完整打印出來,如果我們只想看python調用棧,可以使用py-bt(你需要安裝python-dbg才能用),此外,如果想查看當前程序的py代碼,可以使用py-list(等價pdb的ll命令),如下圖:
從上圖看,py-bt和py-list都沒有正常運行,這是因為我們使用gdb時,沒有在當前項目的根目錄,通過q命令退出一下,然後進入項目根目錄,再用gdb開啟調試。
簡單列一下gdb調試時的常用命令:
bt # 當前C調用棧
py-bt # 當前Py調用棧
py-list # 當前py代碼位置
py-up # 上一幀(py級別的幀)
py-down # 下一幀(py級別的幀)
info thread # 線程信息
thread <id> # 切換到某個線程
thread apply all py-list # 查看所有線程的py代碼位置
ctrl-c # 中斷
更多用法可以看pthon官方關於gdb的文檔:https://devguide.python.org/gdb/
如果利用python虛擬環境中的python解釋器來執行py程序,gdb也可以正常調試,沒有什麼使用上的差異。
通過gdb-dashboard(https://github.com/cyrus-and/gdb-dashboard)可以美化gdb調試信息,從而增加效率。
從gdb7開始,gdb便支持使用Python代碼來擴展gdb,gdb-dashboard的原理正是如此。
在當前使用gdb的用戶目錄下,下載gdb-dashboard提供的.gdbinit文件,你可以直接從github中拉取,然後復制到相應的用戶目錄下。
然後你再安裝一下pygments,用於啟用語法高亮顯示的效果(選擇性安裝):
pip install pygments
因為我是通過root用戶來使用gdb的,所以需要將.gdbinit放置在root用戶目錄下。
然後正常使用gdb,會獲得下圖效果。
上圖中,可以很直觀地看到調用棧(Stack)、變量(Variables)、寄存器(Registers)等信息,都是c層面的。
在gdb中,通過help dashboard可以查看dashboard更多的用法。
掌握gdb調試,可以更好地理解python源碼,對於一些比較難搞的線上情況,也更加游刃有余,學起來呗。
使用gdb調試CPython進程 (https://github.com/ictar/python-doc/blob/master/Others/%E4%BD%BF%E7%94%A8gdb%E8%B0%83%E8%AF%95CPython%E8%BF%9B%E7%A8%8B.md)
gdb調試cpython(https://meteorix.github.io/2019/02/13/gdbpython/)