在 Python 中使用 GDB 來調試 轉載

大約一年前,我接觸了 Java 中的 Btrace 能夠不停機查看線上 JVM 運行情況的特性讓我豔羨不已。 另外還有強悍的 jStack 和 jConsole 來進行運行期偵測,JVM 的工業級強度果然不是蓋的。

當時公司技術方面也遇到了一些瓶頸,一部分原因是 CPython 本身的 IO 模型問題, 另一方面也和早期代碼寫的極不工整脫不了關係。萬般無奈之下,我們用 Jython 推翻重做了主要業務,效果立竿見影,但同時也把真實問題給規避掉了。

在這之後我們在 JVM 的領導下,走上了康莊大道。但我心中始終還有一個梗, 就是想對性能遇到瓶頸的 Python Process 進行線上偵測。 這篇文章就是開始的第一步。

PS:這篇文章理論上是可行的,但是在我機器(Ubuntu 12.04 / 系統自帶 Python) 無法正常執行,會爆出 unable to read python frame information 的問題。解決方法我會在下篇文章中寫出。這裏只是單純翻譯一下原文。

原文可以移步 https://wiki.python.org/moin/DebuggingWithGdb


有一些類型的 bugs 很難使用 Python 直接進行 debug,比如

  • 段錯誤(無法被捕捉的 Python 異常)
  • 卡住的進程(這種情況下面沒法使用 pdb 來進行跟蹤)
  • 控制之外的後臺處理 daemon 進程

這些情況下,你可以使用嘗試使用 gdb。
Fedora:
sudo yum install gdb python-debuginfo
Ubunt:
sudo apt-get install gdb python2.7-dbg

在一些老系統上面,也一樣可以使用 gdb,具體看文章末尾。

使用 GDB

有兩種可行的方法:

  1. 一開始就使用 gdb 來啓動應用
  2. 連接到一個已經運行的 Python 進程

在 gdb 下面啓動 Python 同樣有兩種方式:

交互式:

$ gdb python
...
(gdb) run <programname>.py <arguments>

自動:

$ gdb -ex r --args python <programname>.py <arguments>

這樣的話,它會一直運行直到退出、段錯誤、或者人爲的停止(使用 Ctrl+C)。

如果進程已經開始運行,你可以通過 PID 來接入它:
gdb python <pid of running process>

調試進程

如果你的程序段錯誤了, gdb 會自動暫停程序,這樣你可以切換到 gdb 命令行來檢查狀態。你也可以人爲地使用 Ctrl+C 來暫停程序運行。

查看 EasierPythonDebugging 獲得 gdb 裏面的 Python 命令列表。

查看 C 調用棧

如果你在 debug 段錯誤,你最想做的可能就是查看 C 調用棧。

在 gdb 的命令行裏面,只要運行一下命令:

(gdb) bt
#0  0x0000002a95b3b705 in raise () from /lib/libc.so.6
#1  0x0000002a95b3ce8e in abort () from /lib/libc.so.6
#2  0x00000000004c164f in posix_abort (self=0x0, noargs=0x0)
    at ../Modules/posixmodule.c:7158
#3  0x0000000000489fac in call_function (pp_stack=0x7fbffff110, oparg=0)
    at ../Python/ceval.c:3531
#4  0x0000000000485fc2 in PyEval_EvalFrame (f=0x66ccd8)
    at ../Python/ceval.c:2163
...

運氣好的話,你可以直接看到問題出現在什麼地方。如果它提供的信息不能直接幫你解決問題, 你可以嘗試繼續追蹤調用棧。 調式的結果取決於 debug 信息的有效程度。

查看 Python 調用棧

如果你安裝了 Python 擴展,你可以使用

(gdb) py-bt

可以獲取熟悉的 Python 源代碼。

對掛住的進程開刀

如果一個進程看上去掛住了,他可能在等待什麼東西(比如鎖、IO 等等)。 也有可能在拼命的跑循環。連接上這個進程,然後檢查調用棧也許可以幫上忙。

如果進程在瘋狂循環,你可以先讓它運行一會,使用 cont 命令, 然後使用 Ctrl+C 來暫停,並且打印出調用棧。

如果一些線程卡住了,下面的命令可能會幫上忙:

(gdb) info threads
  Id   Target Id         Frame
  37   Thread 0xa29feb40 (LWP 17914) "NotificationThr" 0xb7fdd424 in __kernel_vsyscall ()
  36   Thread 0xa03fcb40 (LWP 17913) "python2.7" 0xb7fdd424 in __kernel_vsyscall ()
  35   Thread 0xa0bfdb40 (LWP 17911) "QProcessManager" 0xb7fdd424 in __kernel_vsyscall ()
  34   Thread 0xa13feb40 (LWP 17910) "python2.7" 0xb7fdd424 in __kernel_vsyscall ()
  33   Thread 0xa1bffb40 (LWP 17909) "python2.7" 0xb7fdd424 in __kernel_vsyscall ()
  31   Thread 0xa31ffb40 (LWP 17907) "QFileInfoGather" 0xb7fdd424 in __kernel_vsyscall ()
  30   Thread 0xa3fdfb40 (LWP 17906) "QInotifyFileSys" 0xb7fdd424 in __kernel_vsyscall ()
  29   Thread 0xa481cb40 (LWP 17905) "QFileInfoGather" 0xb7fdd424 in __kernel_vsyscall ()
  7    Thread 0xa508db40 (LWP 17883) "QThread" 0xb7fdd424 in __kernel_vsyscall ()
  6    Thread 0xa5cebb40 (LWP 17882) "python2.7" 0xb7fdd424 in __kernel_vsyscall ()
  5    Thread 0xa660cb40 (LWP 17881) "python2.7" 0xb7fdd424 in __kernel_vsyscall ()
  3    Thread 0xabdffb40 (LWP 17876) "gdbus" 0xb7fdd424 in __kernel_vsyscall ()
  2    Thread 0xac7b7b40 (LWP 17875) "dconf worker" 0xb7fdd424 in __kernel_vsyscall ()
* 1    Thread 0xb7d876c0 (LWP 17863) "python2.7" 0xb7fdd424 in __kernel_vsyscall ()

當前運行的線程被標記爲 *,要查看 Python 代碼運行到哪裏,使用 py-list 查看:

(gdb) py-list
2025        # Open external files with our Mac app
2026        if sys.platform == "darwin" and 'Spyder.app' in __file__:
2027            main.connect(app, SIGNAL('open_external_file(QString)'),
2028                         lambda fname: main.open_external_file(fname))
2029
>2030        app.exec_()
2031        return main
2032
2033
2034    def __remove_temp_session():
2035        if osp.isfile(TEMP_SESSION_PATH):

查看所有進程的 Python 代碼位置,可以使用:

“` (gdb) thread apply all py-list … 200 201 def accept(self):

202 sock, addr = self.sock.accept() 203 return socketobject(sock=sock), addr 204 accept.doc = realsocket.accept.doc 205 206 def dup(self): 207 “”“dup() -> socket object

Thread 35 (Thread 0xa0bfdb40 (LWP 17911)): Unable to locate python frame

Thread 34 (Thread 0xa13feb40 (LWP 17910)): 197 for method in delegate_methods: 198 setattr(self, method, dummy) 199 close.doc =realsocket.close.doc 200 201 def accept(self):

202 sock, addr = self.sock.accept() 203 return socketobject(_sock=sock), addr …

## 引用 ##
* [http://fedoraproject.org/wiki/Features/EasierPythonDebugging](http://fedoraproject.org/wiki/Features/EasierPythonDebugging)
* [https://code.google.com/p/spyderlib/wiki/HowToDebugDeadlock](https://code.google.com/p/spyderlib/wiki/HowToDebugDeadlock)
## 老系統上的 GDB ##
有時候你需要在老系統上面安裝 `gdb`,這時候你可能需要下列信息:
### GDB Macros ###
一些隨着 Python 發佈的 GDB 腳本可以用來調試 Python 進程。
你可以把 Python 源碼裏面的 `Misc/gdbinit`  拷貝到 `~/.gdbinit`,
或者從 [Subversion](http://svn.python.org/view/python/branches/release27-maint/Misc/gdbinit?view=log)
來拷貝他們。請注意你的 Python,確保使用正確的代碼版本,否則有些功能可能無法工作。
請注意有些新的 GDB 命令只有在 debug 需要的庫存在才能正常工作。
這個腳本在 Ubuntu 上面的 gcc 4.5.2 工作時,會爆出錯誤
`No symbol "co" in current context.`,是因爲 `call_function` 在
[PyEval_EvalFrameEx](https://wiki.python.org/moin/EvalFrameEx) 和
[PyEval_EvalCodeEx](https://wiki.python.org/moin/EvalCodeEx) 之間。
重新使用 `make "CFLAGS=-g -fno-inline -fno-strict-aliasing"`
編譯 Python 可以解決這個問題。
### 使用 Python Stack Traces GDB 腳本 ##
在 gdb 命令行裏,可以這樣查看 Python stack trace:

(gdb) pystack
同樣的,可以獲取一列 stack frame 的 Python 變量:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章