使用gdb調試Python進程 Posted in python On 2011-10-12 10:08:50 , tagged with debug, gdb, python.
有時我們會想調試一個正在啟動並執行Python進程,或者一個Python進程的coredump。例如現在遇到一個mod_wsgi的進程僵死了,不接受請求,想看看究竟是運行到哪行Python代碼呢。這時就需要祭出gdb這個神器了。 準備
1. 確認你的gdb版本是>=7,gdb從版本7開始支援對Python的debug。 (ref)
2.確認gdb串連的Python是所要debug的Python,否則請重新編譯gdb。
方法: ?
1 2 3 4 5 6 7 |
$ gdb (gdb) python > import sys >print sys.version >end 2.4.3 ( #1, Sep 21 2011, 19:55:41) [GCC 4.1.2 20080704 (Red Hat 4.1.2-51)] |
在一些追求穩定的發行版(例如CentOS),Python的版本會較低,這時都會自己編譯一個Python使用。而從源裡安裝的gdb會串連源裡Python的版本。例如在CentOS 5.4,源裡的Python是2.4.3,從源安裝的gdb也會串連到Python 2.4.3。
編譯時間注意,要把自己編譯的Python路徑加到PATH環境變數裡,這樣gdb configure的時候才會找到新版Python並串連。
3.下載libpython.py 如何Debug
假設要debug的進程號為1000 ?
使用此命令即可使gdb附加到進程。 載入libpython指令碼 如果你的gdb是redhat或fedora等廠商修改過的,會有--python選項,使用此選項即可指定gdb啟動時載入的Python擴充指令碼(此指令碼是擴充gdb的,不是我們需要debug的指令碼)。 ?
1 |
$ gdb --python /path/to/libpython .py -p 1000 |
如果安裝的是GNU的gdb,就需要開啟gdb後手動載入libpython.py指令碼 ?
1 2 3 4 5 6 |
(gdb) python > import sys >sys.path.insert(0, '/path/to/libpython.py' ) > import libpython >end (gdb) |
這時就可以使用py-bt命令列印當前線程的Python traceback了
libpython還提供很多命令,例如py-print列印變數,py-locals列印所有本地變數等等,詳細可開啟libpython.py查看。 一點經驗 在gdb可以使用 generate-core-file命令產生一個coredump檔案。之後可以用gdb –core來開啟coredump檔案進行debug。避免一直attach住進程,可以快速重啟恢複服務 gdb-heap是gdb的一個擴充。可以列印Python的記憶體使用量情況
參考資料 DebuggingWithGdb EasierPythonDebugging Debugging with gdb (gdb documentation)
使用gdb調試python指令碼
調試python指令碼一般可通過記錄log和使用python內建的pdb模組完成, 但凡事總有例外,在以下三種情況時上述方法就無能為力了。
1 段錯誤
2 運行中的daemon程式
3 core dump
這個時候就需祭出gdb進行調試。python2.6的源碼中提供了部分預定義函數以便大家使用gdb調試,我們只需將檔案Python-2.6/Misc/gdbinit所包括的內容加入到使用者目錄下的.gdbinit檔案中即可,這樣每次啟動gdb時會自動完成這些宏的定義。但可惜的是Python2.6.2 gdbini對於pylocals的定義居然有錯誤, 看來是沒有隨著代碼的更新而同步更新。我們只需將 while $_i < f->f_nlocals修改為 while $_i < f->f_code->co_nlocals即可。文章後面所附的幾個宏建議也加入的.gdbinit檔案中,更多的宏可參考
http://web.archive.org/web/20070915134837/
http://www.mashebali.com/?Python_GDB_macros:The_Macros。
我們首先需要構造一個會造成段錯誤的python指令碼。老實說讓python發生段錯誤並不容易,但通過其外部調用庫就很簡單了。我們將該檔案命名為gdb_test.py
import sys, os, libxml2
def segv_test():
s = "<html><body><div><a><a></a></a><a></a></div></body></html>"
options = libxml2.HTML_PARSE_RECOVER + \
libxml2.HTML_PARSE_NOERROR + \
libxml2.HTML_PARSE_NOWARNING
doc = libxml2.htmlReadDoc(s, None, 'utf-8', options).doc
ctxt = doc.xpathNewContext()
nodes = ctxt.xpathEval('//body/node()')
nodes.reverse()
for note in nodes:
nexts = note.xpathEval('node()')
note.unlinkNode()
note.freeNode() //freeNode會將該節點及其子節點釋放掉
nexts[0].unlinkNode()
nexts[0].freeNode() //資源已經釋放,再次釋放會造成段錯誤
def main():
segv_test()
if __name__ == "__main__":
main()
使用gdb運行該指令碼,我們會得到段錯誤資訊。
gdb python
r gdb_test.py
*** glibc detected *** double free or corruption (fasttop): 0x08104570 ***
Program received signal SIGABRT, Aborted.
[Switching to Thread -1208260928 (LWP 26159)]
0x00b987a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
鍵入bt得到如下堆棧資訊:
(gdb) bt
#0 0x00b987a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
#1 0x00c00825 in raise () from /lib/tls/libc.so.6
#2 0x00c02289 in abort () from /lib/tls/libc.so.6
#3 0x00c34cda in __libc_message () from /lib/tls/libc.so.6
#4 0x00c3b56f in _int_free () from /lib/tls/libc.so.6
#5 0x00c3b94a in free () from /lib/tls/libc.so.6
#6 0x009812c6 in xmlFreeNode () from /opt/sohumc/lib/libxml2.so.2
#7 0x0029d7f3 in libxml_xmlFreeNode () from /opt/sohumc/lib/python2.6/site-packages/libxml2mod.so
#8 0x00780bae in PyCFunction_Call (func=0x8104570, arg=0xd05820, kw=0x6) at Objects/methodobject.c:116
#9 0x007d8c79 in call_function (pp_stack=0xbff8c48c, oparg=0) at Python/ceval.c:3679
#10 0x007d6d2b in PyEval_EvalFrameEx (f=0x8124ef4, throwflag=0) at Python/ceval.c:2370
#11 0x007d8e36 in fast_function (func=0x6, pp_stack=0xbff8c5dc, n=1, na=1, nk=0) at Python/ceval.c:3765
#12 0x007d89cd in call_function (pp_stack=0xbff8c5dc, oparg=0) at Python/ceval.c:3700
#13 0x007d6d2b in PyEval_EvalFrameEx (f=0x81242fc, throwflag=0) at Python/ceval.c:2370
#14 0x007d8e36 in fast_function (func=0x6, pp_stack=0xbff8c72c, n=0, na=0, nk=0) at Python/ceval.c:3765
#15 0x007d89cd in call_function (pp_stack=0xbff8c72c, oparg=0) at Python/ceval.c:3700
#16 0x007d6d2b in PyEval_EvalFrameEx (f=0x810a7c4, throwflag=0) at Python/ceval.c:2370
#17 0x007d8e36 in fast_function (func=0x6, pp_stack=0xbff8c87c, n=0, na=0, nk=0) at Python/ceval.c:3765
#18 0x007d89cd in call_function (pp_stack=0xbff8c87c, oparg=0) at Python/ceval.c:3700
#19 0x007d6d2b in PyEval_EvalFrameEx (f=0x8091d0c, throwflag=0) at Python/ceval.c:2370
#20 0x007d76f9 in PyEval_EvalCodeEx (co=0xb7fa3728, globals=0x6, locals=0xb7f9902c, args=0x0, argcount=0, kws=0x0, kwcount=0, defs=0x0, defcount=0,
closure=0x0) at Python/ceval.c:2942
#21 0x007d47cb in PyEval_EvalCode (co=0xb7fa3728, globals=0xb7f9902c, locals=0xb7f9902c) at Python/ceval.c:515
#22 0x007fbbce in run_mod (mod=0x80ea780, filename=0xbffc6be6 "gdb_test.py", globals=0xb7f9902c, locals=0xb7f9902c, flags=0xbff8ca8c, arena=0x807ef28)
at Python/pythonrun.c:1330
#23 0x007fbb58 in PyRun_FileExFlags (fp=0x8091d00, filename=0xbffc6be6 "gdb_test.py", start=257, globals=0xb7f9902c, locals=0xb7f9902c, closeit=1,
flags=0xbff8ca8c) at Python/pythonrun.c:1316
#24 0x007fb22d in PyRun_SimpleFileExFlags (fp=0x8091d00, filename=0xbffc6be6 "gdb_test.py", closeit=1, flags=0xbff8ca8c) at Python/pythonrun.c:926
#25 0x007facc9 in PyRun_AnyFileExFlags (fp=0x8091d00, filename=0xbffc6be6 "gdb_test.py", closeit=1, flags=0xbff8ca8c) at Python/pythonrun.c:731
#26 0x00808fea in Py_Main (argc=1, argv=0xbff8cbb4) at Modules/main.c:597
#27 0x080486ae in main (argc=2, argv=0xbff8cbb4) at Modules/python.c:23
pystack和pystackv兩個宏可用來查看python內部的棧情況;可以看到程式時執行到freeNode函數時結束, 該函數位於libxml2.py的3141行。
(gdb) pystack
/opt/lib/python2.6/site-packages/libxml2.py (3141): freeNode
gdb_test.py (17): segv_test
gdb_test.py (21): main
gdb_test.py (24): <module>
通過堆棧我們可以看到指令碼內部各函數的調用關係, 那麼我們如何查看函數內變數情況呢。 正如大家所, python內部堆棧和函數的調用由PyEval_EvalFrameEx完成的, 一次PyEval_EvalFrameEx意味著一次函數調用,象上面的第19,13,10行分別對應於main, segv_test, freeNode函數, 將gdb定位到對應行後,使用pylocals宏即可查看該函數內部變數的詳細情況。
(gdb) up 13
#13 0x007d6d2b in PyEval_EvalFrameEx (f=0x81242fc, throwflag=0) at Python/ceval.c:2370
2370 in Python/ceval.c
(gdb) pylocals
s:
object : '<html><body><div><a><a></a></a><a></a></div></body></html>'
type : str
refcount: 3
address : 0xb7f64440
options:
object : 97
type : int
refcount: 7
address : 0x8082c20
doc:
object : <xmlDoc (None) object at 0xb7cc04ec>
type : instance
refcount: 1
address : 0xb7cc04ec
ctxt:
object : <libxml2.xpathContext instance at 0xb7f70ccc>
type : instance
refcount: 1
address : 0xb7f70ccc
nodes:
object : [<xmlNode ((儓X? object at 0xb7cc0cac>]
type : list
refcount: 2
address : 0xb7f70a8c
note:
object : <xmlNode ((?圶? object at 0xb7cc0cac>
type : instance
refcount: 2
address : 0xb7cc0cac
nexts:
object : [<xmlNode (hhX? object at 0xb7cc750c>, <xmlNode (HXX? object at 0xb7cc76cc>, <xmlNode (@XX? object at 0xb7c9348c>]
type : list
refcount: 1
address : 0xb7f4ce4c
指令碼調試時斷點的設定是個很麻煩的東西,我所能想到的有兩種方法:1 根據函數的python源碼進行斷點設定;2 採用sleep函數和ctrl+c來中斷程式的運行。無論怎麼樣使用逐條執行進行調試都是很痛苦的事情,因為這個時候python解譯器本身要做很多工作。
由於本身對於python源碼不是很熟悉,因此對如何使用gdb對python指令碼調試上也只是很粗略的理解, 這裡權當拋磚引玉, 歡迎達人們給出分享的經驗。
額外的宏:
define pbt
set $i = 0
set $j = 0
while $i < 1000
select $i
if $eip >= &PyEval_EvalFrameEx
if $eip < &PyEval_EvalCodeEx
echo c frame #
p $i
echo py frame #
p $j
set $j = $j+1
x/s ((PyStringObject*)f->f_code->co_filename)->ob_sval
x/s ((PyStringObject*)f->f_code->co_name)->ob_sval
echo line #
p f->f_lineno
end
end
set $i = $i+1
end
end
document pbt
show python backtrace
end
define pyattrlist
set $dict = *(PyDictObject**)(((int)$arg0)+$arg0.ob_type.tp_dictoffset)
set $i = 0
set $j = 0
while $i < $dict.ma_mask
if 0 != $dict.ma_table[$i].me_value
echo \nattr#:
p $j
x/s ((PyStringObject*)$dict.ma_table[$i].me_key).ob_sval
pyobjinfo $dict.ma_table[$i].me_value
set $j = $j+1
end
set $i = $i+1
end
end
document pyattrlist
show pythonic object attributes list
end
define pyattr
set $dict = *(PyDictObject**)(((int)$arg0)+$arg0.ob_type.tp_dictoffset)
set $i = 0
set $j = 0
while $i < $dict.ma_mask
if 0 != $dict.ma_table[$i].me_value
if $j == $arg1
set $attr = $dict.ma_table[$i].me_value
echo $attr:
p $attr
end
set $j = $j+1
end
set $i = $i+1
end
end
document pyattr
get pythonic object attribute
usage: pyattr <pyobj> <attr#>
atr# correlates to those shown in pyattrlist
end
用pdb調試模組調試
首先你選擇啟動並執行 py
python -m pdb myscript.py
(Pdb) 會自動停在第一行,等待調試,這時你可以看看 協助
(Pdb) h
說明下這幾個關鍵 命令
>斷點設定
(Pdb)b 10 #斷點設定在本py的第10行
或(Pdb)b ots.py:20 #斷點設定到 ots.py第20行
刪除斷點(Pdb)b #查看斷點編號
(Pdb)cl 2 #刪除第2個斷點
>運行
(Pdb)n #單步運行
(Pdb)s #細點運行 也就是會下到,方法
(Pdb)c #跳到下個斷點
>查看
(Pdb)p param #查看當前 變數值
(Pdb)l #查看運行到某處代碼
(Pdb)a #查看全部棧內變數
>如果是在 命令列裡的調試為:
import pdb def tt():
pdb.set_trace()
for i in range( 1 , 5 ):
print i
>>> tt()
# 這裡支援 n p c 而已
> < stdin > ( 3 )tt()
(Pdb) n
.
pyDev For Eclipse PyDev for Eclipse 簡介 鄭 偉芳 (zhengwf@cn.ibm.com), 軟體工程師, EMC
簡介: PyDev for Eclipse 是一個功能強大且易用的 Eclipse Python IDE 外掛程式。本文將向讀者介紹 PyDev 開源項目及其安裝配置方法,並在此基礎上詳細介紹如何利用 PyDev 外掛程式把 Eclipse 變為功能強大且易用的 Python IDE,如何利用其進行 Python 程式的開發和調試。通過本文,讀者不僅可以瞭解 PyDev 這個開源項目,更能深入瞭解如何應用 PyDev外掛程式把 Eclipse 當作 Python IDE 進行 Python 應用程式的開發和調試。
本文的標籤: pydev_introduct, python 標記本文。
PyDev 簡介
2003年7月16日,以 Fabio Zadrozny 為首的三人開發小組在全球最大的開放原始碼軟體開發平台和倉庫 SourceForge 上註冊了一款新的項目,該項目實現了一個功能強大的 Eclipse外掛程式,使用者可以完全利用 Eclipse 來進行 Python 應用程式的開發和調試。這個能夠將 Eclipse當作 Python IDE 的項目就是 PyDev。
PyDev 外掛程式的出現方便了眾多的 Python 開發人員,它提供了一些很好的功能,如:語法錯誤提示、原始碼編輯助手、Quick Outline、Globals Browser、Hierarchy View、運行和調試等等。基於 Eclipse 平台,擁有諸多強大的功能,同時也非常便於使用,PyDev 的這些特性使得它越來越受到人們的關注。
如今,該項目還在不斷地推進新的發布版本,目前最新的版本是2008年10月3日發布的1.3.22。本文接下來將介紹 PyDev 的安裝配置方法,並在此基礎上詳細介紹如何使用 PyDev把 Eclipse 當作 Python IDE 進行Python的開發和調試。
回頁首
PyDev 安裝和配置
安裝 PyDev
在安裝 PyDev 之前,要保證您已經安裝了 Java 1.4 或更高版本、Eclipse 以及 Python。接下來,開始安裝 PyDev 外掛程式。 啟動 Eclipse,利用 Eclipse Update Manager 安裝 PyDev。在 Eclipse 功能表列中找到 Help欄,選擇 Help > Software Updates > Find and Install。 選擇 Search for new features for install,然後單擊 Next。在顯示的視窗中,選擇 new remote site。此時,會彈出一個對話方塊,要求輸入新的更新網站的名稱和連結。這裡,名稱項輸入 PyDev,當然,您也可以輸入其他的名稱;連結裡輸入http://www.fabioz.com/pydev/updates,也可以填http://pydev.sourceforge.net/updates。然後,單擊 Ok。
圖 1. 新的更新網站
這樣,一個新的 PyDev 的網站就建好了,選擇該網站,然後 Finish。接下來,Eclipse 的Update Manager 將會在剛才輸入的網站中搜尋安裝包,選中搜尋出的結果 PyDev,並單擊 Next。
圖 2. 安裝 Pydev
閱讀許可證條款,如果接受,則可單擊 Next。進入安裝直接選取介面,使用預設設定,然後 Finish。 Eclipse Update Manager 將下載 PyDev,您可以從 Eclipse 工作列中看到下載的進度。下載完後,顯示一個需要您確認是否安裝的介面,單擊 Install All 便開始安裝了。
安裝完後,需要重啟 Eclipse 使安裝生效。
驗證是否成功安裝 PyDev
如何才能驗證 Eclipse Update Manager 是否已經成功安裝了所需的 PyDev 外掛程式了呢。
選擇 Help->About Eclipse SDK->Plug-in Details,將會出現 About Eclipse SDK Plug-ins 視窗,該視窗裡列出了所有已經安裝了的 Eclipse 外掛程式。檢查一下在 Plug-in Id 一欄中是否至少有五個以上分別以 com.python.pydev 和 org.python.pydev 開頭的外掛程式。如果是,那麼 PyDev已經被成功安裝,否則,安裝出了一些問題,需要根據具體問題來做具體的分析。
圖 3. 驗證 PyDev 外掛程式
配置 PyDev
安裝好 PyDev 之後,需要配置 Python/Jython 解譯器,配置過程很簡單。
在 Eclipse 功能表列中,選擇 Window > Preferences > Pydev > Interpreter - (Python/Jython),在這裡配置 Python/Jython 解譯器,下面以 Python 為例介紹如何配置。
首先需要添加已安裝的解譯器。這裡,Python 安裝在 C:\Python25 路徑下。單擊 New,選擇 Python 解譯器 python.exe,開啟後顯示出一個包含很多複選框的視窗,選擇需要加入系統 PYTHONPATH 的路徑,單擊 Ok。
圖 4. 配置 PyDev
接下來,檢查一下配置的結果是否正確。
在 System PYTHONPATH 裡,檢查是否包含配置過程中加入的那些路徑。這裡列出了所有的系統所需的庫檔案夾。
另外,在 Forced builtin libs 裡,列出了 Python 的內建庫。對於 Python 而言,這樣的內建庫大約有50個,而對於 Jython 來說,則有30個左右。
這樣,Python 解譯器就配置好了。
回頁首
PyDev Package Explorer
建立項目
在開展工作之前,需要建立一個新的項目。在 Eclipse 功能表列中,選擇 File > New > Project > Pydev > Pydev Project,單擊 Next。
圖 5. 建立 Pydev 項目
這時,顯示出 Pydev Project 視窗,輸入項目名稱、選擇工作路徑、選擇 Python 解譯器的版本類型並選中複選框,然後單擊 Next,進入關聯項目的視窗,如果不需要關聯其他項目,則可以直接單擊 Finish,完成項目的建立。
建立 Python 包和模組
接下來,在剛建立的項目中開始建立 Python 包和模組。 進入 Pydev 透視圖,在 Python Package Explorer 中,按右鍵 src,選擇 New->Pydev Package,輸入 Package 名稱並單擊 Finish,Python 包就建立好了,此時,自動產生__init__.py 檔案,該檔案不包含任何內容。
注意:如果在建立項目的時候沒有選中“Create default src folder and add it to the pythonpath”複選框,則需要通過 File > New > Other > Source Folder 手動建立一個原始碼檔案夾。 建立完 Pydev Package 後,按右鍵建立的包,選擇 New->Pydev Module,輸入模組名稱,單擊 Finish。這樣,Python 模組就建成了。
編輯來源程式
對於來源程式的一些基本編輯方法,就不做介紹了。下面介紹幾點 Pydev 提供的非常實用的編輯功能。 語法錯誤提示
Python 開發人員在建立修改程式的過程中,如果能及時發現編輯過程中出現的語法錯誤,無疑對整個項目開發的品質和進展都是非常重要的。在 Python 透視圖中,Pydev Package Explorer 中列出了項目的原始碼,雙擊其中某一個 Python 檔案,如果該檔案包含語法錯誤,錯誤會以很醒目的方式展現出來。
圖 6. Pydev 檔案語法錯誤提示
如果想把整個項目中所有包含語法錯誤的檔案顯示出來,可以從 Python 透視圖自由切換到 Java 透視圖。在 Java Package 裡,一個個醒目的小紅叉標記了所有包含語法錯誤的 Python檔案。
圖 7. Pydev 項目語法錯誤提示